code:
/obj/item/device/onetankbomb
	name = "bomb"
	icon = 'icons/obj/tank.dmi'
	item_state = "assembly"
	throwforce = 5
	w_class = 3
	throw_speed = 2
	throw_range = 4
	flags = CONDUCT
	var/status = 0   //0 - not readied //1 - bomb finished with welder
	var/obj/item/device/assembly_holder/bombassembly = null   //The first part of the bomb is an assembly holder, holding an igniter+some device
	var/obj/item/weapon/tank/bombtank = null //the second part of the bomb is a plasma tank

/obj/item/device/onetankbomb/examine(mob/user)
	..()
	user.examinate(bombtank)

/obj/item/device/onetankbomb/update_icon()
	if(bombtank)
		icon_state = bombtank.icon_state
	if(bombassembly)
		overlays += bombassembly.icon_state
		overlays += bombassembly.overlays
		overlays += "bomb_assembly"

/obj/item/device/onetankbomb/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/device/analyzer))
		bombtank.attackby(W, user)
		return
	if(istype(W, /obj/item/weapon/wrench) && !status)	//This is basically bomb assembly code inverted. apparently it works.

		user << "You disassemble [src]."

		bombassembly.loc = user.loc
		bombassembly.master = null
		bombassembly = null

		bombtank.loc = user.loc
		bombtank.master = null
		bombtank = null

		qdel(src)
		return
	if((istype(W, /obj/item/weapon/weldingtool) && W:welding))
		if(!status)
			status = 1
			bombers += "[key_name(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]"
			message_admins("[key_name_admin(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]")
			user << "A pressure hole has been bored to [bombtank] valve. \The [bombtank] can now be ignited."
		else
			status = 0
			bombers += "[key_name(user)] unwelded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]"
			user << "The hole has been closed."
	add_fingerprint(user)
	..()

/obj/item/device/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly
	bombassembly.attack_self(user, 1)
	add_fingerprint(user)
	return

/obj/item/device/onetankbomb/receive_signal()	//This is mainly called by the sensor through sense() to the holder, and from the holder to here.
	visible_message("\icon[src] *beep* *beep*", "*beep* *beep*")
	sleep(10)
	if(!src)
		return
	if(status)
		bombtank.ignite()	//if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file
	else
		bombtank.release()

/obj/item/device/onetankbomb/Crossed(atom/movable/AM as mob|obj) //for mousetraps
	if(bombassembly)
		bombassembly.Crossed(AM)

/obj/item/device/onetankbomb/on_found(mob/finder) //for mousetraps
	if(bombassembly)
		bombassembly.on_found(finder)

// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ----------

/obj/item/weapon/tank/proc/bomb_assemble(W,user)	//Bomb assembly proc. This turns assembly+tank into a bomb
	var/obj/item/device/assembly_holder/S = W
	var/mob/M = user
	if(isigniter(S.a_left) == isigniter(S.a_right))		//Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it
		return
	if(!M.drop_item())			//Remove the assembly from your hands
		return

	var/obj/item/device/onetankbomb/R = new /obj/item/device/onetankbomb(loc)

	M.remove_from_mob(src)	//Remove the tank from your character,in case you were holding it
	M.put_in_hands(R)		//Equips the bomb if possible, or puts it on the floor.

	R.bombassembly = S	//Tell the bomb about its assembly part
	S.master = R		//Tell the assembly about its new owner
	S.loc = R			//Move the assembly out of the fucking way

	R.bombtank = src	//Same for tank
	master = R
	loc = R
	R.update_icon()
	return

/obj/item/weapon/tank/proc/ignite()	//This happens when a bomb is told to explode
	air_contents.assert_gases("plasma", "o2")
	var/fuel_moles = air_contents.gases["plasma"][MOLES] + air_contents.gases["o2"][MOLES]/6
	air_contents.garbage_collect()

	var/strength = 1

	var/turf/ground_zero = get_turf(loc)
	loc = null

	if(air_contents.temperature > (T0C + 400))
		strength = (fuel_moles/15)

		if(strength >=1)
			explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1))
		else if(strength >=0.5)
			explosion(ground_zero, 0, 1, 2, 4)
		else if(strength >=0.2)
			explosion(ground_zero, -1, 0, 1, 2)
		else
			ground_zero.assume_air(air_contents)
			ground_zero.hotspot_expose(1000, 125)

	else if(air_contents.temperature > (T0C + 250))
		strength = (fuel_moles/20)

		if(strength >=1)
			explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1))
		else if (strength >=0.5)
			explosion(ground_zero, -1, 0, 1, 2)
		else
			ground_zero.assume_air(air_contents)
			ground_zero.hotspot_expose(1000, 125)

	else if(air_contents.temperature > (T0C + 100))
		strength = (fuel_moles/25)

		if (strength >=1)
			explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1))
		else
			ground_zero.assume_air(air_contents)
			ground_zero.hotspot_expose(1000, 125)

	else
		ground_zero.assume_air(air_contents)
		ground_zero.hotspot_expose(1000, 125)

	air_update_turf()
	if(master)
		qdel(master)
	qdel(src)

/obj/item/weapon/tank/proc/release()	//This happens when the bomb is not welded. Tank contents are just spat out.
	var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles())
	var/turf/T = get_turf(src)
	if(!T)
		return
	T.assume_air(removed)
	air_update_turf()


code:
/obj/item/device/assembly/control
	name = "blast door controller"
	desc = "A small electronic device able to control a blast door remotely."
	icon_state = "control"
	origin_tech = "magnets=1;programming=2"
	attachable = 1
	var/id = null
	var/can_change_id = 0

/obj/item/device/assembly/control/examine(mob/user)
	..()
	if(id)
		user << "Its channel ID is '[id]'."

/obj/item/device/assembly/control/activate()
	cooldown = 1
	var/openclose
	for(var/obj/machinery/door/poddoor/M in machines)
		if(M.id == src.id)
			if(openclose == null)
				openclose = M.density
			spawn(0)
				if(M)
					if(openclose)
						M.open()
					else
						M.close()
				return
	sleep(10)
	cooldown = 0

/obj/item/device/assembly/control/airlock
	name = "airlock controller"
	desc = "A small electronic device able to control an airlock remotely."
	id = "badmin" // Set it to null for MEGAFUN.
	var/specialfunctions = OPEN
	/*
	Bitflag, 	1= open (OPEN)
				2= idscan (IDSCAN)
				4= bolts (BOLTS)
				8= shock (SHOCK)
				16= door safties (SAFE)
	*/

/obj/item/device/assembly/control/airlock/activate()
	cooldown = 1
	for(var/obj/machinery/door/airlock/D in airlocks)
		if(D.id_tag == src.id)
			if(specialfunctions & OPEN)
				if(D.density)
					D.open()
				else
					D.close()
			if(specialfunctions & IDSCAN)
				D.aiDisabledIdScanner = !D.aiDisabledIdScanner
			if(specialfunctions & BOLTS)
				if(!D.wires.is_cut(WIRE_BOLTS) && D.hasPower())
					D.locked = !D.locked
					D.update_icon()
			if(specialfunctions & SHOCK)
				D.secondsElectrified = D.secondsElectrified ? 0 : -1
			if(specialfunctions & SAFE)
				D.safe = !D.safe
	sleep(10)
	cooldown = 0

/obj/item/device/assembly/control/massdriver
	name = "mass driver controller"
	desc = "A small electronic device able to control a mass driver."

/obj/item/device/assembly/control/massdriver/activate()
	cooldown = 1
	for(var/obj/machinery/door/poddoor/M in machines)
		if (M.id == src.id)
			spawn( 0 )
				M.open()

	sleep(10)

	for(var/obj/machinery/mass_driver/M in machines)
		if(M.id == src.id)
			M.drive()

	sleep(60)

	for(var/obj/machinery/door/poddoor/M in machines)
		if (M.id == src.id)
			spawn( 0 )
				M.close()

	sleep(10)
	cooldown = 0

/obj/item/device/assembly/control/igniter
	name = "ignition controller"
	desc = "A remote controller for a mounted igniter."

/obj/item/device/assembly/control/igniter/activate()
	cooldown = 1
	for(var/obj/machinery/sparker/M in machines)
		if (M.id == src.id)
			spawn( 0 )
				M.ignite()

	for(var/obj/machinery/igniter/M in machines)
		if(M.id == src.id)
			M.use_power(50)
			M.on = !M.on
			M.icon_state = "igniter[M.on]"

	sleep(30)
	cooldown = 0

/obj/item/device/assembly/control/flasher
	name = "flasher controller"
	desc = "A remote controller for a mounted flasher."

/obj/item/device/assembly/control/flasher/activate()
	cooldown = 1
	for(var/obj/machinery/flasher/M in machines)
		if(M.id == src.id)
			spawn(0)
				M.flash()

	sleep(50)
	cooldown = 0

/obj/item/device/assembly/control/crematorium
	name = "crematorium controller"
	desc = "An evil-looking remote controller for a crematorium."

/obj/item/device/assembly/control/crematorium/activate()
	cooldown = 1
	for (var/obj/structure/bodycontainer/crematorium/C in crematoriums)
		if (C.id == id)
			C.cremate(usr)

	sleep(50)
	cooldown = 0

license: mit


code:
/obj/item/device/assembly/flash
	name = "flash"
	desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production."
	icon_state = "flash"
	item_state = "flashtool"
	throwforce = 0
	w_class = 1
	materials = list(MAT_METAL = 300, MAT_GLASS = 300)
	origin_tech = "magnets=2;combat=1"

	crit_fail = 0     //Is the flash burnt out?
	var/times_used = 0 //Number of times it's been used.
	var/last_used = 0 //last world.time it was used.

/obj/item/device/assembly/flash/update_icon(var/flash = 0)
	overlays.Cut()
	attached_overlays = list()
	if(crit_fail)
		overlays += "flashburnt"
		attached_overlays += "flashburnt"

	if(flash)
		overlays += "flash-f"
		attached_overlays += "flash-f"
		spawn(5)
			update_icon()
	if(holder)
		holder.update_icon()

/obj/item/device/assembly/flash/proc/clown_check(mob/living/carbon/human/user)
	if(user.disabilities & CLUMSY && prob(50))
		flash_carbon(user, user, 15, 0)
		return 0
	return 1

/obj/item/device/assembly/flash/activate()
	if(!try_use_flash())
		return 0
	var/turf/T = get_turf(src)
	T.visible_message("[src] emits a blinding light!")
	for(var/mob/living/carbon/M in viewers(3, T))
		flash_carbon(M, null, 5, 0)

/obj/item/device/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something.
	if(!crit_fail)
		crit_fail = 1
		update_icon()
		var/turf/T = get_turf(src)
		T.visible_message("The [src.name] burns out!")

/obj/item/device/assembly/flash/proc/flash_recharge(interval=10)
	if(prob(times_used * 3)) //The more often it's used in a short span of time the more likely it will burn out
		burn_out()
		return 0

	var/deciseconds_passed = world.time - last_used
	for(var/seconds = deciseconds_passed/10, seconds>=interval, seconds-=interval) //get 1 charge every interval
		times_used--

	last_used = world.time
	times_used = max(0, times_used) //sanity
	return 1

/obj/item/device/assembly/flash/proc/try_use_flash(mob/user = null)
	flash_recharge(10)

	if(crit_fail)
		return 0

	playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1)
	update_icon(1)
	times_used++

	if(user && !clown_check(user))
		return 0

	return 1

/obj/item/device/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user = null, power = 15, targeted = 1)
	add_logs(user, M, "flashed", src)
	if(user && targeted)
		if(M.weakeyes)
			M.Weaken(3) //quick weaken bypasses eye protection but has no eye flash
		if(M.flash_eyes(1, 1))
			M.confused += power
			terrible_conversion_proc(M, user)
			M.Stun(1)
			visible_message("[user] blinds [M] with the flash!")
			user << "You blind [M] with the flash!"
			M << "[user] blinds you with the flash!"
			if(M.weakeyes)
				M.Stun(2)
				M.visible_message("[M] gasps and shields their eyes!", "You gasp and shields your eyes!")
		else
			visible_message("[user] fails to blind [M] with the flash!")
			user << "You fail to blind [M] with the flash!"
			M << "[user] fails to blind you with the flash!"
	else
		if(M.flash_eyes())
			M.confused += power

/obj/item/device/assembly/flash/attack(mob/living/M, mob/user)
	if(!try_use_flash(user))
		return 0

	if(iscarbon(M))
		flash_carbon(M, user, 5, 1)
		return 1

	else if(issilicon(M))
		add_logs(user, M, "flashed", src)
		update_icon(1)
		M.Weaken(rand(5,10))
		user.visible_message("[user] overloads [M]'s sensors with the flash!", "You overload [M]'s sensors with the flash!")
		return 1

	user.visible_message("[user] fails to blind [M] with the flash!", "You fail to blind [M] with the flash!")

/obj/item/device/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0)
	if(holder)
		return 0
	if(!try_use_flash(user))
		return 0
	user.visible_message("[user]'s flash emits a blinding light!", "Your flash emits a blinding light!")
	for(var/mob/living/carbon/M in oviewers(3, null))
		flash_carbon(M, user, 1, 0)

/obj/item/device/assembly/flash/emp_act(severity)
	if(!try_use_flash() || !loc)
		return 0
	for(var/mob/living/carbon/M in viewers(3, loc))
		flash_carbon(M, null, 10, 0)
	burn_out()
	..()

/obj/item/device/assembly/flash/proc/terrible_conversion_proc(mob/M, mob/user)
	if(ishuman(M) && ishuman(user) && M.stat != DEAD)
		if(user.mind && (user.mind in ticker.mode.head_revolutionaries))
			if(M.client)
				if(M.stat == CONSCIOUS)
					M.mind_initialize() //give them a mind datum if they don't have one.
					var/resisted
					if(!isloyal(M))
						if(user.mind in ticker.mode.head_revolutionaries)
							if(ticker.mode.add_revolutionary(M.mind))
								times_used -- //Flashes less likely to burn out for headrevs when used for conversion
							else
								resisted = 1
					else
						resisted = 1

					if(resisted)
						user << "This mind seems resistant to the flash!"
				else
					user << "They must be conscious before you can convert them!"
			else
				user << "This mind is so vacant that it is not susceptible to influence!"

/obj/item/device/assembly/flash/cyborg
	origin_tech = null

/obj/item/device/assembly/flash/cyborg/attack(mob/living/M, mob/user)
	..()
	cyborg_flash_animation(user)

/obj/item/device/assembly/flash/cyborg/attack_self(mob/user)
	..()
	cyborg_flash_animation(user)

/obj/item/device/assembly/flash/cyborg/attackby(obj/item/weapon/W, mob/user, params)
	return

/obj/item/device/assembly/flash/cyborg/proc/cyborg_flash_animation(mob/living/user)
	var/atom/movable/overlay/animation = new(user.loc)
	animation.layer = user.layer + 1
	animation.icon_state = "blank"
	animation.icon = 'icons/mob/mob.dmi'
	animation.master = user
	flick("blspell", animation)
	sleep(5)
	qdel(animation)

/obj/item/device/assembly/flash/memorizer
	name = "memorizer"
	desc = "If you see this, you're not likely to remember it any time soon."
	icon = 'icons/obj/device.dmi'
	icon_state = "memorizer"
	item_state = "nullrod"

/obj/item/device/assembly/flash/handheld //this is now the regular pocket flashes


code:
/obj/item/device/assembly/health
	name = "health sensor"
	desc = "Used for scanning and monitoring health."
	icon_state = "health"
	materials = list(MAT_METAL=800, MAT_GLASS=200)
	origin_tech = "magnets=1;biotech=1"
	attachable = 1
	secured = 0

	var/scanning = 0
	var/health_scan
	var/alarm_health = 0

/obj/item/device/assembly/health/activate()
	if(!..())
		return 0//Cooldown check
	toggle_scan()
	return 0

/obj/item/device/assembly/health/toggle_secure()
	secured = !secured
	if(secured && scanning)
		SSobj.processing |= src
	else
		scanning = 0
		SSobj.processing.Remove(src)
	update_icon()
	return secured

/obj/item/device/assembly/health/attackby(obj/item/weapon/W as obj, mob/user as mob)
	if(istype(W, /obj/item/device/multitool))
		if(alarm_health == 0)
			alarm_health = -90
			user.show_message("You toggle [src] to \"detect death\" mode.")
		else
			alarm_health = 0
			user.show_message("You toggle [src] to \"detect critical state\" mode.")
		return
	else
		return ..()

/obj/item/device/assembly/health/process()
	if(!scanning || !secured)
		return

	var/atom/A = src
	if(connected && connected.holder)
		A = connected.holder

	for(A, A && !ismob(A), A=A.loc);
	// like get_turf(), but for mobs.
	var/mob/living/M = A

	if(M)
		health_scan = M.health
		if(health_scan <= alarm_health)
			pulse()
			audible_message("\icon[src] *beep* *beep*", "*beep* *beep*")
			toggle_scan()
		return
	return

/obj/item/device/assembly/health/proc/toggle_scan()
	if(!secured)
		return 0
	scanning = !scanning
	if(scanning)
		SSobj.processing |= src
	else
		SSobj.processing.Remove(src)
	return

/obj/item/device/assembly/health/interact(mob/user as mob)//TODO: Change this to the wires thingy
	if(!secured)
		user.show_message("The [name] is unsecured!")
		return 0
	var/dat = "<TT>Health Sensor [scanning?"On":"Off"]"
	if(scanning && health_scan)
		dat += "
Health: [health_scan]"
	user << browse(dat, "window=hscan")
	onclose(user, "hscan")
	return

/obj/item/device/assembly/health/Topic(href, href_list)
	..()
	if(!ismob(usr))
		return

	var/mob/user = usr

	if(!user.canUseTopic(user))
		usr << browse(null, "window=hscan")
		onclose(usr, "hscan")
		return

	if(href_list["scanning"])
		toggle_scan()

	if(href_list["close"])
		usr << browse(null, "window=hscan")
		return

	attack_self(user)
	return


code:
// See _DEFINES/is_helpers.dm for type helpers

/*
Name:	IsSpecialAssembly
Desc:	If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door
*/

/obj/proc/IsSpecialAssembly()
	return 0

/*
Name:	IsAssemblyHolder
Desc:	If true is an object that can hold an assemblyholder object
*/
/obj/proc/IsAssemblyHolder()
	return 0


code:
/obj/item/device/assembly_holder
	name = "Assembly"
	icon = 'icons/obj/assemblies/new_assemblies.dmi'
	icon_state = "holder"
	item_state = "assembly"
	flags = CONDUCT
	throwforce = 5
	w_class = 2
	throw_speed = 2
	throw_range = 7

	var/obj/item/device/assembly/a_left = null
	var/obj/item/device/assembly/a_right = null

/obj/item/device/assembly_holder/IsAssemblyHolder()
	return 1

/obj/item/device/assembly_holder/proc/assemble(obj/item/device/assembly/A, obj/item/device/assembly/A2, mob/user)
	attach(A,user)
	attach(A2,user)
	name = "[A.name]-[A2.name] assembly"
	update_icon()
	feedback_add_details("assembly_made","[A.name]-[A2.name]")

/obj/item/device/assembly_holder/proc/attach(obj/item/device/assembly/A, mob/user)
	if(!A.remove_item_from_storage(src))
		if(user)
			user.remove_from_mob(A)
		A.loc = src
	A.holder = src
	A.toggle_secure()
	if(!a_left)
		a_left = A
	else
		a_right = A

/obj/item/device/assembly_holder/update_icon()
	overlays.Cut()
	if(a_left)
		overlays += "[a_left.icon_state]_left"
		for(var/O in a_left.attached_overlays)
			overlays += "[O]_l"

	if(a_right)
		var/list/images = list()
		images += image(icon, icon_state = "[a_right.icon_state]_left")
		for(var/O in a_right.attached_overlays)
			images += image(icon, icon_state = "[O]_l")
		var/matrix = matrix(-1, 0, 0, 0, 1, 0)
		for(var/image/I in images)
			I.transform = matrix
			overlays += I

	if(master)
		master.update_icon()

/obj/item/device/assembly_holder/Crossed(atom/movable/AM as mob|obj)
	if(a_left)
		a_left.Crossed(AM)
	if(a_right)
		a_right.Crossed(AM)

/obj/item/device/assembly_holder/on_found(mob/finder)
	if(a_left)
		a_left.on_found(finder)
	if(a_right)
		a_right.on_found(finder)

/obj/item/device/assembly_holder/Move()
	..()
	if(a_left && a_right)
		a_left.holder_movement()
		a_right.holder_movement()
	return

/obj/item/device/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess
	if(a_left && a_right)
		a_left.holder_movement()
		a_right.holder_movement()
	..()
	return

/obj/item/device/assembly_holder/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/weapon/screwdriver))
		var/turf/T = get_turf(src)
		if(!T)
			return 0
		if(a_left)
			a_left.holder = null
			a_left.loc = T
		if(a_right)
			a_right.holder = null
			a_right.loc = T
		qdel(src)
	else
		..()

/obj/item/device/assembly_holder/attack_self(mob/user)
	src.add_fingerprint(user)
	if(!a_left || !a_right)
		user << "Assembly part missing!"
		return
	if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code
		switch(alert("Which side would you like to use?",,"Left","Right"))
			if("Left")
				a_left.attack_self(user)
			if("Right")
				a_right.attack_self(user)
		return
	else
		a_left.attack_self(user)
		a_right.attack_self(user)

/obj/item/device/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1)
	if(!D)
		return 0
	if((normal) && (a_right) && (a_left))
		if(a_right != D)
			a_right.pulsed(0)
		if(a_left != D)
			a_left.pulsed(0)
	if(master)
		master.receive_signal()
	return 1


code:
/obj/item/device/assembly/igniter
	name = "igniter"
	desc = "A small electronic device able to ignite combustable substances."
	icon_state = "igniter"
	materials = list(MAT_METAL=500, MAT_GLASS=50)
	origin_tech = "magnets=1"
	var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread
	heat = 1000

/obj/item/device/assembly/igniter/New()
	..()
	sparks.set_up(2, 0, src)
	sparks.attach(src)

/obj/item/device/assembly/igniter/Destroy()
	qdel(sparks)
	sparks = null
	return ..()

/obj/item/device/assembly/igniter/activate()
	if(!..())
		return 0//Cooldown check
	var/turf/location = get_turf(loc)
	if(location)
		location.hotspot_expose(1000,1000)
	sparks.start()
	return 1

/obj/item/device/assembly/igniter/attack_self(mob/user)
	activate()
	add_fingerprint(user)
	return


code:
/obj/item/device/assembly/infra
	name = "infrared emitter"
	desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted.\nAlt-click to rotate it clockwise."
	icon_state = "infrared"
	materials = list(MAT_METAL=1000, MAT_GLASS=500)
	origin_tech = "magnets=2"

	var/on = 0
	var/visible = 0
	var/obj/effect/beam/i_beam/first = null
	var/obj/effect/beam/i_beam/last = null

/obj/item/device/assembly/infra/New()
	..()
	SSobj.processing |= src

/obj/item/device/assembly/infra/Destroy()
	if(first)
		qdel(first)
	return ..()

/obj/item/device/assembly/infra/describe()
	return "The infrared trigger is [on?"on":"off"]."

/obj/item/device/assembly/infra/activate()
	if(!..())
		return 0//Cooldown check
	on = !on
	update_icon()
	return 1

/obj/item/device/assembly/infra/toggle_secure()
	secured = !secured
	if(secured)
		SSobj.processing |= src
	else
		on = 0
		if(first)
			qdel(first)
		SSobj.processing.Remove(src)
	update_icon()
	return secured

/obj/item/device/assembly/infra/update_icon()
	overlays.Cut()
	attached_overlays = list()
	if(on)
		overlays += "infrared_on"
		attached_overlays += "infrared_on"

	if(holder)
		holder.update_icon()
	return

/obj/item/device/assembly/infra/process()
	if(!on)
		if(first)
			qdel(first)
		return
	if(!secured)
		return
	if(first && last)
		last.process()
		return
	var/turf/T = get_turf(src)
	if(T)
		var/obj/effect/beam/i_beam/I = new /obj/effect/beam/i_beam(T)
		I.master = src
		I.density = 1
		I.dir = dir
		first = I
		step(I, I.dir)
		if(first)
			I.density = 0
			I.vis_spread(visible)
			I.limit = 8
			I.process()

/obj/item/device/assembly/infra/attack_hand()
	qdel(first)
	..()
	return

/obj/item/device/assembly/infra/Move()
	var/t = dir
	..()
	dir = t
	qdel(first)
	return

/obj/item/device/assembly/infra/holder_movement()
	if(!holder)
		return 0
//	dir = holder.dir
	qdel(first)
	return 1

/obj/item/device/assembly/infra/proc/trigger_beam()
	if((!secured)||(!on)||(cooldown > 0))
		return 0
	pulse(0)
	audible_message("\icon[src] *beep* *beep*", null, 3)
	cooldown = 2
	spawn(10)
		process_cooldown()
	return

/obj/item/device/assembly/infra/interact(mob/user)//TODO: change this this to the wire control panel
	if(is_secured(user))
		user.set_machine(src)
		var/dat = "<TT>Infrared Laser\nStatus: [on ? "On" : "Off"]
\nVisibility: [visible ? "Visible" : "Invisible"]
\n</TT>"
		dat += "

Refresh"
		dat += "

Close"
		user << browse(dat, "window=infra")
		onclose(user, "infra")
		return

/obj/item/device/assembly/infra/Topic(href, href_list)
	..()
	if(usr.incapacitated() || !in_range(loc, usr))
		usr << browse(null, "window=infra")
		onclose(usr, "infra")
		return
	if(href_list["state"])
		on = !(on)
		update_icon()
	if(href_list["visible"])
		visible = !(visible)
		if(first)
			first.vis_spread(visible)
	if(href_list["close"])
		usr << browse(null, "window=infra")
		return
	if(usr)
		attack_self(usr)

/obj/item/device/assembly/infra/verb/rotate()//This could likely be better
	set name = "Rotate Infrared Laser"
	set category = "Object"
	set src in usr

	if(usr.incapacitated())
		return

	dir = turn(dir, 90)
	return

/obj/item/device/assembly/infra/AltClick(mob/user)
	..()
	if(user.incapacitated())
		user << "You can't do that right now!"
		return
	if(!in_range(src, user))
		return
	else
		rotate()

/***************************IBeam*********************************/

/obj/effect/beam/i_beam
	name = "infrared beam"
	icon = 'icons/obj/projectiles.dmi'
	icon_state = "ibeam"
	var/obj/effect/beam/i_beam/next = null
	var/obj/effect/beam/i_beam/previous = null
	var/obj/item/device/assembly/infra/master = null
	var/limit = null
	var/visible = 0
	var/left = null
	anchored = 1

/obj/effect/beam/i_beam/proc/hit()
	if(master)
		master.trigger_beam()
	qdel(src)
	return

/obj/effect/beam/i_beam/proc/vis_spread(v)
	visible = v
	if(next)
		next.vis_spread(v)

/obj/effect/beam/i_beam/process()
	if((loc.density || !(master)))
		qdel(src)
		return
	if(left > 0)
		left--
	if(left < 1)
		if(!(visible))
			invisibility = 101
		else
			invisibility = 0
	else
		invisibility = 0

	if(!next && (limit > 0))
		var/obj/effect/beam/i_beam/I = new /obj/effect/beam/i_beam(loc)
		I.master = master
		I.density = 1
		I.dir = dir
		I.previous = src
		next = I
		step(I, I.dir)
		if(next)
			I.density = 0
			I.vis_spread(visible)
			I.limit = limit - 1
			master.last = I
			I.process()

/obj/effect/beam/i_beam/Bump()
	qdel(src)
	return

/obj/effect/beam/i_beam/Bumped()
	hit()

/obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj)
	if(istype(AM, /obj/effect/beam))
		return
	hit()

/obj/effect/beam/i_beam/Destroy()
	if(master.first == src)
		master.first = null
	if(next)
		qdel(next)
		next = null
	if(previous)
		previous.next = null
		master.last = previous
	return ..()


code:
/obj/item/device/assembly/mousetrap
	name = "mousetrap"
	desc = "A handy little spring-loaded trap for catching pesty rodents."
	icon_state = "mousetrap"
	materials = list(MAT_METAL=100)
	origin_tech = "combat=1"
	attachable = 1
	var/armed = 0

/obj/item/device/assembly/mousetrap/examine(mob/user)
	..()
	if(armed)
		user << "It looks like it's armed."

/obj/item/device/assembly/mousetrap/activate()
	if(..())
		armed = !armed
		if(!armed)
			if(ishuman(usr))
				var/mob/living/carbon/human/user = usr
				if((user.getBrainLoss() >= 60) || user.disabilities & CLUMSY && prob(50))
					user << "Your hand slips, setting off the trigger!"
					pulse(0)
		update_icon()
		if(usr)
			playsound(usr.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3)

/obj/item/device/assembly/mousetrap/describe()
	return "The pressure switch is [armed?"primed":"safe"]."

/obj/item/device/assembly/mousetrap/update_icon()
	if(armed)
		icon_state = "mousetraparmed"
	else
		icon_state = "mousetrap"
	if(holder)
		holder.update_icon()

/obj/item/device/assembly/mousetrap/proc/triggered(mob/target, type = "feet")
	if(!armed)
		return
	var/obj/item/organ/limb/affecting = null
	if(ishuman(target))
		var/mob/living/carbon/human/H = target
		if(PIERCEIMMUNE in H.dna.species.specflags)
			playsound(src.loc, 'sound/effects/snap.ogg', 50, 1)
			armed = 0
			update_icon()
			pulse(0)
			return 0
		switch(type)
			if("feet")
				if(!H.shoes)
					affecting = H.get_organ(pick("l_leg", "r_leg"))
					H.Weaken(3)
			if("l_hand", "r_hand")
				if(!H.gloves)
					affecting = H.get_organ(type)
					H.Stun(3)
		if(affecting)
			if(affecting.take_damage(1, 0))
				H.update_damage_overlays(0)
			H.updatehealth()
	else if(ismouse(target))
		var/mob/living/simple_animal/mouse/M = target
		visible_message("SPLAT!")
		M.splat()
	playsound(src.loc, 'sound/effects/snap.ogg', 50, 1)
	armed = 0
	update_icon()
	pulse(0)

/obj/item/device/assembly/mousetrap/attack_self(mob/living/carbon/human/user)
	if(!armed)
		user << "You arm [src]."
	else
		if(((user.getBrainLoss() >= 60) || user.disabilities & CLUMSY) && prob(50))
			var/which_hand = "l_hand"
			if(!user.hand)
				which_hand = "r_hand"
			triggered(user, which_hand)
			user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
								 "You accidentally trigger [src]!")
			return
		user << "You disarm [src]."
	armed = !armed
	update_icon()
	playsound(user.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3)

/obj/item/device/assembly/mousetrap/attack_hand(mob/living/carbon/human/user)
	if(armed)
		if(((user.getBrainLoss() >= 60) || user.disabilities & CLUMSY) && prob(50))
			var/which_hand = "l_hand"
			if(!user.hand)
				which_hand = "r_hand"
			triggered(user, which_hand)
			user.visible_message("[user] accidentally sets off [src], breaking their fingers.", \
								 "You accidentally trigger [src]!")
			return
	..()

/obj/item/device/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj)
	if(armed)
		if(ishuman(AM))
			var/mob/living/carbon/H = AM
			if(H.m_intent == "run")
				triggered(H)
				H.visible_message("[H] accidentally steps on [src].", \
								  "You accidentally step on [src]")
		else if(isanimal(AM))
			var/mob/living/simple_animal/SA = AM
			if(!SA.flying)
				triggered(AM)
		else if(AM.density) // For mousetrap grenades, set off by anything heavy
			triggered(AM)
	..()

/obj/item/device/assembly/mousetrap/on_found(mob/finder)
	if(armed)
		finder.visible_message("[finder] accidentally sets off [src], breaking their fingers.", \
							   "You accidentally trigger [src]!")
		triggered(finder, finder.hand ? "l_hand" : "r_hand")
		return 1	//end the search!
	return 0

/obj/item/device/assembly/mousetrap/hitby(A as mob|obj)
	if(!armed)
		return ..()
	visible_message("[src] is triggered by [A].")
	triggered(null)

/obj/item/device/assembly/mousetrap/armed
	icon_state = "mousetraparmed"
	armed = 1


code:
/obj/item/device/assembly/prox_sensor
	name = "proximity sensor"
	desc = "Used for scanning and alerting when someone enters a certain proximity."
	icon_state = "prox"
	materials = list(MAT_METAL=800, MAT_GLASS=200)
	origin_tech = "magnets=1"
	attachable = 1

	var/scanning = 0
	var/timing = 0
	var/time = 10
	var/sensitivity = 1
	var/atom/oldloc
	var/list/turfs_around = list()

/obj/item/device/assembly/prox_sensor/proc/toggle_scan()

/obj/item/device/assembly/prox_sensor/proc/sense()

/obj/item/device/assembly/prox_sensor/New()
	..()
	SSobj.processing |= src
	oldloc = loc

/obj/item/device/assembly/prox_sensor/describe()
	if(timing)
		return "The proximity sensor is arming."
	return "The proximity sensor is [scanning?"armed":"disarmed"]."

/obj/item/device/assembly/prox_sensor/on_attach(datum/wires/w)
	handle_move(w.holder)

/obj/item/device/assembly/prox_sensor/on_detach(datum/wires/w)
	handle_move(w.holder.loc)

/obj/item/device/assembly/prox_sensor/activate()
	if(!..())
		return 0//Cooldown check
	timing = !timing
	update_icon()
	return 1

/obj/item/device/assembly/prox_sensor/toggle_secure()
	secured = !secured
	if(!secured)
		scanning = 0
		timing = 0
	update_icon()
	return secured

/obj/item/device/assembly/prox_sensor/HasProximity(atom/movable/AM as mob|obj)
	if (istype(AM, /obj/effect/beam))
		return
	sense()

/obj/item/device/assembly/prox_sensor/sense()
	if((!secured)||(cooldown > 0))
		return 0
	pulse(0)
	audible_message("\icon[src] *beep* *beep*", null, 3)
	cooldown = 2
	spawn(10)
		process_cooldown()

/obj/item/device/assembly/prox_sensor/process()
	if(timing)
		time--
		if(time <= 0)
			timing = 0
			toggle_scan(1)
			time = initial(time)
	handle_move(get_turf(loc))

/obj/item/device/assembly/prox_sensor/dropped()
	..()
	if(scanning)
		spawn(0)
			sense()

/obj/item/device/assembly/prox_sensor/toggle_scan(scan)
	if(!secured)
		return 0
	scanning = scan
	if(scanning)
		add_to_proximity_list(src, sensitivity)
	else
		remove_from_proximity_list(src, sensitivity)
	oldloc = get_turf(loc)
	update_icon()

/obj/item/device/assembly/prox_sensor/proc/sensitivity_change(value)
	var/sense = min(max(sensitivity + value, 0), 5)
	if(scanning)
		shift_proximity(src, oldloc, sensitivity, loc, sense)
	sensitivity = sense

/obj/item/device/assembly/prox_sensor/update_icon()
	overlays.Cut()
	attached_overlays = list()
	if(timing)
		overlays += "prox_timing"
		attached_overlays += "prox_timing"
	if(scanning)
		overlays += "prox_scanning"
		attached_overlays += "prox_scanning"
	if(holder)
		holder.update_icon()
	return

/obj/item/device/assembly/prox_sensor/proc/handle_move(atom/newloc)
	if(scanning)
		if(shift_proximity(src, oldloc, sensitivity, newloc, sensitivity))
			sense()
			oldloc = newloc

/obj/item/device/assembly/prox_sensor/Move(newloc)
	..()
	handle_move(newloc)

/obj/item/device/assembly/prox_sensor/interact(mob/user)//TODO: Change this to the wires thingy
	if(is_secured(user))
		var/second = time % 60
		var/minute = (time - second) / 60
		var/dat = "<TT>Proximity Sensor\n[(timing ? "Arming" : "Not Arming")] [minute]:[second]\n- - + +\n</TT>"
		dat += "
Armed":"1'>Unarmed"] (Movement sensor active when armed!)"
		dat += "
Detection range: - [sensitivity] +"
		dat += "

Refresh"
		dat += "

Close"
		user << browse(dat, "window=prox")
		onclose(user, "prox")
		return

/obj/item/device/assembly/prox_sensor/Topic(href, href_list)
	..()
	if(usr.incapacitated() || !in_range(loc, usr))
		usr << browse(null, "window=prox")
		onclose(usr, "prox")
		return

	if(href_list["sense"])
		sensitivity_change(((href_list["sense"] == "up") ? 1 : -1))

	if(href_list["scanning"])
		toggle_scan(text2num(href_list["scanning"]))

	if(href_list["time"])
		timing = text2num(href_list["time"])
		update_icon()

	if(href_list["tp"])
		var/tp = text2num(href_list["tp"])
		time += tp
		time = min(max(round(time), 0), 600)

	if(href_list["close"])
		usr << browse(null, "window=prox")
		return

	if(usr)
		attack_self(usr)


code:
/obj/item/assembly/shock_kit
	name = "electrohelmet assembly"
	desc = "This appears to be made from both an electropack and a helmet."
	icon = 'icons/obj/assemblies.dmi'
	icon_state = "shock_kit"
	var/obj/item/clothing/head/helmet/part1 = null
	var/obj/item/device/electropack/part2 = null
	w_class = 5
	flags = CONDUCT

/obj/item/assembly/shock_kit/Destroy()
	qdel(part1)
	qdel(part2)
	return ..()

/obj/item/assembly/shock_kit/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/weapon/wrench))
		var/turf/T = loc
		if(ismob(T))
			T = T.loc
		part1.loc = T
		part2.loc = T
		part1.master = null
		part2.master = null
		part1 = null
		part2 = null
		qdel(src)
		return
	add_fingerprint(user)
	return

/obj/item/assembly/shock_kit/attack_self(mob/user)
	part1.attack_self(user)
	part2.attack_self(user)
	add_fingerprint(user)
	return

/obj/item/assembly/shock_kit/receive_signal()
	if(istype(loc, /obj/structure/chair/e_chair))
		var/obj/structure/chair/e_chair/C = loc
		C.shock()
	return


code:
/obj/item/device/assembly/signaler
	name = "remote signaling device"
	desc = "Used to remotely activate devices."
	icon_state = "signaller"
	item_state = "signaler"
	materials = list(MAT_METAL=400, MAT_GLASS=120)
	origin_tech = "magnets=1"
	wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE
	attachable = 1

	var/code = 30
	var/frequency = 1457
	var/delay = 0
	var/datum/radio_frequency/radio_connection

/obj/item/device/assembly/signaler/New()
	..()
	spawn(40)
		set_frequency(frequency)

/obj/item/device/assembly/signaler/Destroy()
	if(SSradio)
		SSradio.remove_object(src,frequency)
	return ..()

/obj/item/device/assembly/signaler/activate()
	if(cooldown > 0)
		return 0
	cooldown = 2
	spawn(10)
		process_cooldown()

	signal()
	return 1

/obj/item/device/assembly/signaler/update_icon()
	if(holder)
		holder.update_icon()
	return

/obj/item/device/assembly/signaler/interact(mob/user, flag1)
	if(is_secured(user))
		var/t1 = "-------"
	//	if ((src.b_stat && !( flag1 )))
	//		t1 = text("-------
\nGreen Wire: []
\nRed Wire:   []
\nBlue Wire:  []
\n", (src.wires & 4 ? text("Cut Wire", src) : text("Mend Wire", src)), (src.wires & 2 ? text("Cut Wire", src) : text("Mend Wire", src)), (src.wires & 1 ? text("Cut Wire", src) : text("Mend Wire", src)))
	//	else
	//		t1 = "-------"	Speaker: [src.listening ? "Engaged" : "Disengaged"]

		var/dat = {"
<TT>

Send Signal

Frequency/Code for signaler:

Frequency:
-
-
[format_frequency(src.frequency)]
+
+

Code:
-
-
[src.code]
+
+

[t1]
</TT>"}
		user << browse(dat, "window=radio")
		onclose(user, "radio")
		return

/obj/item/device/assembly/signaler/Topic(href, href_list)
	..()

	if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr))
		usr << browse(null, "window=radio")
		onclose(usr, "radio")
		return

	if (href_list["freq"])
		var/new_frequency = (frequency + text2num(href_list["freq"]))
		if(new_frequency < 1200 || new_frequency > 1600)
			new_frequency = sanitize_frequency(new_frequency)
		set_frequency(new_frequency)

	if(href_list["code"])
		src.code += text2num(href_list["code"])
		src.code = round(src.code)
		src.code = min(100, src.code)
		src.code = max(1, src.code)

	if(href_list["send"])
		spawn( 0 )
			signal()

	if(usr)
		attack_self(usr)

	return

/obj/item/device/assembly/signaler/proc/signal()
	if(!radio_connection) return

	var/datum/signal/signal = new
	signal.source = src
	signal.encryption = code
	signal.data["message"] = "ACTIVATE"
	radio_connection.post_signal(src, signal)

	var/time = time2text(world.realtime,"hh:mm:ss")
	var/turf/T = get_turf(src)
	if(usr)
		lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]")

	return
/*
		for(var/obj/item/device/assembly/signaler/S in world)
			if(!S)
				continue
			if(S == src)
				continue
			if((S.frequency == src.frequency) && (S.code == src.code))
				spawn(0)
					if(S)
						S.pulse(0)
		return 0*/

/obj/item/device/assembly/signaler/receive_signal(datum/signal/signal)
	if(!signal)
		return 0
	if(signal.encryption != code)
		return 0
	if(!(src.wires & WIRE_RADIO_RECEIVE))
		return 0
	pulse(1)
	audible_message("\icon[src] *beep* *beep*", null, 1)
	return

/obj/item/device/assembly/signaler/proc/set_frequency(new_frequency)
	if(!SSradio)
		sleep(20)
	if(!SSradio)
		return
	SSradio.remove_object(src, frequency)
	frequency = new_frequency
	radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT)
	return

// Embedded signaller used in grenade construction.
// It's necessary because the signaler doens't have an off state.
// Generated during grenade construction.  -Sayu
/obj/item/device/assembly/signaler/reciever
	var/on = 0

/obj/item/device/assembly/signaler/reciever/proc/toggle_safety()
	on = !on

/obj/item/device/assembly/signaler/reciever/activate()
	toggle_safety()
	return 1

/obj/item/device/assembly/signaler/reciever/describe()
	return "The radio receiver is [on?"on":"off"]."

/obj/item/device/assembly/signaler/reciever/receive_signal(datum/signal/signal)
	if(!on) return
	return ..(signal)

// Embedded signaller used in anomalies.
/obj/item/device/assembly/signaler/anomaly
	name = "anomaly core"
	desc = "The neutralized core of an anomaly. It'd probably be valuable for research."
	icon_state = "anomaly core"
	item_state = "electronic"

/obj/item/device/assembly/signaler/anomaly/receive_signal(datum/signal/signal)
	if(!signal)
		return 0
	if(signal.encryption != code)
		return 0
	for(var/obj/effect/anomaly/A in get_turf(src))
		A.anomalyNeutralize()

/obj/item/device/assembly/signaler/anomaly/attack_self()
	return


code:
/obj/item/device/assembly/timer
	name = "timer"
	desc = "Used to time things. Works well with contraptions which has to count down. Tick tock."
	icon_state = "timer"
	materials = list(MAT_METAL=500, MAT_GLASS=50)
	origin_tech = "magnets=1"
	attachable = 1

	var/timing = 0
	var/time = 5
	var/saved_time = 5
	var/loop = 0

/obj/item/device/assembly/timer/New()
	..()
	SSobj.processing |= src

/obj/item/device/assembly/timer/describe()
	if(timing)
		return "The timer is counting down from [time]!"
	return "The timer is set for [time] seconds."

/obj/item/device/assembly/timer/activate()
	if(!..())
		return 0//Cooldown check
	timing = !timing
	update_icon()
	return 1

/obj/item/device/assembly/timer/toggle_secure()
	secured = !secured
	if(secured)
		SSobj.processing |= src
	else
		timing = 0
		SSobj.processing.Remove(src)
	update_icon()
	return secured

/obj/item/device/assembly/timer/proc/timer_end()
	if((!secured)||(cooldown > 0))
		return 0
	pulse(0)
	audible_message("\icon[src] *beep* *beep*", null, 3)
	cooldown = 2
	spawn(10)
		process_cooldown()
		if(loop)
			timing = 1
	update_icon()

/obj/item/device/assembly/timer/process()
	if(timing)
		time--
		if(time <= 0)
			timing = 0
			timer_end()
			time = saved_time

/obj/item/device/assembly/timer/update_icon()
	overlays.Cut()
	attached_overlays = list()
	if(timing)
		overlays += "timer_timing"
		attached_overlays += "timer_timing"
	if(holder)
		holder.update_icon()

/obj/item/device/assembly/timer/interact(mob/user)//TODO: Have this use the wires
	if(is_secured(user))
		var/second = time % 60
		var/minute = (time - second) / 60
		var/dat = "<TT>Timing Unit\n[(timing ? "Timing" : "Not Timing")] [minute]:[second]\n- - + +\n</TT>"
		dat += "

Stop repeating" : "1'>Set to repeat")]"
		dat += "

Refresh"
		dat += "

Close"
		var/datum/browser/popup = new(user, "timer", name)
		popup.set_content(dat)
		popup.open()

/obj/item/device/assembly/timer/Topic(href, href_list)
	..()
	if(usr.incapacitated() || !in_range(loc, usr))
		usr << browse(null, "window=timer")
		onclose(usr, "timer")
		return

	if(href_list["time"])
		timing = text2num(href_list["time"])
		if(timing && istype(holder, /obj/item/device/transfer_valve))
			var/timer_message = "[key_name_admin(usr)](?) (FLW) activated [src] attachment on [holder]."
			message_admins(timer_message)
			bombers += timer_message
			log_game("[key_name(usr)] activated [src] attachment for [loc]")
		update_icon()
	if(href_list["repeat"])
		loop = text2num(href_list["repeat"])

	if(href_list["tp"])
		var/tp = text2num(href_list["tp"])
		time += tp
		time = min(max(round(time), 1), 600)
		saved_time = time

	if(href_list["close"])
		usr << browse(null, "window=timer")
		return

	if(usr)
		attack_self(usr)


code:
/obj/item/device/assembly/voice
	name = "voice analyzer"
	desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated."
	icon_state = "voice"
	materials = list(MAT_METAL=500, MAT_GLASS=50)
	origin_tech = "magnets=1"
	flags = HEAR
	attachable = 1
	verb_say = "beeps"
	verb_ask = "beeps"
	verb_exclaim = "beeps"
	var/listening = 0
	var/recorded = "" //the activation message
	var/mode = 1
	var/global/list/modes = list("inclusive",
								 "exclusive",
								 "recognizer",
								 "voice sensor")

/obj/item/device/assembly/voice/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans)
	if(speaker == src)
		return

	if(listening && !radio_freq)
		record_speech(speaker, raw_message)
	else
		if(check_activation(speaker, raw_message))
			spawn(10)
				pulse(0)

/obj/item/device/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message)
	switch(mode)
		if(1)
			recorded = raw_message
			listening = 0
			say("Activation message is '[recorded]'.")
		if(2)
			recorded = raw_message
			listening = 0
			say("Activation message is '[recorded]'.")
		if(3)
			recorded = speaker.GetVoice()
			listening = 0
			say("Your voice pattern is saved.")
		if(4)
			if(length(raw_message))
				spawn(10)
					pulse(0)

/obj/item/device/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message)
	. = 0
	switch(mode)
		if(1)
			if(findtextEx(raw_message, recorded))
				. = 1
		if(2)
			if(raw_message == recorded)
				. = 1
		if(3)
			if(speaker.GetVoice() == recorded)
				. = 1
		if(4)
			if(length(raw_message))
				. = 1

/obj/item/device/assembly/voice/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/device/multitool))
		mode %= modes.len
		mode++
		user << "You set [src] into a [modes[mode]] mode."
		listening = 0
		recorded = ""
	else
		return ..()

/obj/item/device/assembly/voice/activate()
	if(secured)
		if(!holder)
			listening = !listening
			say("[listening ? "Now" : "No longer"] recording input.")

/obj/item/device/assembly/voice/attack_self(mob/user)
	if(!user)
		return 0
	activate()
	return 1

/obj/item/device/assembly/voice/toggle_secure()
	. = ..()
	listening = 0


code:
/atom/proc/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
	return null

/turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
	return

/turf/open/hotspot_expose(exposed_temperature, exposed_volume, soh)
	var/datum/gas_mixture/air_contents = return_air()
	if(!air_contents)
		return 0

	var/oxy = air_contents.gases["o2"] ? air_contents.gases["o2"][MOLES] : 0
	var/tox = air_contents.gases["plasma"] ? air_contents.gases["plasma"][MOLES] : 0

	if(active_hotspot)
		if(soh)
			if(tox > 0.5 && oxy > 0.5)
				if(active_hotspot.temperature < exposed_temperature)
					active_hotspot.temperature = exposed_temperature
				if(active_hotspot.volume < exposed_volume)
					active_hotspot.volume = exposed_volume
		return 1

	var/igniting = 0

	if((exposed_temperature > PLASMA_MINIMUM_BURN_TEMPERATURE) && tox > 0.5)
		igniting = 1

	if(igniting)
		if(oxy < 0.5 || tox < 0.5)
			return 0

		active_hotspot = PoolOrNew(/obj/effect/hotspot, src)
		active_hotspot.temperature = exposed_temperature
		active_hotspot.volume = exposed_volume

		active_hotspot.just_spawned = (current_cycle < SSair.times_fired)
			//remove just_spawned protection if no longer processing this cell
		SSair.add_to_active(src, 0)
	return igniting

//This is the icon for fire on turfs, also helps for nurturing small fires until they are full tile
/obj/effect/hotspot
	anchored = 1
	mouse_opacity = 0
	unacidable = 1//So you can't melt fire with acid.
	icon = 'icons/effects/fire.dmi'
	icon_state = "1"
	layer = TURF_LAYER
	luminosity = 3

	var/volume = 125
	var/temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST
	var/just_spawned = 1
	var/bypassing = 0

/obj/effect/hotspot/New()
	..()
	SSair.hotspots += src
	perform_exposure()
	dir = pick(cardinal)
	air_update_turf()

/obj/effect/hotspot/proc/perform_exposure()
	var/turf/open/location = loc
	if(!istype(location) || !(location.air))
		return 0

	if(volume > CELL_VOLUME*0.95)
		bypassing = 1
	else
		bypassing = 0

	if(bypassing)
		if(!just_spawned)
			volume = location.air.fuel_burnt*FIRE_GROWTH_RATE
			temperature = location.air.temperature
	else
		var/datum/gas_mixture/affected = location.air.remove_ratio(volume/location.air.volume)
		affected.temperature = temperature
		affected.react()
		temperature = affected.temperature
		volume = affected.fuel_burnt*FIRE_GROWTH_RATE
		location.assume_air(affected)

	for(var/A in loc)
		var/atom/item = A
		if(item && item != src) // It's possible that the item is deleted in temperature_expose
			item.fire_act(null, temperature, volume)
	return 0

/obj/effect/hotspot/process()
	if(just_spawned)
		just_spawned = 0
		return 0

	var/turf/open/location = loc
	if(!istype(location))
		qdel(src)
		return

	if(location.excited_group)
		location.excited_group.reset_cooldowns()

	if((temperature < FIRE_MINIMUM_TEMPERATURE_TO_EXIST) || (volume <= 1))
		qdel(src)
		return

	if(!(location.air) || !location.air.gases["plasma"] || !location.air.gases["o2"] || location.air.gases["plasma"][MOLES] < 0.5 || location.air.gases["o2"][MOLES] < 0.5)
		qdel(src)
		return

	perform_exposure()

	if(location.wet) location.wet = 0

	if(bypassing)
		icon_state = "3"
		location.burn_tile()

		//Possible spread due to radiated heat
		if(location.air.temperature > FIRE_MINIMUM_TEMPERATURE_TO_SPREAD)
			var/radiated_temperature = location.air.temperature*FIRE_SPREAD_RADIOSITY_SCALE
			for(var/t in location.atmos_adjacent_turfs)
				var/turf/open/T = t
				if(T.active_hotspot)
					T.hotspot_expose(radiated_temperature, CELL_VOLUME/4)

	else
		if(volume > CELL_VOLUME*0.4)
			icon_state = "2"
		else
			icon_state = "1"

	if(temperature > location.max_fire_temperature_sustained)
		location.max_fire_temperature_sustained = temperature

	if(location.heat_capacity && temperature > location.heat_capacity)
		location.to_be_destroyed = 1
		/*if(prob(25))
			location.ReplaceWithSpace()
			return 0*/
	return 1

/obj/effect/hotspot/Destroy()
	SetLuminosity(0)
	SSair.hotspots -= src
	DestroyTurf()
	if(istype(loc, /turf))
		var/turf/open/T = loc
		if(T.active_hotspot == src)
			T.active_hotspot = null
	loc = null
	..()
	return QDEL_HINT_PUTINPOOL

/obj/effect/hotspot/proc/DestroyTurf()

	if(istype(loc, /turf))
		var/turf/T = loc
		if(T.to_be_destroyed)
			var/chance_of_deletion
			if (T.heat_capacity) //beware of division by zero
				chance_of_deletion = T.max_fire_temperature_sustained / T.heat_capacity * 8 //there is no problem with prob(23456), min() was redundant --rastaf0
			else
				chance_of_deletion = 100
			if(prob(chance_of_deletion))
				T.ChangeTurf(T.baseturf)
			else
				T.to_be_destroyed = 0
				T.max_fire_temperature_sustained = 0

/obj/effect/hotspot/Crossed(mob/living/L)
	..()
	if(isliving(L))
		L.fire_act()


code:
/turf/proc/CanAtmosPass(turf/T)

/turf/closed/CanAtmosPass(turf/T)
	return 0

/turf/open/CanAtmosPass(turf/T)
	var/R
	if(blocks_air || T.blocks_air)
		R = 1

	for(var/obj/O in contents+T.contents)
		var/turf/other = (O.loc == src ? T : src)
		if(!O.CanAtmosPass(other))
			R = 1
			if(O.BlockSuperconductivity()) 	//the direction and open/closed are already checked on CanAtmosPass() so there are no arguments
				var/D = get_dir(src, T)
				atmos_supeconductivity |= D
				D = get_dir(T, src)
				T.atmos_supeconductivity |= D
				return 0						//no need to keep going, we got all we asked

	atmos_supeconductivity &= ~get_dir(src, T)
	T.atmos_supeconductivity &= ~get_dir(T, src)

	return !R

/atom/movable/proc/CanAtmosPass()
	return 1

/atom/proc/CanPass(atom/movable/mover, turf/target, height=1.5)
	return (!density || !height)

/turf/CanPass(atom/movable/mover, turf/target, height=1.5)
	if(!target) return 0

	if(istype(mover)) // turf/Enter(...) will perform more advanced checks
		return !density

	else // Now, doing more detailed checks for air movement and air group formation
		if(target.blocks_air||blocks_air)
			return 0

		for(var/obj/obstacle in src)
			if(!obstacle.CanPass(mover, target, height))
				return 0
		for(var/obj/obstacle in target)
			if(!obstacle.CanPass(mover, src, height))
				return 0

		return 1

/atom/movable/proc/BlockSuperconductivity() // objects that block air and don't let superconductivity act. Only firelocks atm.
	return 0

/turf/proc/CalculateAdjacentTurfs()
	for(var/direction in cardinal)
		var/turf/open/T = get_step(src, direction)
		if(!istype(T))
			continue
		if(CanAtmosPass(T))
			atmos_adjacent_turfs |= T
			T.atmos_adjacent_turfs |= src
		else
			atmos_adjacent_turfs -= T
			T.atmos_adjacent_turfs -= src

//returns a list of adjacent turfs that can share air with this one.
//alldir includes adjacent diagonal tiles that can share
//	air with both of the related adjacent cardinal tiles
/turf/proc/GetAtmosAdjacentTurfs(alldir = 0)
	var/adjacent_turfs = atmos_adjacent_turfs.Copy()
	if (!alldir)
		return adjacent_turfs
	var/turf/curloc = src

	for (var/direction in diagonals)
		var/matchingDirections = 0
		var/turf/S = get_step(curloc, direction)

		for (var/checkDirection in cardinal)
			var/turf/checkTurf = get_step(S, checkDirection)
			if(!(checkTurf in S.atmos_adjacent_turfs))
				continue

			if (checkTurf in adjacent_turfs)
				matchingDirections++

			if (matchingDirections >= 2)
				adjacent_turfs += S
				break

	return adjacent_turfs

/atom/movable/proc/air_update_turf(command = 0)
	if(!istype(loc,/turf) && command)
		return
	var/turf/T = get_turf(loc)
	T.air_update_turf(command)

/turf/proc/air_update_turf(command = 0)
	if(command)
		CalculateAdjacentTurfs()
	SSair.add_to_active(src,command)

/atom/movable/proc/move_update_air(turf/T)
    if(istype(T,/turf))
        T.air_update_turf(1)
    air_update_turf(1)

/atom/movable/proc/atmos_spawn_air(text) //because a lot of people loves to copy paste awful code lets just make a easy proc to spawn your plasma fires
	var/turf/open/T = get_turf(src)
	if(!istype(T))
		return
	T.atmos_spawn_air(text)

/turf/open/proc/atmos_spawn_air(text)
	if(!text || !air)
		return

	var/datum/gas_mixture/G = new
	G.parse_gas_string(text)

	air.merge(G)
	SSair.add_to_active(src, 0)


code:
/turf
	//used for temperature calculations
	var/thermal_conductivity = 0.05
	var/heat_capacity = 1
	var/temperature_archived

	//list of open turfs adjacent to us
	var/list/atmos_adjacent_turfs = list()
	//bitfield of dirs in which we are superconducitng
	var/atmos_supeconductivity = 0

	//used to determine whether we should archive
	var/archived_cycle = 0
	var/current_cycle = 0

	//used for mapping and for breathing while in walls (because that's a thing that needs to be accounted for...)
	//string parsed by /datum/gas/proc/copy_from_turf
	var/initial_gas_mix = "o2=22;n2=82;TEMP=293.15" //approximation of MOLES_O2STANDARD and MOLES_N2STANDARD pending byond allowing constant expressions to be embedded in constant strings

/turf/open
	//used for spacewind
	var/pressure_difference = 0
	var/pressure_direction = 0

	var/datum/excited_group/excited_group
	var/excited = 0
	var/recently_active = 0
	var/datum/gas_mixture/air

	var/obj/effect/hotspot/active_hotspot

	var/list/atmos_overlay_types = list() //gas IDs of current active gas overlays

/turf/open/New()
	..()
	if(!blocks_air)
		air = new
		air.copy_from_turf(src)

/turf/open/Destroy()
	if(active_hotspot)
		qdel(active_hotspot)
		active_hotspot = null
	// Adds the adjacent turfs to the current atmos processing
	for(var/T in atmos_adjacent_turfs)
		SSair.add_to_active(T)
	return ..()

/////////////////GAS MIXTURE PROCS///////////////////

/turf/open/assume_air(datum/gas_mixture/giver) //use this for machines to adjust air
	if(!giver)
		return 0
	air.merge(giver)
	update_visuals()
	return 1

/turf/open/remove_air(amount)
	var/datum/gas_mixture/ours = return_air()
	var/datum/gas_mixture/removed = ours.remove(amount)
	update_visuals()
	return removed

/turf/open/proc/copy_air_with_tile(turf/open/T)
	if(istype(T))
		air.copy_from(T.air)

/turf/open/proc/copy_air(datum/gas_mixture/copy)
	if(copy)
		air.copy_from(copy)

/turf/return_air()
	var/datum/gas_mixture/GM = new
	GM.copy_from_turf(src)
	return GM

/turf/open/return_air()
	return air

/turf/temperature_expose()
	if(temperature > heat_capacity)
		to_be_destroyed = 1

/turf/proc/archive()
	temperature_archived = temperature

/turf/open/archive()
	air.archive()
	archived_cycle = SSair.times_fired
	..()

/////////////////////////GAS OVERLAYS//////////////////////////////

/turf/open/proc/update_visuals()
	var/list/new_overlay_types = tile_graphic()

	for(var/overlay in atmos_overlay_types-new_overlay_types) //doesn't remove overlays that would only be added
		overlays -= overlay
		atmos_overlay_types -= overlay

	for(var/overlay in new_overlay_types-atmos_overlay_types) //doesn't add overlays that already exist
		overlays += overlay

	atmos_overlay_types = new_overlay_types

/turf/open/proc/tile_graphic()
	. = new /list
	var/list/gases = air.gases
	for(var/id in gases)
		var/gas = gases[id]
		if(gas[GAS_META][META_GAS_OVERLAY] && gas[MOLES] > gas[GAS_META][META_GAS_MOLES_VISIBLE])
			. += gas[GAS_META][META_GAS_OVERLAY]

/////////////////////////////SIMULATION///////////////////////////////////

/turf/proc/process_cell(fire_count)
	SSair.remove_from_active(src)

/turf/open/process_cell(fire_count)
	if(archived_cycle < fire_count) //archive self if not already done
		archive()

	current_cycle = fire_count
	var/remove = 1 //set by non simulated turfs who are sharing with this turf

	//cache for sanic speed
	var/list/adjacent_turfs = atmos_adjacent_turfs
	var/datum/excited_group/our_excited_group = excited_group
	var/adjacent_turfs_length = adjacent_turfs.len

	for(var/t in adjacent_turfs)
		var/turf/open/enemy_tile = t

		if(fire_count > enemy_tile.current_cycle)
			enemy_tile.archive()

		/******************* GROUP HANDLING START *****************************************************************/

			if(enemy_tile.excited)
				//cache for sanic speed
				var/datum/excited_group/enemy_excited_group = enemy_tile.excited_group
				if(our_excited_group)
					if(enemy_excited_group)
						if(our_excited_group != enemy_excited_group)
							//combine groups (this also handles updating the excited_group var of all involved turfs)
							our_excited_group.merge_groups(enemy_excited_group)
							our_excited_group = excited_group //update our cache
						share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
					else
						if((recently_active == 1 && enemy_tile.recently_active == 1) || air.compare(enemy_tile.air))
							our_excited_group.add_turf(enemy_tile) //add enemy to our group
							share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
				else
					if(enemy_excited_group)
						if((recently_active == 1 && enemy_tile.recently_active == 1) || air.compare(enemy_tile.air))
							enemy_excited_group.add_turf(src) //join self to enemy group
							our_excited_group = excited_group //update our cache
							share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
					else
						if((recently_active == 1 && enemy_tile.recently_active == 1) || air.compare(enemy_tile.air))
							var/datum/excited_group/EG = new //generate new group
							EG.add_turf(src)
							EG.add_turf(enemy_tile)
							our_excited_group = excited_group //update our cache
							share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
			else
				if(air.compare(enemy_tile.air)) //compare if
					SSair.add_to_active(enemy_tile) //excite enemy
					if(our_excited_group)
						excited_group.add_turf(enemy_tile) //add enemy to group
					else
						var/datum/excited_group/EG = new //generate new group
						EG.add_turf(src)
						EG.add_turf(enemy_tile)
						our_excited_group = excited_group //update our cache
					share_air(enemy_tile, fire_count, adjacent_turfs_length) //share

		/******************* GROUP HANDLING FINISH *********************************************************************/

	air.react()

	update_visuals()

	if(air.temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
		hotspot_expose(air.temperature, CELL_VOLUME)
		for(var/atom/movable/item in src)
			item.temperature_expose(air, air.temperature, CELL_VOLUME)
		temperature_expose(air, air.temperature, CELL_VOLUME)

		if(air.temperature > MINIMUM_TEMPERATURE_START_SUPERCONDUCTION)
			if(consider_superconductivity(starting = 1))
				remove = 0

	if(!our_excited_group && remove == 1)
		SSair.remove_from_active(src)

/turf/open/proc/share_air(turf/open/T, fire_count, adjacent_turfs_length)
	if(T.current_cycle < fire_count)
		var/difference = air.share(T.air, adjacent_turfs_length)
		if(difference)
			if(difference > 0)
				consider_pressure_difference(T, difference)
			else
				T.consider_pressure_difference(src, -difference)
		last_share_check()

//////////////////////////SPACEWIND/////////////////////////////

/turf/open/proc/consider_pressure_difference(turf/T, difference)
	SSair.high_pressure_delta |= src
	if(difference > pressure_difference)
		pressure_direction = get_dir(src, T)
		pressure_difference = difference

/turf/open/proc/last_share_check()
	if(air.last_share > MINIMUM_AIR_TO_SUSPEND)
		excited_group.reset_cooldowns()
	else if(air.last_share > MINIMUM_MOLES_DELTA_TO_MOVE)
		excited_group.dismantle_cooldown = 0

/turf/open/proc/high_pressure_movements()
	for(var/atom/movable/M in src)
		M.experience_pressure_difference(pressure_difference, pressure_direction)

/atom/movable/var/pressure_resistance = 5
/atom/movable/var/last_high_pressure_movement_air_cycle = 0
/atom/movable/proc/experience_pressure_difference(pressure_difference, direction)
	set waitfor = 0
	. = 0
	if(!anchored && !pulledby)
		. = 1
		if(pressure_difference > pressure_resistance && last_high_pressure_movement_air_cycle < SSair.times_fired)
			last_high_pressure_movement_air_cycle = SSair.times_fired
			step(src, direction)

///////////////////////////EXCITED GROUPS/////////////////////////////

/datum/excited_group
	var/list/turf_list = list()
	var/breakdown_cooldown = 0
	var/dismantle_cooldown = 0

/datum/excited_group/New()
	SSair.excited_groups += src

/datum/excited_group/proc/add_turf(turf/open/T)
	turf_list += T
	T.excited_group = src
	T.recently_active = 1
	reset_cooldowns()

/datum/excited_group/proc/merge_groups(datum/excited_group/E)
	if(turf_list.len > E.turf_list.len)
		SSair.excited_groups -= E
		for(var/t in E.turf_list)
			var/turf/open/T = t
			T.excited_group = src
			turf_list += T
			reset_cooldowns()
	else
		SSair.excited_groups -= src
		for(var/t in turf_list)
			var/turf/open/T = t
			T.excited_group = E
			E.turf_list += T
			E.reset_cooldowns()

/datum/excited_group/proc/reset_cooldowns()
	breakdown_cooldown = 0
	dismantle_cooldown = 0

/datum/excited_group/proc/self_breakdown()
	var/datum/gas_mixture/A = new

	//make local for sanic speed
	var/list/A_gases = A.gases
	var/list/turf_list = src.turf_list
	var/turflen = turf_list.len

	for(var/t in turf_list)
		var/turf/open/T = t
		A.merge(T.air)

	for(var/id in A_gases)
		A_gases[id][MOLES] = A_gases[id][MOLES]/turflen

	for(var/t in turf_list)
		var/turf/open/T = t
		T.air.copy_from(A)
		T.update_visuals()

	breakdown_cooldown = 0

/datum/excited_group/proc/dismantle()
	for(var/t in turf_list)
		var/turf/open/T = t
		T.excited = 0
		T.recently_active = 0
		T.excited_group = null
		SSair.active_turfs -= T
	garbage_collect()

/datum/excited_group/proc/garbage_collect()
	for(var/t in turf_list)
		var/turf/open/T = t
		T.excited_group = null
	turf_list.Cut()
	SSair.excited_groups -= src

////////////////////////SUPERCONDUCTIVITY/////////////////////////////
/turf/proc/conductivity_directions()
	if(archived_cycle < SSair.times_fired)
		archive()
	return NORTH|SOUTH|EAST|WEST

/turf/open/conductivity_directions()
	if(blocks_air)
		return ..()
	for(var/direction in cardinal)
		var/turf/T = get_step(src, direction)
		if(!(T in atmos_adjacent_turfs) && !(atmos_supeconductivity & direction))
			. |= direction

/turf/proc/neighbor_conduct_with_src(turf/open/other)
	if(!other.blocks_air) //Open but neighbor is solid
		other.temperature_share_open_to_solid(src)
	else //Both tiles are solid
		other.share_temperature_mutual_solid(src, thermal_conductivity)
	temperature_expose(null, temperature, null)

/turf/open/neighbor_conduct_with_src(turf/other)
	if(blocks_air)
		..()
		return

	if(!other.blocks_air) //Both tiles are open
		var/turf/open/T = other
		T.air.temperature_share(air, WINDOW_HEAT_TRANSFER_COEFFICIENT)
	else //Solid but neighbor is open
		temperature_share_open_to_solid(other)
	SSair.add_to_active(src, 0)

/turf/proc/super_conduct()
	var/conductivity_directions = conductivity_directions()

	if(conductivity_directions)
		//Conduct with tiles around me
		for(var/direction in cardinal)
			if(conductivity_directions & direction)
				var/turf/neighbor = get_step(src,direction)

				if(!neighbor.thermal_conductivity)
					continue

				if(neighbor.archived_cycle < SSair.times_fired)
					neighbor.archive()

				neighbor.neighbor_conduct_with_src()

				neighbor.consider_superconductivity()

	radiate_to_spess()

	finish_superconduction()

/turf/proc/finish_superconduction(temp = temperature)
	//Make sure still hot enough to continue conducting heat
	if(temp < MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION)
		SSair.active_super_conductivity -= src
		return 0

/turf/open/finish_superconduction()
	//Conduct with air on my tile if I have it
	if(!blocks_air)
		temperature = air.temperature_share(null, thermal_conductivity, temperature, heat_capacity)
	..((blocks_air ? temperature : air.temperature))

/turf/proc/consider_superconductivity()
	if(!thermal_conductivity)
		return 0

	SSair.active_super_conductivity |= src
	return 1

/turf/open/consider_superconductivity(starting)
	if(air.temperature < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION))
		return 0
	if(air.heat_capacity() < M_CELL_WITH_RATIO) // Was: MOLES_CELLSTANDARD*0.1*0.05 Since there are no variables here we can make this a constant.
		return 0
	return ..()

/turf/closed/consider_superconductivity(starting)
	if(temperature < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION))
		return 0
	return ..()

/turf/proc/radiate_to_spess() //Radiate excess tile heat to space
	if(temperature > T0C) //Considering 0 degC as te break even point for radiation in and out
		var/delta_temperature = (temperature_archived - TCMB) //hardcoded space temperature
		if((heat_capacity > 0) && (abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER))

			var/heat = thermal_conductivity*delta_temperature* \
				(heat_capacity*700000/(heat_capacity+700000)) //700000 is the heat_capacity from a space turf, hardcoded here
			temperature -= heat/heat_capacity

/turf/open/proc/temperature_share_open_to_solid(turf/sharer)
	sharer.temperature = air.temperature_share(null, sharer.thermal_conductivity, sharer.temperature, sharer.heat_capacity)

/turf/proc/share_temperature_mutual_solid(turf/sharer, conduction_coefficient) //to be understood
	var/delta_temperature = (temperature_archived - sharer.temperature_archived)
	if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER && heat_capacity && sharer.heat_capacity)

		var/heat = conduction_coefficient*delta_temperature* \
			(heat_capacity*sharer.heat_capacity/(heat_capacity+sharer.heat_capacity))

		temperature -= heat/heat_capacity
		sharer.temperature += heat/sharer.heat_capacity


code:
 /*
What are the archived variables for?
	Calculations are done using the archived variables with the results merged into the regular variables.
	This prevents race conditions that arise based on the order of tile processing.
*/
#define MINIMUM_HEAT_CAPACITY	0.0003
#define QUANTIZE(variable)		(round(variable,0.0000001))/*I feel the need to document what happens here. Basically this is used to catch most rounding errors, however it's previous value made it so that
															once gases got hot enough, most procedures wouldnt occur due to the fact that the mole counts would get rounded away. Thus, we lowered it a few orders of magnititude */

var/list/meta_gas_info = meta_gas_list() //see ATMOSPHERICS/gas_types.dm

var/list/gaslist_cache = null
/proc/gaslist(id)
	var/list/cached_gas

	//only instantiate the first time it's needed
	if(!gaslist_cache)
		gaslist_cache = new(meta_gas_info.len)

	//only setup the individual lists the first time they're needed
	if(!gaslist_cache[id])
		if(!meta_gas_info[id])
			CRASH("Gas [id] does not exist!")
		cached_gas = new(3)
		gaslist_cache[id] = cached_gas

		cached_gas[MOLES] = 0
		cached_gas[ARCHIVE] = 0
		cached_gas[GAS_META] = meta_gas_info[id]
	else
		cached_gas = gaslist_cache[id]
	//Copy() it because only GAS_META is static
	return cached_gas.Copy()

/datum/gas_mixture
	var/list/gases
	var/temperature //kelvins
	var/tmp/temperature_archived
	var/volume //liters
	var/last_share
	var/tmp/fuel_burnt

/datum/gas_mixture/New(volume = CELL_VOLUME)
	..()
	gases = new
	temperature = 0
	temperature_archived = 0
	src.volume = volume
	last_share = 0
	fuel_burnt = 0

//listmos procs

	//assert_gas(gas_id) - used to guarantee that the gas list for this id exists.
	//Must be used before adding to a gas. May be used before reading from a gas.
/datum/gas_mixture/proc/assert_gas(gas_id)
	var/cached_gases = gases
	if(cached_gases[gas_id])
		return
	cached_gases[gas_id] = gaslist(gas_id)

	//assert_gases(args) - shorthand for calling assert_gas() once for each gas type.
/datum/gas_mixture/proc/assert_gases()
	for(var/id in args)
		assert_gas(id)

	//add_gas(gas_id) - similar to assert_gas(), but does not check for an existing
		//gas list for this id. This can clobber existing gases.
	//Used instead of assert_gas() when you know the gas does not exist. Faster than assert_gas().
/datum/gas_mixture/proc/add_gas(gas_id)
	gases[gas_id] = gaslist(gas_id)

	//add_gases(args) - shorthand for calling add_gas() once for each gas_type.
/datum/gas_mixture/proc/add_gases()
	for(var/id in args)
		add_gas(id)

	//garbage_collect() - removes any gas list which is empty.
	//If called with a list as an argument, only removes gas lists with IDs from that list.
	//Must be used after subtracting from a gas. Must be used after assert_gas()
		//if assert_gas() was called only to read from the gas.
	//By removing empty gases, processing speed is increased.
/datum/gas_mixture/proc/garbage_collect(list/tocheck)
	var/list/cached_gases = gases
	for(var/id in (tocheck || cached_gases))
		if(cached_gases[id][MOLES] <= 0 && cached_gases[id][ARCHIVE] <= 0)
			cached_gases -= id

	//PV = nRT
/datum/gas_mixture/proc/heat_capacity() //joules per kelvin
	var/list/cached_gases = gases
	. = 0
	for(var/id in cached_gases)
		. += cached_gases[id][MOLES] * cached_gases[id][GAS_META][META_GAS_SPECIFIC_HEAT]

/datum/gas_mixture/proc/heat_capacity_archived() //joules per kelvin
	var/list/cached_gases = gases
	. = 0
	for(var/id in cached_gases)
		. += cached_gases[id][ARCHIVE] * cached_gases[id][GAS_META][META_GAS_SPECIFIC_HEAT]

/datum/gas_mixture/proc/total_moles() //moles
	var/list/cached_gases = gases
	. = 0
	for(var/id in cached_gases)
		. += cached_gases[id][MOLES]

/datum/gas_mixture/proc/return_pressure() //kilopascals
	if(volume > 0) // to prevent division by zero
		return total_moles() * R_IDEAL_GAS_EQUATION * temperature / volume
	return 0

/datum/gas_mixture/proc/return_temperature() //kelvins
	return temperature

/datum/gas_mixture/proc/return_volume() //liters
	return max(0, volume)

/datum/gas_mixture/proc/thermal_energy() //joules
	return temperature * heat_capacity()

//Procedures used for very specific events

/datum/gas_mixture/proc/react(atom/dump_location)
	var/list/cached_gases = gases //this speeds things up because >byond
	var/reacting = 0 //set to 1 if a notable reaction occured (used by pipe_network)

	if(temperature < TCMB)
		temperature = TCMB

	if(cached_gases["agent_b"] && temperature > 900 && cached_gases["plasma"] && cached_gases["co2"])
		//agent b converts hot co2 to o2 (endothermic)
		if(cached_gases["plasma"][MOLES] > MINIMUM_HEAT_CAPACITY && cached_gases["co2"][MOLES] > MINIMUM_HEAT_CAPACITY)
			var/reaction_rate = min(cached_gases["co2"][MOLES]*0.75, cached_gases["plasma"][MOLES]*0.25, cached_gases["agent_b"][MOLES]*0.05)

			cached_gases["co2"][MOLES] -= reaction_rate

			assert_gas("o2") //only need to assert oxygen, as this reaction doesn't occur without the other gases existing
			cached_gases["o2"][MOLES] += reaction_rate

			cached_gases["agent_b"][MOLES] -= reaction_rate*0.05

			temperature -= (reaction_rate*20000)/heat_capacity()

			garbage_collect()

			reacting = 1
	/*
	if(thermal_energy() > (PLASMA_BINDING_ENERGY*10))
		if(cached_gases["plasma"] && cached_gases["co2"] && cached_gases["plasma"][MOLES] > MINIMUM_HEAT_CAPACITY && cached_gases["co2"][MOLES] > MINIMUM_HEAT_CAPACITY && (cached_gases["plasma"][MOLES]+cached_gases["co2"][MOLES])/total_moles() >= FUSION_PURITY_THRESHOLD)//Fusion wont occur if the level of impurities is too high.
			//fusion converts plasma and co2 to o2 and n2 (exothermic)
			//world << "pre [temperature, [cached_gases["plasma"][MOLES]], [cached_gases["co2"][MOLES]]
			var/old_heat_capacity = heat_capacity()
			var/carbon_efficency = min(cached_gases["plasma"][MOLES]/cached_gases["co2"][MOLES],MAX_CARBON_EFFICENCY)
			var/reaction_energy = thermal_energy()
			var/moles_impurities = total_moles()-(cached_gases["plasma"][MOLES]+cached_gases["co2"][MOLES])

			var/plasma_fused = (PLASMA_FUSED_COEFFICENT*carbon_efficency)*(temperature/PLASMA_BINDING_ENERGY)
			var/carbon_catalyzed = (CARBON_CATALYST_COEFFICENT*carbon_efficency)*(temperature/PLASMA_BINDING_ENERGY)
			var/oxygen_added = carbon_catalyzed
			var/nitrogen_added = (plasma_fused-oxygen_added)-(thermal_energy()/PLASMA_BINDING_ENERGY)

			reaction_energy = max(reaction_energy+((carbon_efficency*cached_gases["plasma"][MOLES])/((moles_impurities/carbon_efficency)+2)*10)+((plasma_fused/(moles_impurities/carbon_efficency))*PLASMA_BINDING_ENERGY),0)

			assert_gases("o2", "n2")

			cached_gases["plasma"][MOLES] -= plasma_fused
			cached_gases["co2"][MOLES] -= carbon_catalyzed
			cached_gases["o2"][MOLES] += oxygen_added
			cached_gases["n2"][MOLES] += nitrogen_added

			garbage_collect()

			if(reaction_energy > 0)
				reacting = 1
				var/new_heat_capacity = heat_capacity()
				if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
					temperature = max(((temperature*old_heat_capacity + reaction_energy)/new_heat_capacity),TCMB)
					//Prevents whatever mechanism is causing it to hit negative temperatures.
				//world << "post [temperature], [cached_gases["plasma"][MOLES]], [cached_gases["co2"][MOLES]]
			*/
	fuel_burnt = 0
	if(temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
		//world << "pre [temperature], [cached_gases["o2"][MOLES]], [cached_gases["plasma"][MOLES]]"
		if(fire())
			reacting = 1
		//world << "post [temperature], [cached_gases["o2"][MOLES]], [cached_gases["plasma"][MOLES]]"

	return reacting

/datum/gas_mixture/proc/fire()
	//combustion of plasma and volatile fuel, which both act as hydrocarbons (exothermic)
	var/energy_released = 0
	var/old_heat_capacity = heat_capacity()
	var/list/cached_gases = gases //this speeds things up because accessing datum vars is slow

 	//General volatile gas burn
	if(cached_gases["v_fuel"] && cached_gases["v_fuel"][MOLES])
		var/burned_fuel

		if(!cached_gases["o2"])
			burned_fuel = 0
		else if(cached_gases["o2"][MOLES] < cached_gases["v_fuel"][MOLES])
			burned_fuel = cached_gases["o2"][MOLES]
			cached_gases["v_fuel"][MOLES] -= burned_fuel
			cached_gases["o2"][MOLES] = 0
		else
			burned_fuel = cached_gases["v_fuel"][MOLES]
			cached_gases["o2"][MOLES] -= cached_gases["v_fuel"][MOLES]

		if(burned_fuel)
			energy_released += FIRE_CARBON_ENERGY_RELEASED * burned_fuel

			assert_gas("co2")
			cached_gases["co2"][MOLES] += burned_fuel

			fuel_burnt += burned_fuel

	//Handle plasma burning
	if(cached_gases["plasma"] && cached_gases["plasma"][MOLES] > MINIMUM_HEAT_CAPACITY)
		var/plasma_burn_rate = 0
		var/oxygen_burn_rate = 0
		//more plasma released at higher temperatures
		var/temperature_scale
		if(temperature > PLASMA_UPPER_TEMPERATURE)
			temperature_scale = 1
		else
			temperature_scale = (temperature-PLASMA_MINIMUM_BURN_TEMPERATURE)/(PLASMA_UPPER_TEMPERATURE-PLASMA_MINIMUM_BURN_TEMPERATURE)
		if(temperature_scale > 0)
			assert_gas("o2")
			oxygen_burn_rate = OXYGEN_BURN_RATE_BASE - temperature_scale
			if(cached_gases["o2"][MOLES] > cached_gases["plasma"][MOLES]*PLASMA_OXYGEN_FULLBURN)
				plasma_burn_rate = (cached_gases["plasma"][MOLES]*temperature_scale)/PLASMA_BURN_RATE_DELTA
			else
				plasma_burn_rate = (temperature_scale*(cached_gases["o2"][MOLES]/PLASMA_OXYGEN_FULLBURN))/PLASMA_BURN_RATE_DELTA
			if(plasma_burn_rate > MINIMUM_HEAT_CAPACITY)
				assert_gas("co2")
				cached_gases["plasma"][MOLES] = QUANTIZE(cached_gases["plasma"][MOLES] - plasma_burn_rate)
				cached_gases["o2"][MOLES] = QUANTIZE(cached_gases["o2"][MOLES] - (plasma_burn_rate * oxygen_burn_rate))
				cached_gases["co2"][MOLES] += plasma_burn_rate

				energy_released += FIRE_PLASMA_ENERGY_RELEASED * (plasma_burn_rate)

				fuel_burnt += (plasma_burn_rate)*(1+oxygen_burn_rate)
				garbage_collect()

	if(energy_released > 0)
		var/new_heat_capacity = heat_capacity()
		if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
			temperature = (temperature*old_heat_capacity + energy_released)/new_heat_capacity

	return fuel_burnt

/datum/gas_mixture/proc/archive()
	//Update archived versions of variables
	//Returns: 1 in all cases

/datum/gas_mixture/proc/merge(datum/gas_mixture/giver)
	//Merges all air from giver into self. Deletes giver.
	//Returns: 1 if we are mutable, 0 otherwise

/datum/gas_mixture/proc/remove(amount)
	//Proportionally removes amount of gas from the gas_mixture
	//Returns: gas_mixture with the gases removed

/datum/gas_mixture/proc/remove_ratio(ratio)
	//Proportionally removes amount of gas from the gas_mixture
	//Returns: gas_mixture with the gases removed

/datum/gas_mixture/proc/copy()
	//Creates new, identical gas mixture
	//Returns: duplicate gas mixture

/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample)
	//Copies variables from sample
	//Returns: 1 if we are mutable, 0 otherwise

/datum/gas_mixture/proc/copy_from_turf(turf/model)
	//Copies all gas info from the turf into the gas list along with temperature
	//Returns: 1 if we are mutable, 0 otherwise

/datum/gas_mixture/proc/parse_gas_string(gas_string)
	//Copies variables from a particularly formatted string.
	//Returns: 1 if we are mutable, 0 otherwise

/datum/gas_mixture/proc/share(datum/gas_mixture/sharer)
	//Performs air sharing calculations between two gas_mixtures assuming only 1 boundary length
	//Returns: amount of gas exchanged (+ if sharer received)

/datum/gas_mixture/proc/after_share(datum/gas_mixture/sharer)
	//called on share's sharer to let it know it just got some gases

/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient)
	//Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length
	//Returns: new temperature of the sharer

/datum/gas_mixture/proc/compare(datum/gas_mixture/sample)
	//Compares sample to self to see if within acceptable ranges that group processing may be enabled
	//Returns: a string indicating what check failed, or "" if check passes

/datum/gas_mixture/archive()
	var/list/cached_gases = gases

	temperature_archived = temperature
	for(var/id in cached_gases)
		cached_gases[id][ARCHIVE] = cached_gases[id][MOLES]

	return 1

/datum/gas_mixture/merge(datum/gas_mixture/giver)
	if(!giver)
		return 0

	//heat transfer
	if(abs(temperature - giver.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
		var/self_heat_capacity = heat_capacity()
		var/giver_heat_capacity = giver.heat_capacity()
		var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
		if(combined_heat_capacity)
			temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity

	var/list/cached_gases = gases //accessing datum vars is slower than proc vars
	var/list/giver_gases = giver.gases
	//gas transfer
	for(var/giver_id in giver_gases)
		assert_gas(giver_id)
		cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES]

	return 1

/datum/gas_mixture/remove(amount)
	var/sum = total_moles()
	amount = min(amount, sum) //Can not take more air than tile has!
	if(amount <= 0)
		return null

	var/list/cached_gases = gases
	var/datum/gas_mixture/removed = new
	var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars

	removed.temperature = temperature
	for(var/id in cached_gases)
		removed.add_gas(id)
		removed_gases[id][MOLES] = QUANTIZE((cached_gases[id][MOLES] / sum) * amount)
		cached_gases[id][MOLES] -= removed_gases[id][MOLES]
	garbage_collect()

	return removed

/datum/gas_mixture/remove_ratio(ratio)
	if(ratio <= 0)
		return null
	ratio = min(ratio, 1)

	var/list/cached_gases = gases
	var/datum/gas_mixture/removed = new
	var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars

	removed.temperature = temperature
	for(var/id in cached_gases)
		removed.add_gas(id)
		removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio)
		cached_gases[id][MOLES] -= removed_gases[id][MOLES]
	garbage_collect()

	return removed

/datum/gas_mixture/copy()
	var/list/cached_gases = gases
	var/datum/gas_mixture/copy = new
	var/list/copy_gases = copy.gases

	copy.temperature = temperature
	for(var/id in cached_gases)
		add_gas(id)
		copy_gases[id][MOLES] = cached_gases[id][MOLES]

	return copy

/datum/gas_mixture/copy_from(datum/gas_mixture/sample)
	var/list/cached_gases = gases //accessing datum vars is slower than proc vars
	var/list/sample_gases = sample.gases

	temperature = sample.temperature
	for(var/id in sample_gases)
		assert_gas(id)
		cached_gases[id][MOLES] = sample_gases[id][MOLES]
	//remove all gases not in the sample
	cached_gases &= sample_gases

	return 1

/datum/gas_mixture/copy_from_turf(turf/model)
	parse_gas_string(model.initial_gas_mix)

	//acounts for changes in temperature
	var/turf/model_parent = model.parent_type
	if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature))
		temperature = model.temperature

	return 1

/datum/gas_mixture/parse_gas_string(gas_string)
	//global so that we don't have to make them more than once
	var/global/regex/R_gas_id
	var/global/regex/R_gas_value
	if(!R_gas_id)
		R_gas_id = regex("(.+?)(?=\\=)") //matches all characters before equal sign
	if(!R_gas_value)
		R_gas_value = regex("(?<=\\=)(.*)") //matches all characters after equal sign

	var/list/values = splittext(gas_string, ";")
	var/list/gases = list()
	//populate an associative list of gas -> moles by parsing the string
	while(values.len)
		var/value = values[1]
		values.Cut(1,2)
		R_gas_id.Find(value)
		R_gas_value.Find(value)
		gases[R_gas_id.group[1]] = text2num(R_gas_value.group[1])

	//deal with the specific case of temperature
	if("TEMP" in gases)
		temperature = gases["TEMP"]
		gases -= "TEMP"

	var/list/cached_gases = src.gases
	cached_gases &= gases
	for(var/id in gases-cached_gases)
		add_gas(id) //add_gases() causes a runtime due to the nature of arglist()
	for(var/id in gases)
		cached_gases[id][MOLES] = gases[id]

	return 1

/datum/gas_mixture/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
	if(!sharer)
		return 0

	var/list/cached_gases = gases
	var/list/sharer_gases = sharer.gases

	var/temperature_delta = temperature_archived - sharer.temperature_archived
	var/abs_temperature_delta = abs(temperature_delta)

	var/old_self_heat_capacity = 0
	var/old_sharer_heat_capacity = 0
	if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
		old_self_heat_capacity = heat_capacity()
		old_sharer_heat_capacity = sharer.heat_capacity()

	var/heat_capacity_self_to_sharer = 0 //heat capacity of the moles transferred from us to the sharer
	var/heat_capacity_sharer_to_self = 0 //heat capacity of the moles transferred from the sharer to us

	var/moved_moles = 0
	var/abs_moved_moles = 0

	//GAS TRANSFER
	for(var/id in sharer_gases - cached_gases) // create gases not in our cache
		add_gas(id)
	for(var/id in cached_gases) // transfer gases
		if(!sharer_gases[id]) //checking here prevents an uneeded proc call if the check fails.
			sharer.add_gas(id)

		var/gas = cached_gases[id]
		var/sharergas = sharer_gases[id]

		var/delta = QUANTIZE(gas[ARCHIVE] - sharergas[ARCHIVE])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures

		if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
			var/gas_heat_capacity = delta * gas[GAS_META][META_GAS_SPECIFIC_HEAT]
			if(delta > 0)
				heat_capacity_self_to_sharer += gas_heat_capacity
			else
				heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative. saves a proc call.

		gas[MOLES]			-= delta
		sharergas[MOLES]	+= delta
		moved_moles			+= delta
		abs_moved_moles		+= abs(delta)

	last_share = abs_moved_moles

	//THERMAL ENERGY TRANSFER
	if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
		var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer
		var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self

		//transfer of thermal energy (via changed heat capacity) between self and sharer
		if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY)
			temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity

		if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY)
			sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity
		//thermal energy of the system (self and sharer) is unchanged

			if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY)
				if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.10) // <10% change in sharer heat capacity
					temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT)

	var/list/unique_gases = cached_gases ^ sharer_gases
	if(unique_gases.len) //if all gases were present in both mixtures, we know that no gases are 0
		garbage_collect(cached_gases - sharer_gases) //any gases the sharer had, we are guaranteed to have. gases that it didn't have we are not.
		sharer.garbage_collect(sharer_gases - cached_gases) //the reverse is equally true
	sharer.after_share(src, atmos_adjacent_turfs)
	if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE)
		var/delta_pressure = temperature_archived*(total_moles() + moved_moles) - sharer.temperature_archived*(sharer.total_moles() - moved_moles)
		return delta_pressure * R_IDEAL_GAS_EQUATION / volume

/datum/gas_mixture/after_share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
	return

/datum/gas_mixture/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
	//transfer of thermal energy (via conduction) between self and sharer
	sharer_temperature = sharer_temperature || sharer.temperature_archived
	var/temperature_delta = temperature_archived - sharer_temperature
	if(abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
		var/self_heat_capacity = heat_capacity_archived()
		sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity_archived()

		if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
			var/heat = conduction_coefficient*temperature_delta* \
				(self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity))

			temperature = max(temperature - heat/self_heat_capacity, TCMB)
			sharer_temperature = max(sharer.temperature + heat/sharer_heat_capacity, TCMB)
			if(sharer)
				sharer.temperature = sharer_temperature
	return sharer_temperature
	//thermal energy of the system (self and sharer) is unchanged

/datum/gas_mixture/compare(datum/gas_mixture/sample, datatype = MOLES, adjacents = 0)
	var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars
	var/list/cached_gases = gases

	for(var/id in cached_gases | sample_gases) // compare gases from either mixture
		var/gas_moles = cached_gases[id] ? cached_gases[id][datatype] : 0
		var/sample_moles = sample_gases[id] ? sample_gases[id][datatype] : 0
		var/delta = abs(gas_moles - sample_moles)/(adjacents+1)
		if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \
			delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE)
			return id

	if(total_moles() > MINIMUM_MOLES_DELTA_TO_MOVE)
		var/temp
		var/sample_temp

		switch(datatype)
			if(MOLES)
				temp = temperature
				sample_temp = sample.temperature
			if(ARCHIVE)
				temp = temperature_archived
				sample_temp = sample.temperature_archived

		var/temperature_delta = abs(temp - sample_temp)
		if((temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) && \
			temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND * temp)
			return "temp"

	return ""

//Takes the amount of the gas you want to PP as an argument
//So I don't have to do some hacky switches/defines/magic strings
//eg:
//Tox_PP = get_partial_pressure(gas_mixture.toxins)
//O2_PP = get_partial_pressure(gas_mixture.oxygen)
//Does handle trace gases!
/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure)
	return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME
//inverse
/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure)
	return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature)

//Mathematical proofs:
/*
get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp
get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()

10/20*5 = 2.5
10 = 2.5/5*20
*/


code:
var/list/hardcoded_gases = list("o2","n2","co2","plasma") //the main four gases, which were at one time hardcoded

/proc/meta_gas_list()
	. = new /list
	for(var/gas_path in subtypesof(/datum/gas))
		var/list/gas_info = new(4)
		var/datum/gas/gas = gas_path

		gas_info[META_GAS_SPECIFIC_HEAT] = initial(gas.specific_heat)
		gas_info[META_GAS_NAME] = initial(gas.name)
		gas_info[META_GAS_MOLES_VISIBLE] = initial(gas.moles_visible)
		if(initial(gas.moles_visible) != null)
			gas_info[META_GAS_OVERLAY] = new /obj/effect/overlay/gas(initial(gas.gas_overlay))
		.[initial(gas.id)] = gas_info

/*||||||||||||||/----------\||||||||||||||*\
||||||||||||||||[GAS DATUMS]||||||||||||||||
||||||||||||||||\__________/||||||||||||||||
||||These should never be instantiated. ||||
||||They exist only to make it easier   ||||
||||to add a new gas. They are accessed ||||
||||only by meta_gas_list().            ||||
\*||||||||||||||||||||||||||||||||||||||||*/

/datum/gas
	var/id = ""
	var/specific_heat = 0
	var/name = ""
	var/gas_overlay = "" //icon_state in icons/effects/tile_effects.dmi
	var/moles_visible = null

/datum/gas/oxygen
	id = "o2"
	specific_heat = 20
	name = "Oxygen"

/datum/gas/nitrogen
	id = "n2"
	specific_heat = 20
	name = "Nitrogen"

/datum/gas/carbon_dioxide //what the fuck is this?
	id = "co2"
	specific_heat = 30
	name = "Carbon Dioxide"

/datum/gas/plasma
	id = "plasma"
	specific_heat = 200
	name = "Plasma"
	gas_overlay = "plasma"
	moles_visible = MOLES_PLASMA_VISIBLE

/datum/gas/nitrous_oxide
	id = "n2o"
	specific_heat = 40
	name = "Nitrous Oxide"
	gas_overlay = "nitrous_oxide"
	moles_visible = 1

/datum/gas/oxygen_agent_b
	id = "agent_b"
	specific_heat = 300
	name = "Oxygen Agent B"

/datum/gas/volatile_fuel
	id = "v_fuel"
	specific_heat = 30
	name = "Volatile Fuel"

/obj/effect/overlay/gas/
	icon = 'icons/effects/tile_effects.dmi'
	mouse_opacity = 0
	layer = 5
	appearance_flags = RESET_COLOR|TILE_BOUND

/obj/effect/overlay/gas/New(state)
	. = ..()
	icon_state = state


code:
//"immutable" gas mixture used for space calculations
//it can be changed, but any changes will ultimately be undone before they can have any effect

/datum/gas_mixture/space
	var/static/datum/gas_mixture/dummy = new

/datum/gas_mixture/space/New()
	..()
	temperature = TCMB
	temperature_archived = TCMB

/datum/gas_mixture/space/garbage_collect()
	gases.Cut() //clever way of ensuring we always are empty.

/datum/gas_mixture/space/archive()
	return 1 //nothing changes, so we do nothing and the archive is successful

/datum/gas_mixture/space/merge()
	return 0 //we're immutable.

/datum/gas_mixture/space/remove()
	return copy() //we're immutable, so we can just return a copy.

/datum/gas_mixture/space/remove_ratio()
	return copy() //we're immutable, so we can just return a copy.

/datum/gas_mixture/space/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
	. = dummy.share(sharer, atmos_adjacent_turfs) //keep all the sharing calculations, but don't actually change anything about ourselves
	dummy.copy_from(src)

/datum/gas_mixture/space/after_share()
	temperature = TCMB
	gases.Cut()

/datum/gas_mixture/space/react()
	return 0 //we're immutable.

/datum/gas_mixture/space/fire()
	return 0 //we're immutable.

/datum/gas_mixture/space/copy()
	return new /datum/gas_mixture/space //we're immutable, so we can just return a new instance.

/datum/gas_mixture/space/copy_from()
	return 0 //we're immutable.

/datum/gas_mixture/space/copy_from_turf()
	return 0 //we're immutable.

/datum/gas_mixture/space/parse_gas_string()
	return 0 //we're immutable.

/datum/gas_mixture/space/temperature_share()
	..()
	temperature = TCMB