code:
/obj/machinery/atmospherics/components/unary
	icon = 'icons/obj/atmospherics/components/unary_devices.dmi'
	dir = SOUTH
	initialize_directions = SOUTH
	layer = TURF_LAYER+0.1
	device_type = UNARY

/obj/machinery/atmospherics/components/unary/SetInitDirections()
	initialize_directions = dir

/*
Iconnery
*/

/obj/machinery/atmospherics/components/unary/hide(intact)
	update_icon()

	..(intact)


code:
#define EXT_BOUND	1
#define INT_BOUND	2
#define NO_BOUND	3

#define SIPHONING	0
#define RELEASING	1

/obj/machinery/atmospherics/components/unary/vent_pump
	name = "air vent"
	desc = "Has a valve and pump attached to it"
	icon_state = "vent_map"
	use_power = 1
	can_unwrench = 1
	welded = 0
	level = 1

	var/id_tag = null
	var/on = 0
	var/pump_direction = RELEASING

	var/pressure_checks = EXT_BOUND
	var/external_pressure_bound = ONE_ATMOSPHERE
	var/internal_pressure_bound = 0
	//EXT_BOUND: Do not pass external_pressure_bound
	//INT_BOUND: Do not pass internal_pressure_bound
	//NO_BOUND: Do not pass either

	var/frequency = 1439
	var/datum/radio_frequency/radio_connection
	var/radio_filter_out
	var/radio_filter_in

/obj/machinery/atmospherics/components/unary/vent_pump/on
	on = 1
	icon_state = "vent_out"

/obj/machinery/atmospherics/components/unary/vent_pump/siphon
	pump_direction = SIPHONING

/obj/machinery/atmospherics/components/unary/vent_pump/siphon/on
	on = 1
	icon_state = "vent_in"

/obj/machinery/atmospherics/components/unary/vent_pump/New()
	..()
	if(!id_tag)
		assign_uid()
		id_tag = num2text(uid)

/obj/machinery/atmospherics/components/unary/vent_pump/Destroy()
	var/area/A = get_area_master(src)
	A.air_vent_names -= id_tag
	A.air_vent_info -= id_tag

	if(SSradio)
		SSradio.remove_object(src,frequency)
	radio_connection = null

	return ..()

/obj/machinery/atmospherics/components/unary/vent_pump/high_volume
	name = "large air vent"
	power_channel = EQUIP

/obj/machinery/atmospherics/components/unary/vent_pump/high_volume/New()
	..()
	var/datum/gas_mixture/air_contents = AIR1
	air_contents.volume = 1000

/obj/machinery/atmospherics/components/unary/vent_pump/update_icon_nopipes()
	overlays.Cut()
	if(showpipe)
		overlays += getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "vent_cap", initialize_directions)

	if(welded)
		icon_state = "vent_welded"
		return

	if(!NODE1 || !on || stat & (NOPOWER|BROKEN))
		icon_state = "vent_off"
		return

	if(pump_direction & RELEASING)
		icon_state = "vent_out"
	else //pump_direction == SIPHONING
		icon_state = "vent_in"

/obj/machinery/atmospherics/components/unary/vent_pump/process_atmos()
	..()
	if(stat & (NOPOWER|BROKEN))
		return
	if (!NODE1)
		on = 0
	if(!on || welded)
		return 0

	var/datum/gas_mixture/air_contents = AIR1
	var/datum/gas_mixture/environment = loc.return_air()
	var/environment_pressure = environment.return_pressure()

	if(pump_direction & RELEASING) //internal -> external
		var/pressure_delta = 10000

		if(pressure_checks&EXT_BOUND)
			pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure))
		if(pressure_checks&INT_BOUND)
			pressure_delta = min(pressure_delta, (air_contents.return_pressure() - internal_pressure_bound))

		if(pressure_delta > 0)
			if(air_contents.temperature > 0)
				var/transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION)

				var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)

				loc.assume_air(removed)
				air_update_turf()

	else //external -> internal
		var/pressure_delta = 10000
		if(pressure_checks&EXT_BOUND)
			pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound))
		if(pressure_checks&INT_BOUND)
			pressure_delta = min(pressure_delta, (internal_pressure_bound - air_contents.return_pressure()))

		if(pressure_delta > 0)
			if(environment.temperature > 0)
				var/transfer_moles = pressure_delta*air_contents.volume/(environment.temperature * R_IDEAL_GAS_EQUATION)

				var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
				if (isnull(removed)) //in space
					return

				air_contents.merge(removed)
				air_update_turf()
	update_parents()

	return 1

//Radio remote control

/obj/machinery/atmospherics/components/unary/vent_pump/proc/set_frequency(new_frequency)
	SSradio.remove_object(src, frequency)
	frequency = new_frequency
	if(frequency)
		radio_connection = SSradio.add_object(src, frequency,radio_filter_in)

/obj/machinery/atmospherics/components/unary/vent_pump/proc/broadcast_status()
	if(!radio_connection)
		return 0

	var/datum/signal/signal = new
	signal.transmission_method = 1 //radio signal
	signal.source = src

	signal.data = list(
		"tag" = id_tag,
		"frequency" = frequency,
		"device" = "VP",
		"timestamp" = world.time,
		"power" = on,
		"direction" = pump_direction ? "release" : "siphon",
		"checks" = pressure_checks,
		"internal" = internal_pressure_bound,
		"external" = external_pressure_bound,
		"sigtype" = "status"
	)

	var/area/A = get_area_master(src)
	if(!A.air_vent_names[id_tag])
		name = "\improper [A.name] vent pump #[A.air_vent_names.len + 1]"
		A.air_vent_names[id_tag] = name
	A.air_vent_info[id_tag] = signal.data

	radio_connection.post_signal(src, signal, radio_filter_out)

	return 1

/obj/machinery/atmospherics/components/unary/vent_pump/atmosinit()
	//some vents work his own spesial way
	radio_filter_in = frequency==1439?(RADIO_FROM_AIRALARM):null
	radio_filter_out = frequency==1439?(RADIO_TO_AIRALARM):null
	if(frequency)
		set_frequency(frequency)
	broadcast_status()
	..()

/obj/machinery/atmospherics/components/unary/vent_pump/receive_signal(datum/signal/signal)
	if(stat & (NOPOWER|BROKEN))
		return
	//log_admin("DEBUG \[[world.timeofday]\]: /obj/machinery/atmospherics/components/unary/vent_pump/receive_signal([signal.debug_print()])")
	if(!signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
		return 0

	if("purge" in signal.data)
		pressure_checks &= ~EXT_BOUND
		pump_direction = SIPHONING

	if("stabalize" in signal.data)
		pressure_checks |= EXT_BOUND
		pump_direction = RELEASING

	if("power" in signal.data)
		on = text2num(signal.data["power"])

	if("power_toggle" in signal.data)
		on = !on

	if("checks" in signal.data)
		pressure_checks = text2num(signal.data["checks"])

	if("checks_toggle" in signal.data)
		pressure_checks = (pressure_checks?0:NO_BOUND)

	if("direction" in signal.data)
		pump_direction = text2num(signal.data["direction"])

	if("set_internal_pressure" in signal.data)
		internal_pressure_bound = Clamp(text2num(signal.data["set_internal_pressure"]),0,ONE_ATMOSPHERE*50)

	if("set_external_pressure" in signal.data)
		external_pressure_bound = Clamp(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50)

	if("reset_external_pressure" in signal.data)
		external_pressure_bound = ONE_ATMOSPHERE

	if("adjust_internal_pressure" in signal.data)
		internal_pressure_bound = Clamp(internal_pressure_bound + text2num(signal.data["adjust_internal_pressure"]),0,ONE_ATMOSPHERE*50)

	if("adjust_external_pressure" in signal.data)
		external_pressure_bound = Clamp(external_pressure_bound + text2num(signal.data["adjust_external_pressure"]),0,ONE_ATMOSPHERE*50)

	if("init" in signal.data)
		name = signal.data["init"]
		return

	if("status" in signal.data)
		broadcast_status()
		return //do not update_icon

		//log_admin("DEBUG \[[world.timeofday]\]: vent_pump/receive_signal: unknown command \"[signal.data["command"]]\"\n[signal.debug_print()]")
	broadcast_status()
	update_icon()
	return

/obj/machinery/atmospherics/components/unary/vent_pump/attackby(obj/item/W, mob/user, params)
	if (istype(W, /obj/item/weapon/wrench)&& !(stat & NOPOWER) && on)
		user << "You cannot unwrench this [src], turn it off first!"
		return 1
	if(istype(W, /obj/item/weapon/weldingtool))
		var/obj/item/weapon/weldingtool/WT = W
		if (WT.remove_fuel(0,user))
			playsound(loc, 'sound/items/Welder.ogg', 40, 1)
			user << "You begin welding the vent..."
			if(do_after(user, 20/W.toolspeed, target = src))
				if(!src || !WT.isOn()) return
				playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1)
				if(!welded)
					user.visible_message("[user] welds the vent shut.", "You weld the vent shut.", "You hear welding.")
					welded = 1
				else
					user.visible_message("[user] unwelds the vent.", "You unweld the vent.", "You hear welding.")
					welded = 0
				update_icon()
				pipe_vision_img = image(src, loc, layer = 20, dir = dir)
			return 0
	else
		return ..()

/obj/machinery/atmospherics/components/unary/vent_pump/examine(mob/user)
	..()
	if(welded)
		user << "It seems welded shut."

/obj/machinery/atmospherics/components/unary/vent_pump/power_change()
	..()
	update_icon_nopipes()

/obj/machinery/atmospherics/components/unary/vent_pump/can_crawl_through()
	return !welded

/obj/machinery/atmospherics/components/unary/vent_pump/attack_alien(mob/user)
	if(!welded || !(do_after(user, 20, target = src)))
		return
	user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the vent", "You hear loud scraping noises.")
	welded = 0
	update_icon()
	pipe_vision_img = image(src, loc, layer = 20, dir = dir)
	playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)

#undef INT_BOUND
#undef EXT_BOUND
#undef NO_BOUND

#undef SIPHONING
#undef RELEASING

license: mit


code:
#define SIPHONING	0
#define SCRUBBING	1

/obj/machinery/atmospherics/components/unary/vent_scrubber
	name = "air scrubber"
	desc = "Has a valve and pump attached to it"
	icon_state = "scrub_map"
	use_power = 1
	idle_power_usage = 10
	active_power_usage = 60
	can_unwrench = 1
	welded = 0
	level = 1

	var/id_tag = null
	var/on = 0
	var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing

	var/scrub_CO2 = 1
	var/scrub_Toxins = 0
	var/scrub_N2O = 0

	var/volume_rate = 200
	var/widenet = 0 //is this scrubber acting on the 3x3 area around it.
	var/list/turf/adjacent_turfs = list()

	var/frequency = 1439
	var/datum/radio_frequency/radio_connection
	var/radio_filter_out
	var/radio_filter_in

/obj/machinery/atmospherics/components/unary/vent_scrubber/New()
	..()
	if(!id_tag)
		assign_uid()
		id_tag = num2text(uid)

/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy()
	var/area/A = get_area_master(src)
	A.air_scrub_names -= id_tag
	A.air_scrub_info -= id_tag

	if(SSradio)
		SSradio.remove_object(src,frequency)
	radio_connection = null

	for(var/I in adjacent_turfs)
		I = null

	return ..()

/obj/machinery/atmospherics/components/unary/vent_scrubber/auto_use_power()
	if(!powered(power_channel))
		return 0
	if(!on || welded)
		return 0
	if(stat & (NOPOWER|BROKEN))
		return 0

	var/amount = idle_power_usage

	if(scrubbing & SCRUBBING)
		if (scrub_CO2)
			amount += idle_power_usage
		if (scrub_Toxins)
			amount += idle_power_usage
		if (scrub_N2O)
			amount += idle_power_usage
	else //scrubbing == SIPHONING
		amount = active_power_usage

	if (widenet)
		amount += amount * (adjacent_turfs.len * (adjacent_turfs.len / 2))
	use_power(amount, power_channel)
	return 1

/obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes()
	overlays.Cut()
	if(showpipe)
		overlays += getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "scrub_cap", initialize_directions)

	if(welded)
		icon_state = "scrub_welded"
		return

	if(!NODE1 || !on || stat & (NOPOWER|BROKEN))
		icon_state = "scrub_off"
		return

	if(scrubbing & SCRUBBING)
		icon_state = "scrub_on"
	else //scrubbing == SIPHONING
		icon_state = "scrub_purge"

/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/set_frequency(new_frequency)
	SSradio.remove_object(src, frequency)
	frequency = new_frequency
	radio_connection = SSradio.add_object(src, frequency, radio_filter_in)

/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/broadcast_status()
	if(!radio_connection)
		return 0

	var/datum/signal/signal = new
	signal.transmission_method = 1 //radio signal
	signal.source = src
	signal.data = list(
		"tag" = id_tag,
		"frequency" = frequency,
		"device" = "VS",
		"timestamp" = world.time,
		"power" = on,
		"scrubbing" = scrubbing,
		"widenet" = widenet,
		"filter_co2" = scrub_CO2,
		"filter_toxins" = scrub_Toxins,
		"filter_n2o" = scrub_N2O,
		"sigtype" = "status"
	)

	var/area/A = get_area_master(src)
	if(!A.air_scrub_names[id_tag])
		name = "\improper [A.name] air scrubber #[A.air_scrub_names.len + 1]"
		A.air_scrub_names[id_tag] = name
	A.air_scrub_info[id_tag] = signal.data

	radio_connection.post_signal(src, signal, radio_filter_out)

	return 1

/obj/machinery/atmospherics/components/unary/vent_scrubber/atmosinit()
	radio_filter_in = frequency==initial(frequency)?(RADIO_FROM_AIRALARM):null
	radio_filter_out = frequency==initial(frequency)?(RADIO_TO_AIRALARM):null
	if(frequency)
		set_frequency(frequency)
	broadcast_status()
	check_turfs()
	..()

/obj/machinery/atmospherics/components/unary/vent_scrubber/process_atmos()
	..()
	if(stat & (NOPOWER|BROKEN))
		return
	if (!NODE1)
		on = 0
	if(!on || welded)
		return 0
	scrub(loc)
	if(widenet)
		for (var/turf/tile in adjacent_turfs)
			scrub(tile)

/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile)
	if (!istype(tile))
		return 0

	var/datum/gas_mixture/environment = tile.return_air()
	var/datum/gas_mixture/air_contents = AIR1
	var/list/env_gases = environment.gases

	if(scrubbing & SCRUBBING)
		var/should_we_scrub = FALSE
		for(var/id in env_gases)
			if(id == "n2" || id == "o2")
				continue
			if(env_gases[id][MOLES])
				should_we_scrub = TRUE
				break
		if(should_we_scrub)
			var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles()

			//Take a gas sample
			var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
			var/list/removed_gases = removed.gases
			if (isnull(removed)) //in space
				return

			//Filter it
			var/datum/gas_mixture/filtered_out = new
			var/list/filtered_gases = filtered_out.gases
			filtered_out.temperature = removed.temperature

			if(scrub_Toxins && removed_gases["plasma"])
				filtered_out.assert_gas("plasma")
				filtered_gases["plasma"][MOLES] = removed_gases["plasma"][MOLES]
				removed.gases["plasma"][MOLES] = 0

			if(scrub_CO2 && removed_gases["co2"])
				filtered_out.assert_gas("co2")
				filtered_out.gases["co2"][MOLES] = removed_gases["co2"][MOLES]
				removed.gases["co2"][MOLES] = 0

			if(removed_gases["agent_b"])
				filtered_out.assert_gas("agent_b")
				filtered_out.gases["agent_b"][MOLES] = removed_gases["agent_b"][MOLES]
				removed.gases["agent_b"][MOLES] = 0

			if(scrub_N2O && removed_gases["n2o"])
				filtered_out.assert_gas("n2o")
				filtered_out.gases["n2o"][MOLES] = removed_gases["n2o"][MOLES]
				removed.gases["n2o"][MOLES] = 0

			removed.garbage_collect()

			//Remix the resulting gases
			air_contents.merge(filtered_out)

			tile.assume_air(removed)
			tile.air_update_turf()

	else //Just siphoning all air
		if (air_contents.return_pressure()>=50*ONE_ATMOSPHERE)
			return

		var/transfer_moles = environment.total_moles()*(volume_rate/environment.volume)

		var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)

		air_contents.merge(removed)
		tile.air_update_turf()

	update_parents()

	return 1

//There is no easy way for an object to be notified of changes to atmos can pass flags
//	So we check every machinery process (2 seconds)
/obj/machinery/atmospherics/components/unary/vent_scrubber/process()
	if (widenet)
		check_turfs()

//we populate a list of turfs with nonatmos-blocked cardinal turfs AND
//	diagonal turfs that can share atmos with *both* of the cardinal turfs
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/check_turfs()
	adjacent_turfs.Cut()
	var/turf/T = get_turf(src)
	if(istype(T))
		adjacent_turfs = T.GetAtmosAdjacentTurfs(alldir = 1)

/obj/machinery/atmospherics/components/unary/vent_scrubber/receive_signal(datum/signal/signal)
	if(stat & (NOPOWER|BROKEN))
		return
	if(!signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
		return 0

	if("power" in signal.data)
		on = text2num(signal.data["power"])
	if("power_toggle" in signal.data)
		on = !on

	if("widenet" in signal.data)
		widenet = text2num(signal.data["widenet"])
	if("toggle_widenet" in signal.data)
		widenet = !widenet

	if("scrubbing" in signal.data)
		scrubbing = text2num(signal.data["scrubbing"])
	if("toggle_scrubbing" in signal.data)
		scrubbing = !scrubbing

	if("co2_scrub" in signal.data)
		scrub_CO2 = text2num(signal.data["co2_scrub"])
	if("toggle_co2_scrub" in signal.data)
		scrub_CO2 = !scrub_CO2

	if("tox_scrub" in signal.data)
		scrub_Toxins = text2num(signal.data["tox_scrub"])
	if("toggle_tox_scrub" in signal.data)
		scrub_Toxins = !scrub_Toxins

	if("n2o_scrub" in signal.data)
		scrub_N2O = text2num(signal.data["n2o_scrub"])
	if("toggle_n2o_scrub" in signal.data)
		scrub_N2O = !scrub_N2O

	if("init" in signal.data)
		name = signal.data["init"]
		return

	if("status" in signal.data)
		broadcast_status()
		return //do not update_icon

	broadcast_status()
	update_icon()
	return

/obj/machinery/atmospherics/components/unary/vent_scrubber/power_change()
	..()
	update_icon_nopipes()

/obj/machinery/atmospherics/components/unary/vent_scrubber/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/weapon/weldingtool))
		var/obj/item/weapon/weldingtool/WT = W
		if(WT.remove_fuel(0,user))
			playsound(loc, 'sound/items/Welder.ogg', 40, 1)
			user << "Now welding the scrubber."
			if(do_after(user, 20/W.toolspeed, target = src))
				if(!src || !WT.isOn())
					return
				playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1)
				if(!welded)
					user.visible_message("[user] welds the scrubber shut.","You weld the scrubber shut.", "You hear welding.")
					welded = 1
				else
					user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.")
					welded = 0
				update_icon()
				pipe_vision_img = image(src, loc, layer = 20, dir = dir)
			return 0
	if (!istype(W, /obj/item/weapon/wrench))
		return ..()
	if (!(stat & NOPOWER) && on)
		user << "You cannot unwrench this [src], turn it off first!"
		return 1
	return ..()

/obj/machinery/atmospherics/components/unary/vent_scrubber/can_crawl_through()
	return !welded

/obj/machinery/atmospherics/components/unary/vent_scrubber/attack_alien(mob/user)
	if(!welded || !(do_after(user, 20, target = src)))
		return
	user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.")
	welded = 0
	update_icon()
	pipe_vision_img = image(src, loc, layer = 20, dir = dir)
	playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)

#undef SIPHONING
#undef SCRUBBING


code:
/datum/pipeline
	var/datum/gas_mixture/air
	var/list/datum/gas_mixture/other_airs = list()

	var/list/obj/machinery/atmospherics/pipe/members = list()
	var/list/obj/machinery/atmospherics/components/other_atmosmch = list()

	var/update = 1

/datum/pipeline/New()
	SSair.networks += src

/datum/pipeline/Destroy()
	SSair.networks -= src
	if(air && air.volume)
		temporarily_store_air()
	for(var/obj/machinery/atmospherics/pipe/P in members)
		P.parent = null
	for(var/obj/machinery/atmospherics/components/C in other_atmosmch)
		C.nullifyPipenet(src)
	return ..()

/datum/pipeline/process()
	if(update)
		update = 0
		reconcile_air()
	update = air.react()

var/pipenetwarnings = 10

/datum/pipeline/proc/build_pipeline(obj/machinery/atmospherics/base)
	var/volume = 0
	if(istype(base, /obj/machinery/atmospherics/pipe))
		var/obj/machinery/atmospherics/pipe/E = base
		volume = E.volume
		members += E
		if(E.air_temporary)
			air = E.air_temporary
			E.air_temporary = null
	else
		addMachineryMember(base)
	if(!air)
		air = new
	var/list/possible_expansions = list(base)
	while(possible_expansions.len>0)
		for(var/obj/machinery/atmospherics/borderline in possible_expansions)

			var/list/result = borderline.pipeline_expansion(src)

			if(result.len>0)
				for(var/obj/machinery/atmospherics/P in result)
					if(istype(P, /obj/machinery/atmospherics/pipe))
						var/obj/machinery/atmospherics/pipe/item = P
						if(!members.Find(item))

							if(item.parent)
								if(pipenetwarnings > 0)
									warning("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z])")
									pipenetwarnings -= 1
									if(pipenetwarnings == 0)
										warning("build_pipeline(): further messages about pipenets will be supressed")
							members += item
							possible_expansions += item

							volume += item.volume
							item.parent = src

							if(item.air_temporary)
								air.merge(item.air_temporary)
								item.air_temporary = null
					else
						P.setPipenet(src, borderline)
						addMachineryMember(P)

			possible_expansions -= borderline

	air.volume = volume

/datum/pipeline/proc/addMachineryMember(obj/machinery/atmospherics/components/C)
	other_atmosmch |= C
	var/datum/gas_mixture/G = C.returnPipenetAir(src)
	other_airs |= G

/datum/pipeline/proc/addMember(obj/machinery/atmospherics/A, obj/machinery/atmospherics/N)
	if(istype(A, /obj/machinery/atmospherics/pipe))
		var/obj/machinery/atmospherics/pipe/P = A
		P.parent = src
		var/list/adjacent = P.pipeline_expansion()
		for(var/obj/machinery/atmospherics/pipe/I in adjacent)
			if(I.parent == src)
				continue
			var/datum/pipeline/E = I.parent
			merge(E)
		if(!members.Find(P))
			members += P
			air.volume += P.volume
	else
		A.setPipenet(src, N)
		addMachineryMember(A)

/datum/pipeline/proc/merge(datum/pipeline/E)
	air.volume += E.air.volume
	members.Add(E.members)
	for(var/obj/machinery/atmospherics/pipe/S in E.members)
		S.parent = src
	air.merge(E.air)
	for(var/obj/machinery/atmospherics/components/C in E.other_atmosmch)
		C.replacePipenet(E, src)
	other_atmosmch.Add(E.other_atmosmch)
	other_airs.Add(E.other_airs)
	E.members.Cut()
	E.other_atmosmch.Cut()
	qdel(E)

/obj/machinery/atmospherics/proc/addMember(obj/machinery/atmospherics/A)
	return

/obj/machinery/atmospherics/pipe/addMember(obj/machinery/atmospherics/A)
	parent.addMember(A, src)

/obj/machinery/atmospherics/components/addMember(obj/machinery/atmospherics/A)
	var/datum/pipeline/P = returnPipenet(A)
	P.addMember(A, src)

/datum/pipeline/proc/temporarily_store_air()
	//Update individual gas_mixtures by volume ratio

	for(var/obj/machinery/atmospherics/pipe/member in members)
		member.air_temporary = new
		member.air_temporary.volume = member.volume
		member.air_temporary.copy_from(air)
		var/member_gases = member.air_temporary.gases

		for(var/id in member_gases)
			member_gases[id][MOLES] *= member.volume/air.volume

		member.air_temporary.temperature = air.temperature

/datum/pipeline/proc/temperature_interact(turf/target, share_volume, thermal_conductivity)
	var/total_heat_capacity = air.heat_capacity()
	var/partial_heat_capacity = total_heat_capacity*(share_volume/air.volume)

	if(istype(target, /turf/open))
		var/turf/open/modeled_location = target

		if(modeled_location.blocks_air)

			if((modeled_location.heat_capacity>0) && (partial_heat_capacity>0))
				var/delta_temperature = air.temperature - modeled_location.temperature

				var/heat = thermal_conductivity*delta_temperature* \
					(partial_heat_capacity*modeled_location.heat_capacity/(partial_heat_capacity+modeled_location.heat_capacity))

				air.temperature -= heat/total_heat_capacity
				modeled_location.temperature += heat/modeled_location.heat_capacity

		else
			var/delta_temperature = 0
			var/sharer_heat_capacity = 0

			delta_temperature = (air.temperature - modeled_location.air.temperature)
			sharer_heat_capacity = modeled_location.air.heat_capacity()

			var/self_temperature_delta = 0
			var/sharer_temperature_delta = 0

			if((sharer_heat_capacity>0) && (partial_heat_capacity>0))
				var/heat = thermal_conductivity*delta_temperature* \
					(partial_heat_capacity*sharer_heat_capacity/(partial_heat_capacity+sharer_heat_capacity))

				self_temperature_delta = -heat/total_heat_capacity
				sharer_temperature_delta = heat/sharer_heat_capacity
			else
				return 1

			air.temperature += self_temperature_delta

			modeled_location.air.temperature += sharer_temperature_delta
			modeled_location.air_update_turf()

	else
		if((target.heat_capacity>0) && (partial_heat_capacity>0))
			var/delta_temperature = air.temperature - target.temperature

			var/heat = thermal_conductivity*delta_temperature* \
				(partial_heat_capacity*target.heat_capacity/(partial_heat_capacity+target.heat_capacity))

			air.temperature -= heat/total_heat_capacity
	update = 1

/datum/pipeline/proc/reconcile_air()
	var/list/datum/gas_mixture/GL = list()
	var/list/datum/pipeline/PL = list()
	PL += src

	for(var/i = 1; i <= PL.len; i++) //can't do a for-each here because we may add to the list within the loop
		var/datum/pipeline/P = PL[i]
		GL += P.air
		GL += P.other_airs
		for(var/obj/machinery/atmospherics/components/binary/valve/V in P.other_atmosmch)
			if(V.open)
				PL |= V.PARENT1
				PL |= V.PARENT2
		for(var/obj/machinery/atmospherics/components/unary/portables_connector/C in P.other_atmosmch)
			if(C.connected_device)
				GL += C.portableConnectorReturnAir()

	var/total_thermal_energy = 0
	var/total_heat_capacity = 0
	var/datum/gas_mixture/total_gas_mixture = new(0)

	for(var/i in GL)
		var/datum/gas_mixture/G = i
		total_gas_mixture.volume += G.volume

		total_gas_mixture.merge(G)

		total_thermal_energy += G.thermal_energy()
		total_heat_capacity += G.heat_capacity()

	total_gas_mixture.temperature = total_heat_capacity ? total_thermal_energy/total_heat_capacity : 0

	if(total_gas_mixture.volume > 0)
		//Update individual gas_mixtures by volume ratio
		for(var/i in GL)
			var/datum/gas_mixture/G = i
			G.copy_from(total_gas_mixture)
			var/list/G_gases = G.gases
			for(var/id in G_gases)
				G_gases[id][MOLES] *= G.volume/total_gas_mixture.volume


code:
/obj/machinery/meter
	name = "gas flow meter"
	desc = "It measures something."
	icon = 'icons/obj/meter.dmi'
	icon_state = "meterX"
	var/obj/machinery/atmospherics/pipe/target = null
	anchored = 1
	power_channel = ENVIRON
	var/frequency = 0
	var/id_tag
	use_power = 1
	idle_power_usage = 2
	active_power_usage = 4

/obj/machinery/meter/New()
	..()
	SSair.atmos_machinery += src
	src.target = locate(/obj/machinery/atmospherics/pipe) in loc
	return 1

/obj/machinery/meter/Destroy()
	SSair.atmos_machinery -= src
	src.target = null
	return ..()

/obj/machinery/meter/initialize()
	if (!target)
		src.target = locate(/obj/machinery/atmospherics/pipe) in loc

/obj/machinery/meter/process_atmos()
	if(!target)
		icon_state = "meterX"
		return 0

	if(stat & (BROKEN|NOPOWER))
		icon_state = "meter0"
		return 0

	use_power(5)

	var/datum/gas_mixture/environment = target.return_air()
	if(!environment)
		icon_state = "meterX"
		return 0

	var/env_pressure = environment.return_pressure()
	if(env_pressure <= 0.15*ONE_ATMOSPHERE)
		icon_state = "meter0"
	else if(env_pressure <= 1.8*ONE_ATMOSPHERE)
		var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5)
		icon_state = "meter1_[val]"
	else if(env_pressure <= 30*ONE_ATMOSPHERE)
		var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1
		icon_state = "meter2_[val]"
	else if(env_pressure <= 59*ONE_ATMOSPHERE)
		var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1
		icon_state = "meter3_[val]"
	else
		icon_state = "meter4"

	if(frequency)
		var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency)

		if(!radio_connection)
			return

		var/datum/signal/signal = new
		signal.source = src
		signal.transmission_method = 1
		signal.data = list(
			"id_tag" = id_tag,
			"device" = "AM",
			"pressure" = round(env_pressure),
			"sigtype" = "status"
		)
		radio_connection.post_signal(src, signal)

/obj/machinery/meter/proc/status()
	var/t = ""
	if (src.target)
		var/datum/gas_mixture/environment = target.return_air()
		if(environment)
			t += "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)] K ([round(environment.temperature-T0C,0.01)]°C)"
		else
			t += "The sensor error light is blinking."
	else
		t += "The connect error light is blinking."
	return t

/obj/machinery/meter/examine(mob/user)
	..()
	user << status()

/obj/machinery/meter/attackby(obj/item/weapon/W, mob/user, params)
	if (istype(W, /obj/item/weapon/wrench))
		playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
		user << "You begin to unfasten \the [src]..."
		if (do_after(user, 40/W.toolspeed, target = src))
			user.visible_message( \
				"[user] unfastens \the [src].", \
				"You unfasten \the [src].", \
				"You hear ratchet.")
			new /obj/item/pipe_meter(src.loc)
			qdel(src)
	else
		..()

/obj/machinery/meter/attack_ai(mob/user)
	return src.attack_hand(user)

/obj/machinery/meter/attack_paw(mob/user)
	return src.attack_hand(user)

/obj/machinery/meter/attack_hand(mob/user)

	if(stat & (NOPOWER|BROKEN))
		return 1
	else
		usr << status()
		return 1

/obj/machinery/meter/singularity_pull(S, current_size)
	if(current_size >= STAGE_FIVE)
		new /obj/item/pipe_meter(loc)
		qdel(src)

// TURF METER - REPORTS A TILE'S AIR CONTENTS
//	why are you yelling?

/obj/machinery/meter/turf/New()
	..()
	src.target = loc
	return 1

/obj/machinery/meter/turf/initialize()
	if (!target)
		src.target = loc


code:
/obj/machinery/zvent
	name = "interfloor air transfer system"

	icon = 'icons/obj/atmospherics/components/unary_devices.dmi'
	icon_state = "vent_map"
	density = 0
	anchored=1

	var/on = 0
	var/volume_rate = 800

/obj/machinery/zvent/New()
	..()
	SSair.atmos_machinery += src

/obj/machinery/zvent/Destroy()
	SSair.atmos_machinery -= src
	return ..()

/obj/machinery/zvent/process_atmos()

	//all this object does, is make its turf share air with the ones above and below it, if they have a vent too.
	if (istype(loc,/turf)) //if we're not on a valid turf, forget it
		for (var/new_z in list(-1,1))  //change this list if a fancier system of z-levels gets implemented
			var/turf/open/zturf_conn = locate(x,y,z+new_z)
			if (istype(zturf_conn))
				var/obj/machinery/zvent/zvent_conn= locate(/obj/machinery/zvent) in zturf_conn
				if (istype(zvent_conn))
					//both floors have simulated turfs, share()
					var/turf/open/myturf = loc
					var/datum/gas_mixture/conn_air = zturf_conn.air //TODO: pop culture reference
					var/datum/gas_mixture/my_air = myturf.air
					if (istype(conn_air) && istype(my_air))
						my_air.share(conn_air)
						air_update_turf()


code:
/obj/machinery/atmospherics/pipe/heat_exchanging/
	icon = 'icons/obj/atmospherics/pipes/heat.dmi'
	level = 2
	var/initialize_directions_he
	var/minimum_temperature_difference = 20
	var/thermal_conductivity = WINDOW_HEAT_TRANSFER_COEFFICIENT
	color = "#404040"
	buckle_lying = 1
	var/icon_temperature = T20C //stop small changes in temperature causing icon refresh

/obj/machinery/atmospherics/pipe/heat_exchanging/New()
	..()
	color = "#404040"

/obj/machinery/atmospherics/pipe/heat_exchanging/can_be_node(obj/machinery/atmospherics/pipe/heat_exchanging/target)
	if(!istype(target))
		return 0
	if(target.initialize_directions_he & get_dir(target,src))
		return 1

/obj/machinery/atmospherics/pipe/heat_exchanging/hide()
	return

/obj/machinery/atmospherics/pipe/heat_exchanging/GetInitDirections()
	return ..() | initialize_directions_he

/obj/machinery/atmospherics/pipe/heat_exchanging/process_atmos()
	var/environment_temperature = 0
	var/datum/gas_mixture/pipe_air = return_air()

	var/turf/T = loc
	if(istype(T))
		if(T.blocks_air)
			environment_temperature = T.temperature
		else
			var/datum/gas_mixture/environment = T.return_air()
			environment_temperature = environment.temperature
	else
		environment_temperature = T.temperature

	if(abs(environment_temperature-pipe_air.temperature) > minimum_temperature_difference)
		parent.temperature_interact(T, volume, thermal_conductivity)

	//heatup/cooldown any mobs buckled to ourselves based on our temperature
	if(buckled_mobs.len)
		var/hc = pipe_air.heat_capacity()
		var/mob/living/heat_source = buckled_mobs[1]
		//Best guess-estimate of the total bodytemperature of all the mobs, since they share the same environment it's ~ok~ to guess like this
		var/avg_temp = (pipe_air.temperature * hc + (heat_source.bodytemperature * buckled_mobs.len) * 3500) / (hc + (buckled_mobs.len * 3500))
		for(var/m in buckled_mobs)
			var/mob/living/L = m
			L.bodytemperature = avg_temp
		pipe_air.temperature = avg_temp

/obj/machinery/atmospherics/pipe/heat_exchanging/process()
	if(!parent)
		return //machines subsystem fires before atmos is initialized so this prevents race condition runtimes

	var/datum/gas_mixture/pipe_air = return_air()

	//Heat causes pipe to glow
	if(pipe_air.temperature && (icon_temperature > 500 || pipe_air.temperature > 500)) //glow starts at 500K
		if(abs(pipe_air.temperature - icon_temperature) > 10)
			icon_temperature = pipe_air.temperature

			var/h_r = heat2colour_r(icon_temperature)
			var/h_g = heat2colour_g(icon_temperature)
			var/h_b = heat2colour_b(icon_temperature)

			if(icon_temperature < 2000)//scale glow until 2000K
				var/scale = (icon_temperature - 500) / 1500
				h_r = 64 + (h_r - 64) * scale
				h_g = 64 + (h_g - 64) * scale
				h_b = 64 + (h_b - 64) * scale

			animate(src, color = rgb(h_r, h_g, h_b), time = 20, easing = SINE_EASING)

	//burn any mobs buckled based on temperature
	if(buckled_mobs.len)
		var/heat_limit = 1000
		if(pipe_air.temperature > heat_limit + 1)
			for(var/m in buckled_mobs)
				var/mob/living/buckled_mob = m
				buckled_mob.apply_damage(4 * log(pipe_air.temperature - heat_limit), BURN, "chest")


code:
/obj/machinery/atmospherics/pipe/heat_exchanging/junction
	icon = 'icons/obj/atmospherics/pipes/junction.dmi'
	icon_state = "intact"

	name = "junction"
	desc = "A one meter junction that connects regular and heat-exchanging pipe"

	minimum_temperature_difference = 300
	thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT

	dir = SOUTH
	initialize_directions = NORTH
	initialize_directions_he = SOUTH

	device_type = BINARY

/obj/machinery/atmospherics/pipe/heat_exchanging/junction/SetInitDirections()
	switch(dir)
		if(SOUTH)
			initialize_directions = NORTH
			initialize_directions_he = SOUTH
		if(NORTH)
			initialize_directions = SOUTH
			initialize_directions_he = NORTH
		if(EAST)
			initialize_directions = WEST
			initialize_directions_he = EAST
		if(WEST)
			initialize_directions = EAST
			initialize_directions_he = WEST

/obj/machinery/atmospherics/pipe/heat_exchanging/junction/atmosinit()
	var/node2_connect = dir
	var/node1_connect = turn(dir, 180)
	var/list/node_connects = list(node1_connect, node2_connect)

	..(node_connects)

/obj/machinery/atmospherics/pipe/heat_exchanging/junction/can_be_node(obj/machinery/atmospherics/target, iteration)
	var/init_dir
	switch(iteration)
		if(1)
			init_dir = target.initialize_directions
		if(2)
			var/obj/machinery/atmospherics/pipe/heat_exchanging/H = target
			if(!istype(H))
				return 0
			init_dir = H.initialize_directions_he
	if(init_dir & get_dir(target,src))
		return 1


code:
//3-way manifold
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold
	icon_state = "manifold"

	name = "pipe manifold"
	desc = "A manifold composed of regular pipes"

	dir = SOUTH
	initialize_directions_he = EAST|NORTH|WEST

	device_type = TRINARY

/obj/machinery/atmospherics/pipe/heat_exchanging/manifold/SetInitDirections()
	switch(dir)
		if(NORTH)
			initialize_directions_he = EAST|SOUTH|WEST
		if(SOUTH)
			initialize_directions_he = WEST|NORTH|EAST
		if(EAST)
			initialize_directions_he = SOUTH|WEST|NORTH
		if(WEST)
			initialize_directions_he = NORTH|EAST|SOUTH

/obj/machinery/atmospherics/pipe/heat_exchanging/manifold/update_icon()
	var/invis = invisibility ? "-f" : ""

	icon_state = "manifold_center[invis]"

	overlays.Cut()

	//Add non-broken pieces
	for(DEVICE_TYPE_LOOP)
		if(NODE_I)
			overlays += getpipeimage('icons/obj/atmospherics/pipes/heat.dmi', "manifold_intact[invis]", get_dir(src, NODE_I))

//4-way manifold
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w
	icon_state = "manifold4w"

	name = "4-way pipe manifold"
	desc = "A manifold composed of heat-exchanging pipes"

	initialize_directions_he = NORTH|SOUTH|EAST|WEST

	device_type = QUATERNARY

/obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w/SetInitDirections()
	initialize_directions_he = initial(initialize_directions_he)

/obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w/update_icon()
	var/invis = invisibility ? "-f" : ""

	icon_state = "manifold4w_center[invis]"

	overlays.Cut()

	//Add non-broken pieces
	for(DEVICE_TYPE_LOOP)
		if(NODE_I)
			overlays += getpipeimage('icons/obj/atmospherics/pipes/heat.dmi', "manifold_intact[invis]", get_dir(src, NODE_I))


code:
/obj/machinery/atmospherics/pipe/heat_exchanging/simple
	icon_state = "intact"

	name = "pipe"
	desc = "A one meter section of heat-exchanging pipe"

	dir = SOUTH
	initialize_directions_he = SOUTH|NORTH

	device_type = BINARY

/obj/machinery/atmospherics/pipe/heat_exchanging/simple/SetInitDirections()
	if(dir in diagonals)
		initialize_directions_he = dir
	switch(dir)
		if(NORTH,SOUTH)
			initialize_directions_he = SOUTH|NORTH
		if(EAST,WEST)
			initialize_directions_he = WEST|EAST

/obj/machinery/atmospherics/pipe/heat_exchanging/simple/proc/normalize_dir()
	if(dir==SOUTH)
		dir = NORTH
	else if(dir==WEST)
		dir = EAST

/obj/machinery/atmospherics/pipe/heat_exchanging/simple/atmosinit()
	normalize_dir()
	..()

/obj/machinery/atmospherics/pipe/heat_exchanging/simple/update_icon()
	normalize_dir()
	..()


code:
/*
3-Way Manifold
*/
/obj/machinery/atmospherics/pipe/manifold
	icon = 'icons/obj/atmospherics/pipes/manifold.dmi'
	icon_state = "manifold"

	name = "pipe manifold"
	desc = "A manifold composed of regular pipes"

	dir = SOUTH
	initialize_directions = EAST|NORTH|WEST

	device_type = TRINARY

/obj/machinery/atmospherics/pipe/manifold/SetInitDirections()
	switch(dir)
		if(NORTH)
			initialize_directions = EAST|SOUTH|WEST
		if(SOUTH)
			initialize_directions = WEST|NORTH|EAST
		if(EAST)
			initialize_directions = SOUTH|WEST|NORTH
		if(WEST)
			initialize_directions = NORTH|EAST|SOUTH

/obj/machinery/atmospherics/pipe/manifold/update_icon()
	var/invis = invisibility ? "-f" : ""

	icon_state = "manifold_center[invis]"

	overlays.Cut()

	//Add non-broken pieces
	for(DEVICE_TYPE_LOOP)
		if(NODE_I)
			overlays += getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full[invis]", get_dir(src, NODE_I))

//Colored pipes, use these for mapping
/obj/machinery/atmospherics/pipe/manifold/general
	name="pipe"

/obj/machinery/atmospherics/pipe/manifold/general/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold/general/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold/scrubbers
	name="scrubbers pipe"
	pipe_color=rgb(255,0,0)
	color=rgb(255,0,0)

/obj/machinery/atmospherics/pipe/manifold/scrubbers/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold/supply
	name="air supply pipe"
	pipe_color=rgb(0,0,255)
	color=rgb(0,0,255)

/obj/machinery/atmospherics/pipe/manifold/supply/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold/supply/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold/supplymain
	name="main air supply pipe"
	pipe_color=rgb(130,43,272)
	color=rgb(130,43,272)

/obj/machinery/atmospherics/pipe/manifold/supplymain/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold/supplymain/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold/yellow
	pipe_color=rgb(255,198,0)
	color=rgb(255,198,0)

/obj/machinery/atmospherics/pipe/manifold/yellow/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold/yellow/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold/cyan
	pipe_color=rgb(0,256,249)
	color=rgb(0,256,249)

/obj/machinery/atmospherics/pipe/manifold/cyan/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold/cyan/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold/green
	pipe_color=rgb(30,256,0)
	color=rgb(30,256,0)

/obj/machinery/atmospherics/pipe/manifold/green/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold/green/hidden
	level = 1


code:
/*
4-way manifold
*/
/obj/machinery/atmospherics/pipe/manifold4w
	icon = 'icons/obj/atmospherics/pipes/manifold.dmi'
	icon_state = "manifold4w"

	name = "4-way pipe manifold"
	desc = "A manifold composed of regular pipes"

	initialize_directions = NORTH|SOUTH|EAST|WEST

	device_type = QUATERNARY

/obj/machinery/atmospherics/pipe/manifold4w/SetInitDirections()
	initialize_directions = initial(initialize_directions)

/obj/machinery/atmospherics/pipe/manifold4w/update_icon()
	var/invis = invisibility ? "-f" : ""

	icon_state = "manifold4w_center[invis]"

	overlays.Cut()

	//Add non-broken pieces
	for(DEVICE_TYPE_LOOP)
		if(NODE_I)
			overlays += getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full[invis]", get_dir(src, NODE_I))

//Colored pipes, use these for mapping
/obj/machinery/atmospherics/pipe/manifold4w/general
	name="pipe"

/obj/machinery/atmospherics/pipe/manifold4w/general/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold4w/general/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold4w/scrubbers
	name="scrubbers pipe"
	pipe_color=rgb(255,0,0)
	color=rgb(255,0,0)

/obj/machinery/atmospherics/pipe/manifold4w/scrubbers/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold4w/scrubbers/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold4w/supply
	name="air supply pipe"
	pipe_color=rgb(0,0,255)
	color=rgb(0,0,255)

/obj/machinery/atmospherics/pipe/manifold4w/supply/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold4w/supply/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold4w/supplymain
	name="main air supply pipe"
	pipe_color=rgb(130,43,272)
	color=rgb(130,43,272)

/obj/machinery/atmospherics/pipe/manifold4w/supplymain/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold4w/supplymain/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold4w/yellow
	pipe_color=rgb(255,198,0)
	color=rgb(255,198,0)

/obj/machinery/atmospherics/pipe/manifold4w/yellow/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold4w/yellow/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold4w/cyan
	pipe_color=rgb(0,256,249)
	color=rgb(0,256,249)

/obj/machinery/atmospherics/pipe/manifold4w/cyan/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold4w/cyan/hidden
	level = 1

/obj/machinery/atmospherics/pipe/manifold4w/green
	pipe_color=rgb(30,256,0)
	color=rgb(30,256,0)

/obj/machinery/atmospherics/pipe/manifold4w/green/visible
	level = 2

/obj/machinery/atmospherics/pipe/manifold4w/green/hidden
	level = 1


code:
/obj/machinery/atmospherics/pipe
	var/datum/gas_mixture/air_temporary //used when reconstructing a pipeline that broke
	var/volume = 0

	level = 1
	layer = 2.4 //under wires with their 2.44

	use_power = 0
	can_unwrench = 1
	var/datum/pipeline/parent = null

	//Buckling
	can_buckle = 1
	buckle_requires_restraints = 1
	buckle_lying = -1

/obj/machinery/atmospherics/pipe/New()
	color = pipe_color
	volume = 35 * device_type
	..()

/obj/machinery/atmospherics/pipe/nullifyNode(I)
	var/obj/machinery/atmospherics/oldN = NODE_I
	..()
	if(oldN)
		oldN.build_network()

/obj/machinery/atmospherics/pipe/update_icon() //overridden by manifolds
	if(NODE1&&NODE2)
		icon_state = "intact[invisibility ? "-f" : "" ]"
	else
		var/have_node1 = NODE1?1:0
		var/have_node2 = NODE2?1:0
		icon_state = "exposed[have_node1][have_node2][invisibility ? "-f" : "" ]"

/obj/machinery/atmospherics/pipe/atmosinit()
	var/turf/T = loc			// hide if turf is not intact
	hide(T.intact)
	..()

/obj/machinery/atmospherics/pipe/hide(i)
	if(level == 1 && istype(loc, /turf))
		invisibility = i ? 101 : 0
	update_icon()

/obj/machinery/atmospherics/pipe/proc/check_pressure(pressure)
	//Return 1 if parent should continue checking other pipes
	//Return null if parent should stop checking other pipes. Recall: del(src) will by default return null
	return 1

/obj/machinery/atmospherics/pipe/proc/releaseAirToTurf()
	if(air_temporary)
		var/turf/T = loc
		T.assume_air(air_temporary)
		air_update_turf()

/obj/machinery/atmospherics/pipe/return_air()
	return parent.air

/obj/machinery/atmospherics/pipe/build_network()
	if(!parent)
		parent = new /datum/pipeline()
		parent.build_pipeline(src)

/obj/machinery/atmospherics/pipe/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/device/analyzer))
		atmosanalyzer_scan(parent.air, user)
		return

	if(istype(W,/obj/item/device/pipe_painter) || istype(W,/obj/item/weapon/pipe_dispenser))
		return

	return ..()

/obj/machinery/atmospherics/pipe/returnPipenet()
	return parent

/obj/machinery/atmospherics/pipe/setPipenet(datum/pipeline/P)
	parent = P

/obj/machinery/atmospherics/pipe/Destroy()
	releaseAirToTurf()
	qdel(air_temporary)
	air_temporary = null

	var/turf/T = loc
	for(var/obj/machinery/meter/meter in T)
		if(meter.target == src)
			var/obj/item/pipe_meter/PM = new (T)
			meter.transfer_fingerprints_to(PM)
			qdel(meter)
	. = ..()

	if(parent && !qdeleted(parent))
		qdel(parent)
	parent = null

/obj/machinery/atmospherics/pipe/proc/update_node_icon()
	for(DEVICE_TYPE_LOOP)
		if(NODE_I)
			var/obj/machinery/atmospherics/N = NODE_I
			N.update_icon()

/obj/machinery/atmospherics/pipe/returnPipenets()
	. = list(parent)


code:
/*
Simple Pipe
The regular pipe you see everywhere, including bent ones.
*/

/obj/machinery/atmospherics/pipe/simple
	icon = 'icons/obj/atmospherics/pipes/simple.dmi'
	icon_state = "intact"

	name = "pipe"
	desc = "A one meter section of regular pipe"

	dir = SOUTH
	initialize_directions = SOUTH|NORTH

	device_type = BINARY

/obj/machinery/atmospherics/pipe/simple/SetInitDirections()
	if(dir in diagonals)
		initialize_directions = dir
	switch(dir)
		if(NORTH,SOUTH)
			initialize_directions = SOUTH|NORTH
		if(EAST,WEST)
			initialize_directions = EAST|WEST

/obj/machinery/atmospherics/pipe/simple/atmosinit()
	normalize_dir()
	..()

/obj/machinery/atmospherics/pipe/simple/proc/normalize_dir()
	if(dir==SOUTH)
		dir = NORTH
	else if(dir==WEST)
		dir = EAST

/obj/machinery/atmospherics/pipe/simple/update_icon()
	normalize_dir()
	..()

//Colored pipes, use these for mapping
/obj/machinery/atmospherics/pipe/simple/general
	name="pipe"

/obj/machinery/atmospherics/pipe/simple/general/visible
	level = 2

/obj/machinery/atmospherics/pipe/simple/general/hidden
	level = 1

/obj/machinery/atmospherics/pipe/simple/scrubbers
	name="scrubbers pipe"
	pipe_color=rgb(255,0,0)
	color=rgb(255,0,0)

/obj/machinery/atmospherics/pipe/simple/scrubbers/visible
	level = 2

/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden
	level = 1

/obj/machinery/atmospherics/pipe/simple/supply
	name="air supply pipe"
	pipe_color=rgb(0,0,255)
	color=rgb(0,0,255)

/obj/machinery/atmospherics/pipe/simple/supply/visible
	level = 2

/obj/machinery/atmospherics/pipe/simple/supply/hidden
	level = 1

/obj/machinery/atmospherics/pipe/simple/supplymain
	name="main air supply pipe"
	pipe_color=rgb(130,43,272)
	color=rgb(130,43,272)

/obj/machinery/atmospherics/pipe/simple/supplymain/visible
	level = 2

/obj/machinery/atmospherics/pipe/simple/supplymain/hidden
	level = 1

/obj/machinery/atmospherics/pipe/simple/yellow
	pipe_color=rgb(255,198,0)
	color=rgb(255,198,0)

/obj/machinery/atmospherics/pipe/simple/yellow/visible
	level = 2

/obj/machinery/atmospherics/pipe/simple/yellow/hidden
	level = 1

/obj/machinery/atmospherics/pipe/simple/cyan
	pipe_color=rgb(0,256,249)
	color=rgb(0,256,249)

/obj/machinery/atmospherics/pipe/simple/cyan/visible
	level = 2

/obj/machinery/atmospherics/pipe/simple/cyan/hidden
	level = 1

/obj/machinery/atmospherics/pipe/simple/green
	pipe_color=rgb(30,256,0)
	color=rgb(30,256,0)

/obj/machinery/atmospherics/pipe/simple/green/visible
	level = 2

/obj/machinery/atmospherics/pipe/simple/green/hidden
	level = 1


code:
#define CAN_MAX_RELEASE_PRESSURE (ONE_ATMOSPHERE * 10)
#define CAN_MIN_RELEASE_PRESSURE (ONE_ATMOSPHERE / 10)
#define CAN_DEFAULT_RELEASE_PRESSURE (ONE_ATMOSPHERE)

/obj/machinery/portable_atmospherics/canister
	name = "canister"
	desc = "A canister for the storage of gas."
	icon_state = "yellow"
	density = 1

	var/valve_open = FALSE
	var/obj/machinery/atmospherics/components/binary/passive_gate/pump
	var/release_log = ""

	volume = 1000
	var/filled = 0.5
	var/gas_type = ""
	var/release_pressure = ONE_ATMOSPHERE

	var/health = 100
	pressure_resistance = 7 * ONE_ATMOSPHERE
	var/temperature_resistance = 1000 + T0C

	var/update = 0
	var/static/list/label2types = list(
		"n2" = /obj/machinery/portable_atmospherics/canister/nitrogen,
		"o2" = /obj/machinery/portable_atmospherics/canister/oxygen,
		"co2" = /obj/machinery/portable_atmospherics/canister/carbon_dioxide,
		"plasma" = /obj/machinery/portable_atmospherics/canister/toxins,
		"n2o" = /obj/machinery/portable_atmospherics/canister/nitrous_oxide,
		"air" = /obj/machinery/portable_atmospherics/canister/air,
		"caution" = /obj/machinery/portable_atmospherics/canister,
	)

/obj/machinery/portable_atmospherics/canister/nitrogen
	name = "n2 canister"
	desc = "Nitrogen gas. Reportedly useful for something."
	icon_state = "red"
	gas_type = "n2"
/obj/machinery/portable_atmospherics/canister/oxygen
	name = "o2 canister"
	desc = "Oxygen. Necessary for human life."
	icon_state = "blue"
	gas_type = "o2"
/obj/machinery/portable_atmospherics/canister/carbon_dioxide
	name = "co2 canister"
	desc = "Carbon dioxide. What the fuck is carbon dioxide?"
	icon_state = "black"
	gas_type = "co2"
/obj/machinery/portable_atmospherics/canister/toxins
	name = "plasma canister"
	desc = "Plasma gas. The reason YOU are here. Highly toxic."
	icon_state = "orange"
	gas_type = "plasma"
/obj/machinery/portable_atmospherics/canister/agent_b
	name = "agent b canister"
	desc = "Oxygen Agent B. You're not quite sure what it does."
	gas_type = "agent_b"
/obj/machinery/portable_atmospherics/canister/nitrous_oxide
	name = "n2o canister"
	desc = "Nitrous oxide gas. Known to cause drowsiness."
	icon_state = "redws"
	gas_type = "n2o"
/obj/machinery/portable_atmospherics/canister/air
	name = "air canister"
	desc = "Pre-mixed air."
	icon_state = "grey"

/obj/machinery/portable_atmospherics/canister/New(loc, datum/gas_mixture/existing_mixture)
	..()
	if(existing_mixture)
		air_contents.copy_from(existing_mixture)
	else
		create_gas()

	pump = new(src, FALSE)
	pump.on = TRUE
	pump.stat = 0
	pump.build_network()

	update_icon()

/obj/machinery/portable_atmospherics/canister/Destroy()
	qdel(pump)
	pump = null
	return ..()

/obj/machinery/portable_atmospherics/canister/proc/create_gas()
	if(gas_type)
		air_contents.add_gas(gas_type)
		air_contents.gases[gas_type][MOLES] = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)

/obj/machinery/portable_atmospherics/canister/air/create_gas()
	air_contents.add_gases("o2","n2")
	air_contents.gases["o2"][MOLES] = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
	air_contents.gases["n2"][MOLES] = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)

#define HOLDING 1
#define CONNECTED 2
#define EMPTY 4
#define LOW 8
#define FULL 16
#define DANGER 32
/obj/machinery/portable_atmospherics/canister/update_icon()
	if(stat & BROKEN)
		overlays.Cut()
		icon_state = "[initial(icon_state)]-1"
		return

	var/last_update = update
	update = 0

	if(holding)
		update |= HOLDING
	if(connected_port)
		update |= CONNECTED
	var/pressure = air_contents.return_pressure()
	if(pressure < 10)
		update |= EMPTY
	else if(pressure < ONE_ATMOSPHERE)
		update |= LOW
	else if(pressure < 15 * ONE_ATMOSPHERE)
		update |= FULL
	else
		update |= DANGER

	if(update == last_update)
		return

	overlays.Cut()
	if(update & HOLDING)
		overlays += "can-open"
	if(update & CONNECTED)
		overlays += "can-connector"
	if(update & EMPTY)
		overlays += "can-o0"
	else if(update & LOW)
		overlays += "can-o1"
	else if(update & FULL)
		overlays += "can-o2"
	else if(update & DANGER)
		overlays += "can-o3"
#undef HOLDING
#undef CONNECTED
#undef EMPTY
#undef LOW
#undef FULL
#undef DANGER

/obj/machinery/portable_atmospherics/canister/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
	if(exposed_temperature > temperature_resistance)
		health -= 5
		healthcheck()

/obj/machinery/portable_atmospherics/canister/proc/healthcheck()
	if(stat & BROKEN)
		return

	if(health <= 10)
		disconnect()
		var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles())
		var/turf/T = get_turf(src)
		T.assume_air(expelled_gas)
		air_update_turf()

		stat |= BROKEN
		density = 0
		playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3)
		update_icon()
		investigate_log("was destroyed.", "atmos")

		if(holding)
			holding.loc = T
			holding = null

/obj/machinery/portable_atmospherics/canister/process_atmos()
	..()
	if(stat & BROKEN)
		return PROCESS_KILL
	if(!valve_open)
		pump.AIR1 = null
		pump.AIR2 = null
		return

	var/turf/T = get_turf(src)
	pump.AIR1 = air_contents
	pump.AIR2 = holding ? holding.air_contents : T.return_air()
	pump.target_pressure = release_pressure

	pump.process_atmos() // Pump gas.
	if(!holding)
		air_update_turf() // Update the environment if needed.
	update_icon()

/obj/machinery/portable_atmospherics/canister/blob_act()
	health = 0
	healthcheck()

/obj/machinery/portable_atmospherics/canister/bullet_act(obj/item/projectile/P)
	if((P.damage_type == BRUTE || P.damage_type == BURN))
		if(P.damage)
			health -= round(P.damage / 2)
			healthcheck()
	..()

/obj/machinery/portable_atmospherics/canister/ex_act(severity, target)
	switch(severity)
		if(1)
			if((stat & BROKEN) || prob(30))
				qdel(src)
				return
			else
				health = 0
		if(2)
			if(stat & BROKEN)
				qdel(src)
				return
			else
				health -= rand(40, 100)
		if(3)
			health -= rand(15, 40)
	healthcheck()

/obj/machinery/portable_atmospherics/canister/attackby(obj/item/weapon/W, mob/user, params)
	if(!istype(W, /obj/item/weapon/wrench) && !istype(W, /obj/item/weapon/tank) && !istype(W, /obj/item/device/analyzer) && !istype(W, /obj/item/device/pda))
		investigate_log("was smacked with \a [W] by [key_name(user)].", "atmos")
		health -= W.force
		add_fingerprint(user)
		healthcheck()
	else
		..()

/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
															datum/tgui/master_ui = null, datum/ui_state/state = physical_state)
	ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
	if(!ui)
		ui = new(user, src, ui_key, "canister", name, 420, 405, master_ui, state)
		ui.open()

/obj/machinery/portable_atmospherics/canister/ui_data()
	var/data = list()
	data["portConnected"] = connected_port ? 1 : 0
	data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
	data["releasePressure"] = round(release_pressure ? release_pressure : 0)
	data["defaultReleasePressure"] = round(CAN_DEFAULT_RELEASE_PRESSURE)
	data["minReleasePressure"] = round(CAN_MIN_RELEASE_PRESSURE)
	data["maxReleasePressure"] = round(CAN_MAX_RELEASE_PRESSURE)
	data["valveOpen"] = valve_open ? 1 : 0

	data["hasHoldingTank"] = holding ? 1 : 0
	if (holding)
		data["holdingTank"] = list()
		data["holdingTank"]["name"] = holding.name
		data["holdingTank"]["tankPressure"] = round(holding.air_contents.return_pressure())
	return data

/obj/machinery/portable_atmospherics/canister/ui_act(action, params)
	if(..())
		return
	switch(action)
		if("relabel")
			var/label = input("New canister label:", name) as null|anything in label2types
			if(label && !..())
				var/newtype = label2types[label]
				if(newtype)
					var/obj/machinery/portable_atmospherics/canister/replacement = new newtype(loc, air_contents)
					replacement.interact(usr)
					qdel(src)
		if("pressure")
			var/pressure = params["pressure"]
			if(pressure == "reset")
				pressure = CAN_DEFAULT_RELEASE_PRESSURE
				. = TRUE
			else if(pressure == "min")
				pressure = CAN_MIN_RELEASE_PRESSURE
				. = TRUE
			else if(pressure == "max")
				pressure = CAN_MAX_RELEASE_PRESSURE
				. = TRUE
			else if(pressure == "input")
				pressure = input("New release pressure ([CAN_MIN_RELEASE_PRESSURE]-[CAN_MAX_RELEASE_PRESSURE] kPa):", name, release_pressure) as num|null
				if(!isnull(pressure) && !..())
					. = TRUE
			else if(text2num(pressure) != null)
				pressure = text2num(pressure)
				. = TRUE
			if(.)
				release_pressure = Clamp(round(pressure), CAN_MIN_RELEASE_PRESSURE, CAN_MAX_RELEASE_PRESSURE)
				investigate_log("was set to [release_pressure] kPa by [key_name(usr)].", "atmos")
		if("valve")
			var/logmsg
			valve_open = !valve_open
			if(valve_open)
				logmsg = "Valve was opened by [key_name(usr)], starting a transfer into \the [holding || "air"].
"
				if(!holding)
					var/plasma = air_contents.gases["plasma"]
					var/n2o = air_contents.gases["n2o"]
					if(n2o || plasma)
						message_admins("[key_name_admin(usr)] (?) (FLW) opened a canister that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""]! (JMP)")
						log_admin("[key_name(usr)] opened a canister that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [x], [y], [z]")
			else
				logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into \the [holding || "air"].
"
			investigate_log(logmsg, "atmos")
			release_log += logmsg
			. = TRUE
		if("eject")
			if(holding)
				if(valve_open)
					investigate_log("[key_name(usr)] removed the [holding], leaving the valve open and transfering into the air
", "atmos")
				holding.loc = get_turf(src)
				holding = null
				. = TRUE
	update_icon()


code:
/obj/machinery/portable_atmospherics
	name = "portable_atmospherics"
	icon = 'icons/obj/atmos.dmi'
	use_power = 0

	var/datum/gas_mixture/air_contents
	var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port
	var/obj/item/weapon/tank/holding

	var/volume = 0

	var/maximum_pressure = 90 * ONE_ATMOSPHERE

/obj/machinery/portable_atmospherics/New()
	..()
	SSair.atmos_machinery += src

	air_contents = new
	air_contents.volume = volume
	air_contents.temperature = T20C

	return 1

/obj/machinery/portable_atmospherics/Destroy()
	SSair.atmos_machinery -= src

	disconnect()
	qdel(air_contents)
	air_contents = null

	return ..()

/obj/machinery/portable_atmospherics/process_atmos()
	if(!connected_port) // Pipe network handles reactions if connected.
		air_contents.react()
	else
		update_icon()

/obj/machinery/portable_atmospherics/return_air()
	return air_contents

/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port)
	//Make sure not already connected to something else
	if(connected_port || !new_port || new_port.connected_device)
		return 0

	//Make sure are close enough for a valid connection
	if(new_port.loc != loc)
		return 0

	//Perform the connection
	connected_port = new_port
	connected_port.connected_device = src
	var/datum/pipeline/connected_port_parent = connected_port.PARENT1
	connected_port_parent.reconcile_air()

	anchored = 1 //Prevent movement
	return 1

/obj/machinery/portable_atmospherics/proc/disconnect()
	if(!connected_port)
		return 0
	anchored = 0
	connected_port.connected_device = null
	connected_port = null
	return 1

/obj/machinery/portable_atmospherics/portableConnectorReturnAir()
	return air_contents

/obj/machinery/portable_atmospherics/attackby(obj/item/weapon/W, mob/user, params)
	if(istype(W, /obj/item/weapon/tank) && !(stat & BROKEN))
		var/obj/item/weapon/tank/T = W
		if(holding || !user.drop_item())
			return
		T.loc = src
		holding = T
		update_icon()
	else if(istype(W, /obj/item/weapon/wrench) && !(stat & BROKEN))
		if(connected_port)
			disconnect()
			playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
			user.visible_message( \
				"[user] disconnects [src].", \
				"You unfasten [src] from the port.", \
				"You hear a ratchet.")
			update_icon()
			return
		else
			var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc
			if(!possible_port)
				user << "Nothing happens."
				return
			if(!connect(possible_port))
				user << "[name] failed to connect to the port."
				return
			playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
			user.visible_message( \
				"[user] connects [src].", \
				"You fasten [src] to the port.", \
				"You hear a ratchet.")
			update_icon()
	else if((istype(W, /obj/item/device/analyzer)) && Adjacent(user))
		atmosanalyzer_scan(air_contents, user)
	else
		..()


code:
#define PUMP_OUT "out"
#define PUMP_IN "in"
#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 10)
#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10)
#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE)

/obj/machinery/portable_atmospherics/pump
	name = "portable air pump"
	icon_state = "psiphon:0"
	density = 1

	var/on = FALSE
	var/direction = PUMP_OUT
	var/obj/machinery/atmospherics/components/binary/pump/pump

	volume = 1000

/obj/machinery/portable_atmospherics/pump/New()
	..()
	pump = new(src, FALSE)
	pump.on = TRUE
	pump.stat = 0
	pump.build_network()

/obj/machinery/portable_atmospherics/pump/Destroy()
	var/turf/T = get_turf(src)
	T.assume_air(air_contents)
	air_update_turf()
	qdel(pump)
	pump = null
	return ..()

/obj/machinery/portable_atmospherics/pump/update_icon()
	icon_state = "psiphon:[on]"

	overlays.Cut()
	if(holding)
		overlays += "siphon-open"
	if(connected_port)
		overlays += "siphon-connector"

/obj/machinery/portable_atmospherics/pump/process_atmos()
	..()
	if(!on)
		pump.AIR1 = null
		pump.AIR2 = null
		return

	var/turf/T = get_turf(src)
	if(direction == PUMP_OUT) // Hook up the internal pump.
		pump.AIR1 = holding ? holding.air_contents : air_contents
		pump.AIR2 = holding ? air_contents : T.return_air()
	else
		pump.AIR1 = holding ? air_contents : T.return_air()
		pump.AIR2 = holding ? holding.air_contents : air_contents

	pump.process_atmos() // Pump gas.
	if(!holding)
		air_update_turf() // Update the environment if needed.

/obj/machinery/portable_atmospherics/pump/emp_act(severity)
	if(is_operational())
		if(prob(50 / severity))
			on = !on
		if(prob(100 / severity))
			direction = PUMP_OUT
		pump.target_pressure = rand(0, 100 * ONE_ATMOSPHERE)
		update_icon()
	..()

/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
														datum/tgui/master_ui = null, datum/ui_state/state = physical_state)
	ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
	if(!ui)
		ui = new(user, src, ui_key, "portable_pump", name, 420, 415, master_ui, state)
		ui.open()

/obj/machinery/portable_atmospherics/pump/ui_data()
	var/data = list()
	data["on"] = on
	data["direction"] = direction
	data["connected"] = connected_port ? 1 : 0
	data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
	data["target_pressure"] = round(pump.target_pressure ? pump.target_pressure : 0)
	data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE)
	data["min_pressure"] = round(PUMP_MIN_PRESSURE)
	data["max_pressure"] = round(PUMP_MAX_PRESSURE)

	if(holding)
		data["holding"] = list()
		data["holding"]["name"] = holding.name
		data["holding"]["pressure"] = round(holding.air_contents.return_pressure())
	return data

/obj/machinery/portable_atmospherics/pump/ui_act(action, params)
	if(..())
		return
	switch(action)
		if("power")
			on = !on
			if(on && !holding)
				var/plasma = air_contents.gases["plasma"]
				var/n2o = air_contents.gases["n2o"]
				if(n2o || plasma)
					message_admins("[key_name_admin(usr)] (?) (FLW) turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""]! (JMP)")
					log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [x], [y], [z]")
			. = TRUE
		if("direction")
			if(direction == PUMP_OUT)
				direction = PUMP_IN
			else
				direction = PUMP_OUT
			. = TRUE
		if("pressure")
			var/pressure = params["pressure"]
			if(pressure == "reset")
				pressure = PUMP_DEFAULT_PRESSURE
				. = TRUE
			else if(pressure == "min")
				pressure = PUMP_MIN_PRESSURE
				. = TRUE
			else if(pressure == "max")
				pressure = PUMP_MAX_PRESSURE
				. = TRUE
			else if(pressure == "input")
				pressure = input("New release pressure ([PUMP_MIN_PRESSURE]-[PUMP_MAX_PRESSURE] kPa):", name, pump.target_pressure) as num|null
				if(!isnull(pressure) && !..())
					. = TRUE
			else if(text2num(pressure) != null)
				pressure = text2num(pressure)
				. = TRUE
			if(.)
				pump.target_pressure = Clamp(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE)
				investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", "atmos")
		if("eject")
			if(holding)
				holding.loc = get_turf(src)
				holding = null
				. = TRUE
	update_icon()


code:
/obj/machinery/portable_atmospherics/scrubber
	name = "portable air scrubber"
	icon_state = "pscrubber:0"
	density = 1

	var/on = FALSE
	var/volume_rate = 1000
	volume = 1000

	var/list/scrubbing = list("plasma", "co2", "n2o", "agent_b")

/obj/machinery/portable_atmospherics/scrubber/Destroy()
	var/turf/T = get_turf(src)
	T.assume_air(air_contents)
	air_update_turf()
	return ..()

/obj/machinery/portable_atmospherics/scrubber/update_icon()
	icon_state = "pscrubber:[on]"

	overlays.Cut()
	if(holding)
		overlays += "scrubber-open"
	if(connected_port)
		overlays += "scrubber-connector"

/obj/machinery/portable_atmospherics/scrubber/process_atmos()
	..()
	if(!on)
		return

	if(holding)
		scrub(holding.air_contents)
	else
		var/turf/T = get_turf(src)
		scrub(T.return_air())

/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/datum/gas_mixture/mixture)
	var/transfer_moles = min(1, volume_rate / mixture.volume) * mixture.total_moles()

	var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter.
	var/datum/gas_mixture/filtered = new
	if(!filtering)
		return

	filtered.temperature = filtering.temperature
	for(var/gas in filtering.gases & scrubbing)
		filtered.add_gas(gas)
		filtered.gases[gas][MOLES] = filtering.gases[gas][MOLES] // Shuffle the "bad" gasses to the filtered mixture.
		filtering.gases[gas][MOLES] = 0
	filtering.garbage_collect() // Now that the gasses are set to 0, clean up the mixture.

	air_contents.merge(filtered) // Store filtered out gasses.
	mixture.merge(filtering) // Returned the cleaned gas.
	if(!holding)
		air_update_turf()

/obj/machinery/portable_atmospherics/scrubber/emp_act(severity)
	if(is_operational())
		if(prob(50 / severity))
			on = !on
		update_icon()
	..()

/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
														datum/tgui/master_ui = null, datum/ui_state/state = physical_state)
	ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
	if(!ui)
		ui = new(user, src, ui_key, "portable_scrubber", name, 420, 335, master_ui, state)
		ui.open()

/obj/machinery/portable_atmospherics/scrubber/ui_data()
	var/data = list()
	data["on"] = on
	data["connected"] = connected_port ? 1 : 0
	data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)

	if(holding)
		data["holding"] = list()
		data["holding"]["name"] = holding.name
		data["holding"]["pressure"] = round(holding.air_contents.return_pressure())
	return data

/obj/machinery/portable_atmospherics/scrubber/ui_act(action, params)
	if(..())
		return
	switch(action)
		if("power")
			on = !on
			. = TRUE
		if("eject")
			if(holding)
				holding.loc = get_turf(src)
				holding = null
				. = TRUE
	update_icon()

/obj/machinery/portable_atmospherics/scrubber/huge
	name = "huge air scrubber"
	icon_state = "scrubber:0"
	anchored = TRUE
	active_power_usage = 500
	idle_power_usage = 10

	volume_rate = 1500
	volume = 50000

	var/movable = FALSE

/obj/machinery/portable_atmospherics/scrubber/huge/movable
	movable = TRUE

/obj/machinery/portable_atmospherics/scrubber/huge/update_icon()
	icon_state = "scrubber:[on]"

/obj/machinery/portable_atmospherics/scrubber/huge/process_atmos()
	if((!anchored && !movable) || !is_operational())
		on = FALSE
		update_icon()
	use_power = 1 + on
	if(!on)
		return

	..()
	if(!holding)
		var/turf/T = get_turf(src)
		for(var/turf/AT in T.GetAtmosAdjacentTurfs(alldir = TRUE))
			scrub(AT.return_air())

/obj/machinery/portable_atmospherics/scrubber/huge/attackby(obj/item/weapon/W, mob/user)
	if(default_unfasten_wrench(user, W))
		if(!movable)
			on = FALSE
	else
		..()


code:
#define ARTILLERY_RELOAD_TIME 60
#define EXPLOSION_SIZE 3

/obj/machinery/artillerycontrol
	var/reload = ARTILLERY_RELOAD_TIME
	var/explosionsize = EXPLOSION_SIZE
	name = "bluespace artillery control"
	icon_state = "control_boxp1"
	icon = 'icons/obj/machines/particle_accelerator.dmi'
	density = 1
	anchored = 1

/obj/machinery/artillerycontrol/process()
	if(src.reload<ARTILLERY_RELOAD_TIME)
		src.reload++

/obj/structure/artilleryplaceholder
	name = "artillery"
	icon = 'icons/obj/machines/artillery.dmi'
	anchored = 1
	density = 1

/obj/structure/artilleryplaceholder/decorative
	density = 0

/obj/machinery/artillerycontrol/attack_hand(mob/user)
	user.set_machine(src)
	var/dat = "Bluespace Artillery Control:
"
	dat += "Locked on
"
	dat += "Charge progress: [reload]/[ARTILLERY_RELOAD_TIME]:
"
	dat += "Open Fire
"
	dat += "Deployment of weapon authorized by
Nanotrasen Naval Command

Remember, friendly fire is grounds for termination of your contract and life.
"
	user << browse(dat, "window=scroll")
	onclose(user, "scroll")
	return

/obj/machinery/artillerycontrol/Topic(href, href_list)
	if(..())
		return
	var/A
	A = input("Area to jump bombard", "Open Fire", A) in teleportlocs
	var/area/thearea = teleportlocs[A]
	if (usr.stat || usr.restrained()) return
	if(src.reload < ARTILLERY_RELOAD_TIME) return
	if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon)))
		priority_announce("Bluespace artillery fire detected. Brace for impact.")
		message_admins("[key_name_admin(usr)] has launched an artillery strike.")
		var/list/L = list()
		for(var/turf/T in get_area_turfs(thearea.type))
			L+=T
		var/loc = pick(L)
		explosion(loc,explosionsize,explosionsize*2,explosionsize*4)
		reload = 0

/*/mob/proc/openfire()
	var/A
	A = input("Area to jump bombard", "Open Fire", A) in teleportlocs
	var/area/thearea = teleportlocs[A]
	priority_announce("Bluespace artillery fire detected. Brace for impact.")
	spawn(30)
	var/list/L = list()

	for(var/turf/T in get_area_turfs(thearea.type))
		L+=T
	var/loc = pick(L)
	explosion(loc,2,5,11)*/


code:
#define WHITE_TEAM "white"
#define RED_TEAM "red"
#define BLUE_TEAM "blue"
#define FLAG_RETURN_TIME 200 // 20 seconds
#define INSTAGIB_RESPAWN 50 //5 seconds
#define DEFAULT_RESPAWN 150 //15 seconds

/obj/item/weapon/twohanded/required/ctf
	name = "banner"
	icon = 'icons/obj/items.dmi'
	icon_state = "banner"
	item_state = "banner"
	desc = "A banner with Nanotrasen's logo on it."
	slowdown = 2
	throw_speed = 0
	throw_range = 1
	force = 200
	armour_penetration = 100
	anchored = TRUE
	flags = HANDSLOW
	var/team = WHITE_TEAM
	var/reset_cooldown = 0
	var/obj/effect/landmark/reset

/obj/item/weapon/twohanded/required/ctf/New()
	if(!reset)
		reset = new /obj/effect/landmark(get_turf(src))

/obj/item/weapon/twohanded/required/ctf/initialize()
	if(!reset)
		reset = new /obj/effect/landmark(get_turf(src))

/obj/item/weapon/twohanded/required/ctf/process()
	if(world.time > reset_cooldown)
		src.loc = get_turf(src.reset)
		for(var/mob/M in player_list)
			var/area/mob_area = get_area(M)
			if(istype(mob_area, /area/ctf))
				M << "\The [src] has been returned to base!"
		SSobj.processing.Remove(src)

/obj/item/weapon/twohanded/required/ctf/attack_hand(mob/living/user)
	if (!user)
		return
	if(team in user.faction)
		user << "You can't move your own flag!"
		return
	if(loc == user)
		if(!user.unEquip(src))
			return
	anchored = FALSE
	pickup(user)
	if(!user.put_in_active_hand(src))
		dropped(user)
		return
	for(var/mob/M in player_list)
		var/area/mob_area = get_area(M)
		if(istype(mob_area, /area/ctf))
			M << "\The [src] has been taken!"
	SSobj.processing.Remove(src)

/obj/item/weapon/twohanded/required/ctf/dropped(mob/user)
	..()
	reset_cooldown = world.time + 200 //20 seconds
	SSobj.processing |= src
	for(var/mob/M in player_list)
		var/area/mob_area = get_area(M)
		if(istype(mob_area, /area/ctf))
			M << "\The [src] has been dropped!"
	anchored = TRUE

/obj/item/weapon/twohanded/required/ctf/red
	name = "red flag"
	icon_state = "banner-red"
	item_state = "banner-red"
	desc = "A red banner, used to play capture the flag."
	team = RED_TEAM

/obj/item/weapon/twohanded/required/ctf/blue
	name = "blue flag"
	icon_state = "banner-blue"
	item_state = "banner-blue"
	desc = "A blue banner, used to play capture the flag."
	team = BLUE_TEAM

/obj/machinery/capture_the_flag
	name = "CTF Controller"
	desc = "Used for running friendly games of capture the flag."
	icon = 'icons/obj/device.dmi'
	icon_state = "syndbeacon"
	anchored = 1
	var/team = WHITE_TEAM
	//Capture the Flag scoring
	var/points = 0
	var/points_to_win = 3
	var/respawn_cooldown = DEFAULT_RESPAWN
	//Capture Point/King of the Hill scoring
	var/control_points = 0
	var/control_points_to_win = 180
	var/list/team_members = list()
	var/ctf_enabled = FALSE
	var/ctf_gear = /datum/outfit/ctf
	var/instagib_gear = /datum/outfit/ctf/instagib

/obj/machinery/capture_the_flag/New()
	..()
	poi_list |= src

/obj/machinery/capture_the_flag/Destroy()
	poi_list.Remove(src)
	..()

/obj/machinery/capture_the_flag/red
	name = "Red CTF Controller"
	icon_state = "syndbeacon"
	team = RED_TEAM
	ctf_gear = /datum/outfit/ctf/red
	instagib_gear = /datum/outfit/ctf/red/instagib

/obj/machinery/capture_the_flag/blue
	name = "Blue CTF Controller"
	icon_state = "bluebeacon"
	team = BLUE_TEAM
	ctf_gear = /datum/outfit/ctf/blue
	instagib_gear = /datum/outfit/ctf/blue/instagib

/obj/machinery/capture_the_flag/attack_ghost(mob/user)
	if(ctf_enabled == FALSE)
		return
	if(ticker.current_state != GAME_STATE_PLAYING)
		return
	if(user.ckey in team_members)
		if(user.mind.current && user.mind.current.timeofdeath + respawn_cooldown > world.time)
			user << "It must be more than [respawn_cooldown/10] seconds from your last death to respawn!"
			return
		var/client/new_team_member = user.client
		dust_old(user)
		spawn_team_member(new_team_member)
		return

	for(var/obj/machinery/capture_the_flag/CTF in machines)
		if(CTF == src || CTF.ctf_enabled == FALSE)
			continue
		if(user.ckey in CTF.team_members)
			user << "No switching teams while the round is going!"
			return
		if(CTF.team_members.len < src.team_members.len)
			user << "[src.team] has more team members than [CTF.team]. Try joining [CTF.team] to even things up."
			return
	team_members |= user.ckey
	var/client/new_team_member = user.client
	dust_old(user)
	spawn_team_member(new_team_member)

/obj/machinery/capture_the_flag/proc/dust_old(mob/user)
	if(user.mind && user.mind.current && user.mind.current.z == src.z)
		new /obj/item/ammo_box/magazine/recharge/ctf (get_turf(user.mind.current))
		new /obj/item/ammo_box/magazine/recharge/ctf (get_turf(user.mind.current))
		user.mind.current.dust()

/obj/machinery/capture_the_flag/proc/spawn_team_member(client/new_team_member)
	var/mob/living/carbon/human/M = new/mob/living/carbon/human(get_turf(src))
	new_team_member.prefs.copy_to(M)
	M.key = new_team_member.key
	M.faction += team
	M.equipOutfit(ctf_gear)

/obj/machinery/capture_the_flag/attackby(obj/item/I, mob/user, params)
	if(istype(I, /obj/item/weapon/twohanded/required/ctf))
		var/obj/item/weapon/twohanded/required/ctf/flag = I
		if(flag.team != src.team)
			user.unEquip(flag)
			flag.loc = get_turf(flag.reset)
			points++
			for(var/mob/M in player_list)
				var/area/mob_area = get_area(M)
				if(istype(mob_area, /area/ctf))
					M << "[user.real_name] has captured \the [flag], scoring a point for [team] team! They now have [points]/[points_to_win] points!"
		if(points >= points_to_win)
			victory()

/obj/machinery/capture_the_flag/proc/victory()
	for(var/mob/M in mob_list)
		var/area/mob_area = get_area(M)
		if(istype(mob_area, /area/ctf))
			M << "[team] team wins!"
			M << "The game has been reset! Teams have been cleared. The machines will be active again in 30 seconds."
			for(var/obj/item/weapon/twohanded/required/ctf/W in M)
				M.unEquip(W)
			M.dust()
	for(var/obj/machinery/control_point/control in machines)
		control.icon_state = "dominator"
		control.controlling = null
	for(var/obj/machinery/capture_the_flag/CTF in machines)
		if(CTF.ctf_enabled == TRUE)
			CTF.points = 0
			CTF.control_points = 0
			CTF.ctf_enabled = FALSE
			CTF.team_members = list()
			spawn(300)
				CTF.ctf_enabled = TRUE

/obj/machinery/capture_the_flag/proc/instagib_mode()
	for(var/obj/machinery/capture_the_flag/CTF in machines)
		if(CTF.ctf_enabled == TRUE)
			CTF.ctf_gear = CTF.instagib_gear
			CTF.respawn_cooldown = INSTAGIB_RESPAWN

/obj/item/weapon/gun/projectile/automatic/pistol/deagle/CTF
	desc = "This looks like it could really hurt in melee."
	force = 75

/obj/item/weapon/gun/projectile/automatic/laser/ctf
	mag_type = /obj/item/ammo_box/magazine/recharge/ctf
	desc = "This looks like it could really hurt in melee."
	force = 50

/obj/item/ammo_box/magazine/recharge/ctf
	ammo_type = /obj/item/ammo_casing/caseless/laser/ctf

/obj/item/ammo_casing/caseless/laser/ctf
 	projectile_type = /obj/item/projectile/beam/ctf

/obj/item/projectile/beam/ctf
	damage = 150

/datum/outfit/ctf
	name = "CTF"
	/obj/item/device/radio/headset
	uniform = /obj/item/clothing/under/syndicate
	suit = /obj/item/clothing/suit/space/hardsuit/shielded/ctf
	shoes = /obj/item/clothing/shoes/combat
	gloves = /obj/item/clothing/gloves/combat
	id = /obj/item/weapon/card/id/syndicate
	belt = /obj/item/weapon/gun/projectile/automatic/pistol/deagle/CTF
	l_pocket = /obj/item/ammo_box/magazine/recharge/ctf
	r_pocket = /obj/item/ammo_box/magazine/recharge/ctf
	r_hand = /obj/item/weapon/gun/projectile/automatic/laser/ctf

/datum/outfit/ctf/instagib
	r_hand = /obj/item/weapon/gun/energy/laser/instakill
	shoes = /obj/item/clothing/shoes/jackboots/fast

/datum/outfit/ctf/red
	ears = /obj/item/device/radio/headset/syndicate/alt
	suit = /obj/item/clothing/suit/space/hardsuit/shielded/ctf/red

/datum/outfit/ctf/red/instagib
	r_hand = /obj/item/weapon/gun/energy/laser/instakill/red
	shoes = /obj/item/clothing/shoes/jackboots/fast

/datum/outfit/ctf/blue
	ears = /obj/item/device/radio/headset/headset_cent/commander
	suit = /obj/item/clothing/suit/space/hardsuit/shielded/ctf/blue

/datum/outfit/ctf/blue/instagib
	r_hand = /obj/item/weapon/gun/energy/laser/instakill/blue
	shoes = /obj/item/clothing/shoes/jackboots/fast

/datum/outfit/ctf/red/post_equip(mob/living/carbon/human/H)
	var/obj/item/device/radio/R = H.ears
	R.set_frequency(SYND_FREQ)
	R.freqlock = 1

/datum/outfit/ctf/blue/post_equip(mob/living/carbon/human/H)
	var/obj/item/device/radio/R = H.ears
	R.set_frequency(CENTCOM_FREQ)
	R.freqlock = 1

/obj/structure/divine/trap/ctf
	name = "Spawn protection"
	desc = "Stay outta the enemy spawn!"
	icon_state = "trap"
	health = INFINITY
	maxhealth = INFINITY
	var/team = WHITE_TEAM
	time_between_triggers = 1
	alpha = 255

/obj/structure/divine/trap/examine(mob/user)
	return

/obj/structure/divine/trap/ctf/trap_effect(mob/living/L)
	if(!(src.team in L.faction))
		L << "Stay out of the enemy spawn!"
		L.dust()

/obj/structure/divine/trap/ctf/red
	team = RED_TEAM
	icon_state = "trap-fire"

/obj/structure/divine/trap/ctf/blue
	team = BLUE_TEAM
	icon_state = "trap-frost"

/obj/structure/barricade/security/CTF
	health = INFINITY

//Areas

/area/ctf
	name = "Capture the Flag"
	icon_state = "yellow"
	requires_power = 0
	has_gravity = 1

/area/ctf/control_room
	name = "Control Room A"

/area/ctf/control_room2
	name = "Control Room B"

/area/ctf/central
	name = "Central"

/area/ctf/main_hall
	name = "Main Hall A"

/area/ctf/main_hall2
	name = "Main Hall B"

/area/ctf/corridor
	name = "Corridor A"

/area/ctf/corridor2
	name = "Corridor B"

/area/ctf/flag_room
	name = "Flag Room A"

/area/ctf/flag_room2
	name = "Flag Room B"

//Control Point

/obj/machinery/control_point
	name = "control point"
	desc = "You should capture this."
	icon = 'icons/obj/machines/dominator.dmi'
	icon_state = "dominator"
	anchored = 1
	var/obj/machinery/capture_the_flag/controlling
	var/team = "none"
	var/point_rate = 1

/obj/machinery/control_point/process()
	if(controlling)
		controlling.control_points += point_rate
		if(controlling.control_points >= controlling.control_points_to_win)
			controlling.victory()

/obj/machinery/control_point/attackby(mob/user, params)
	capture(user)

/obj/machinery/control_point/attack_hand(mob/user)
	capture(user)

/obj/machinery/control_point/proc/capture(mob/user)
	if(do_after(user, 30, target = src))
		for(var/obj/machinery/capture_the_flag/CTF in machines)
			if(CTF.ctf_enabled && (user.ckey in CTF.team_members))
				controlling = CTF
				icon_state = "dominator-[CTF.team]"
				for(var/mob/M in player_list)
					var/area/mob_area = get_area(M)
					if(istype(mob_area, /area/ctf))
						M << "[user.real_name] has captured \the [src], claiming it for [CTF.team]! Go take it back!"
				break


code:
//If someone can do this in a neater way, be my guest-Kor

//To do: Allow corpses to appear mangled, bloody, etc. Allow customizing the bodies appearance (they're all bald and white right now).

/obj/effect/mob_spawn
	name = "Unknown"
	var/mob_type = null
	var/mob_name = ""
	var/mob_gender = MALE
	var/death = TRUE //Kill the mob
	var/roundstart = TRUE //fires on initialize
	var/instant = FALSE	//fires on New
	var/flavour_text = "The mapper forgot to set this!"
	var/faction = null
	var/objectives = null
	var/brute_damage = 0
	var/oxy_damage = 0
	density = 1
	anchored = 1

/obj/effect/mob_spawn/attack_ghost(mob/user)
	if(ticker.current_state != GAME_STATE_PLAYING || !loc)
		return
	var/ghost_role = alert("Become [mob_name]? (Warning, You can no longer be cloned!)",,"Yes","No")
	if(ghost_role == "No" || !loc)
		return
	log_game("[user.ckey] became [mob_name]")
	create(ckey = user.ckey)

/obj/effect/mob_spawn/spawn_atom_to_world()
	//We no longer need to spawn mobs, deregister ourself
	SSobj.atom_spawners -= src
	if(roundstart)
		create()
	else
		poi_list |= src

/obj/effect/mob_spawn/New()
	..()
	if(roundstart)
		//Add to the atom spawners register for roundstart atom spawning
		SSobj.atom_spawners += src

	if(instant)
		create()
	else
		poi_list |= src

/obj/effect/mob_spawn/Destroy()
	poi_list.Remove(src)
	..()

/obj/effect/mob_spawn/proc/special(mob/M)
	return

/obj/effect/mob_spawn/proc/equip(mob/M)
	return

/obj/effect/mob_spawn/proc/create(ckey)
	var/mob/living/M = new mob_type(loc) //living mobs only
	M.real_name = mob_name ? mob_name : M.name
	M.gender = mob_gender
	if(faction)
		M.faction = list(faction)
	if(death)
		M.death(1) //Kills the new mob

	M.adjustOxyLoss(oxy_damage)
	M.adjustBruteLoss(brute_damage)
	equip(M)

	if(ckey)
		M.ckey = ckey
		M << "[flavour_text]"
		if(objectives)
			var/datum/mind/MM = M.mind
			for(var/objective in objectives)
				MM.objectives += new/datum/objective(objective)
		special(M)
	qdel(src)

// Base version - place these on maps/templates.
/obj/effect/mob_spawn/human
	mob_type = /mob/living/carbon/human
	//Human specific stuff.
	var/mob_species = null //Set to make them a mutant race such as lizard or skeleton. Uses the datum typepath instead of the ID.
	var/uniform = null //Set this to an object path to have the slot filled with said object on the corpse.
	var/suit = null
	var/shoes = null
	var/gloves = null
	var/radio = null
	var/glasses = null
	var/mask = null
	var/helmet = null
	var/belt = null
	var/pocket1 = null
	var/pocket2 = null
	var/back = null
	var/has_id = 0     //Just set to 1 if you want them to have an ID
	var/id_job = null // Needs to be in quotes, such as "Clown" or "Chef." This just determines what the ID reads as, not their access
	var/id_access = null //This is for access. See access.dm for which jobs give what access. Again, put in quotes. Use "Captain" if you want it to be all access.
	var/id_icon = null //For setting it to be a gold, silver, centcom etc ID
	var/husk = null
	var/outfit_type = null // Will start with this if exists then apply specific slots
	var/list/implants = list()

/obj/effect/mob_spawn/human/equip(mob/living/carbon/human/H)
	if(mob_species)
		H.set_species(mob_species)
	if(husk)
		H.Drain()
	if(outfit_type)
		H.equipOutfit(outfit_type)
	if(uniform)
		H.equip_to_slot_or_del(new uniform(H), slot_w_uniform)
	if(suit)
		H.equip_to_slot_or_del(new suit(H), slot_wear_suit)
	if(shoes)
		H.equip_to_slot_or_del(new shoes(H), slot_shoes)
	if(gloves)
		H.equip_to_slot_or_del(new gloves(H), slot_gloves)
	if(radio)
		H.equip_to_slot_or_del(new radio(H), slot_ears)
	if(glasses)
		H.equip_to_slot_or_del(new glasses(H), slot_glasses)
	if(mask)
		H.equip_to_slot_or_del(new mask(H), slot_wear_mask)
	if(helmet)
		H.equip_to_slot_or_del(new helmet(H), slot_head)
	if(belt)
		H.equip_to_slot_or_del(new belt(H), slot_belt)
	if(pocket1)
		H.equip_to_slot_or_del(new pocket1(H), slot_r_store)
	if(pocket2)
		H.equip_to_slot_or_del(new pocket2(H), slot_l_store)
	if(back)
		H.equip_to_slot_or_del(new back(H), slot_back)
	if(has_id)
		var/obj/item/weapon/card/id/W = new(H)
		if(id_icon)
			W.icon_state = id_icon
		if(id_access)
			var/datum/job/jobdatum
			for(var/jobtype in typesof(/datum/job))
				var/datum/job/J = new jobtype
				if(J.title == id_access)
					jobdatum = J
					break
			if(jobdatum)
				W.access = jobdatum.get_access()
			else
				W.access = list()
		if(id_job)
			W.assignment = id_job
		W.registered_name = H.real_name
		W.update_label()
		H.equip_to_slot_or_del(W, slot_wear_id)

	for(var/I in implants)
		var/obj/item/weapon/implant/X = new I
		X.implant(H)

//Instant version - use when spawning corpses during runtime
/obj/effect/mob_spawn/human/corpse
	roundstart = FALSE
	instant = TRUE

/obj/effect/mob_spawn/human/corpse/damaged
	brute_damage = 1000

/obj/effect/mob_spawn/human/alive
	icon = 'icons/obj/Cryogenic2.dmi'
	icon_state = "sleeper"
	death = FALSE
	roundstart = FALSE //you could use these for alive fake humans on roundstart but this is more common scenario

//Non-human spawners

/obj/effect/mob_spawn/AICorpse/create() //Creates a corrupted AI
	var/A = locate(/mob/living/silicon/ai) in loc
	if(A)
		return
	var/L = new /datum/ai_laws/default/asimov
	var/B = new /obj/item/device/mmi
	var/mob/living/silicon/ai/M = new(src.loc, L, B, 1) //spawn new AI at landmark as var M
	M.name = src.name
	M.real_name = src.name
	M.aiPDA.toff = 1 //turns the AI's PDA messenger off, stopping it showing up on player PDAs
	M.death() //call the AI's death proc
	qdel(src)

/obj/effect/mob_spawn/slime
	mob_type = 	/mob/living/simple_animal/slime
	var/mobcolour = "grey"
	icon = 'icons/mob/slimes.dmi'
	icon_state = "grey baby slime" //sets the icon in the map editor

/obj/effect/mob_spawn/slime/equip(mob/living/simple_animal/slime/S)
	S.colour = mobcolour

/obj/effect/mob_spawn/human/facehugger/create() //Creates a squashed facehugger
	var/obj/item/clothing/mask/facehugger/O = new(src.loc) //variable O is a new facehugger at the location of the landmark
	O.name = src.name
	O.Die() //call the facehugger's death proc
	qdel(src)

// I'll work on making a list of corpses people request for maps, or that I think will be commonly used. Syndicate operatives for example.

/obj/effect/mob_spawn/human/syndicatesoldier
	name = "Syndicate Operative"
	uniform = /obj/item/clothing/under/syndicate
	suit = /obj/item/clothing/suit/armor/vest
	shoes = /obj/item/clothing/shoes/combat
	gloves = /obj/item/clothing/gloves/combat
	radio = /obj/item/device/radio/headset
	mask = /obj/item/clothing/mask/gas
	helmet = /obj/item/clothing/head/helmet/swat
	back = /obj/item/weapon/storage/backpack
	has_id = 1
	id_job = "Operative"
	id_access = "Syndicate"

/obj/effect/mob_spawn/human/syndicatecommando
	name = "Syndicate Commando"
	uniform = /obj/item/clothing/under/syndicate
	suit = /obj/item/clothing/suit/space/hardsuit/syndi
	shoes = /obj/item/clothing/shoes/combat
	gloves = /obj/item/clothing/gloves/combat
	radio = /obj/item/device/radio/headset
	mask = /obj/item/clothing/mask/gas/syndicate
	helmet = /obj/item/clothing/head/helmet/space/hardsuit/syndi
	back = /obj/item/weapon/tank/jetpack/oxygen
	pocket1 = /obj/item/weapon/tank/internals/emergency_oxygen
	has_id = 1
	id_job = "Operative"
	id_access = "Syndicate"

///////////Civilians//////////////////////

/obj/effect/mob_spawn/human/cook
	name = "Cook"
	uniform = /obj/item/clothing/under/rank/chef
	suit = /obj/item/clothing/suit/apron/chef
	shoes = /obj/item/clothing/shoes/sneakers/black
	helmet = /obj/item/clothing/head/chefhat
	back = /obj/item/weapon/storage/backpack
	radio = /obj/item/device/radio/headset
	has_id = 1
	id_job = "Cook"
	id_access = "Cook"

/obj/effect/mob_spawn/human/doctor
	name = "Doctor"
	radio = /obj/item/device/radio/headset/headset_med
	uniform = /obj/item/clothing/under/rank/medical
	suit = /obj/item/clothing/suit/toggle/labcoat
	back = /obj/item/weapon/storage/backpack/medic
	pocket1 = /obj/item/device/flashlight/pen
	shoes = /obj/item/clothing/shoes/sneakers/black
	has_id = 1
	id_job = "Medical Doctor"
	id_access = "Medical Doctor"

/obj/effect/mob_spawn/human/engineer
	name = "Engineer"
	radio = /obj/item/device/radio/headset/headset_eng
	uniform = /obj/item/clothing/under/rank/engineer
	back = /obj/item/weapon/storage/backpack/industrial
	shoes = /obj/item/clothing/shoes/sneakers/orange
	belt = /obj/item/weapon/storage/belt/utility/full
	gloves = /obj/item/clothing/gloves/color/yellow
	helmet = /obj/item/clothing/head/hardhat
	has_id = 1
	id_job = "Station Engineer"
	id_access = "Station Engineer"

/obj/effect/mob_spawn/human/engineer/rig
	suit = /obj/item/clothing/suit/space/hardsuit/engine
	mask = /obj/item/clothing/mask/breath

/obj/effect/mob_spawn/human/clown
	name = "Clown"
	uniform = /obj/item/clothing/under/rank/clown
	shoes = /obj/item/clothing/shoes/clown_shoes
	radio = /obj/item/device/radio/headset
	mask = /obj/item/clothing/mask/gas/clown_hat
	pocket1 = /obj/item/weapon/bikehorn
	back = /obj/item/weapon/storage/backpack/clown
	has_id = 1
	id_job = "Clown"
	id_access = "Clown"

/obj/effect/mob_spawn/human/scientist
	name = "Scientist"
	radio = /obj/item/device/radio/headset/headset_sci
	uniform = /obj/item/clothing/under/rank/scientist
	suit = /obj/item/clothing/suit/toggle/labcoat/science
	back = /obj/item/weapon/storage/backpack
	shoes = /obj/item/clothing/shoes/sneakers/white
	has_id = 1
	id_job = "Scientist"
	id_access = "Scientist"

/obj/effect/mob_spawn/human/miner
	radio = /obj/item/device/radio/headset/headset_cargo
	uniform = /obj/item/clothing/under/rank/miner
	gloves = /obj/item/clothing/gloves/fingerless
	back = /obj/item/weapon/storage/backpack/industrial
	shoes = /obj/item/clothing/shoes/sneakers/black
	has_id = 1
	id_job = "Shaft Miner"
	id_access = "Shaft Miner"

/obj/effect/mob_spawn/human/miner/rig
	suit = /obj/item/clothing/suit/space/hardsuit/mining
	mask = /obj/item/clothing/mask/breath

/obj/effect/mob_spawn/human/plasmaman
	mob_species = /datum/species/plasmaman
	helmet = /obj/item/clothing/head/helmet/space/plasmaman
	uniform = /obj/item/clothing/under/plasmaman
	back = /obj/item/weapon/tank/internals/plasmaman/full
	mask = /obj/item/clothing/mask/breath

/obj/effect/mob_spawn/human/bartender
	name = "Space Bartender"
	uniform = /obj/item/clothing/under/rank/bartender
	back = /obj/item/weapon/storage/backpack
	shoes = /obj/item/clothing/shoes/sneakers/black
	suit = /obj/item/clothing/suit/armor/vest
	glasses = /obj/item/clothing/glasses/sunglasses/reagent
	has_id = 1
	id_job = "Bartender"
	id_access = "Bartender"

/obj/effect/mob_spawn/human/bartender/alive
	death = FALSE
	roundstart = FALSE
	mob_name = "Space Bartender"
	name = "sleeper"
	icon = 'icons/obj/Cryogenic2.dmi'
	icon_state = "sleeper"
	flavour_text = "You are a space bartender!"

/////////////////Officers+Nanotrasen Security//////////////////////

/obj/effect/mob_spawn/human/bridgeofficer
	name = "Bridge Officer"
	radio = /obj/item/device/radio/headset/heads/hop
	uniform = /obj/item/clothing/under/rank/centcom_officer
	suit = /obj/item/clothing/suit/armor/bulletproof
	shoes = /obj/item/clothing/shoes/sneakers/black
	glasses = /obj/item/clothing/glasses/sunglasses
	has_id = 1
	id_job = "Bridge Officer"
	id_access = "Captain"

/obj/effect/mob_spawn/human/commander
	name = "Commander"
	uniform = /obj/item/clothing/under/rank/centcom_commander
	suit = /obj/item/clothing/suit/armor
	radio = /obj/item/device/radio/headset/heads/captain
	glasses = /obj/item/clothing/glasses/eyepatch
	mask = /obj/item/clothing/mask/cigarette/cigar/cohiba
	helmet = /obj/item/clothing/head/centhat
	gloves = /obj/item/clothing/gloves/combat
	shoes = /obj/item/clothing/shoes/combat/swat
	pocket1 = /obj/item/weapon/lighter
	has_id = 1
	id_job = "Commander"
	id_access = "Captain"

/obj/effect/mob_spawn/human/nanotrasensoldier
	name = "Nanotrasen Private Security Officer"
	uniform = /obj/item/clothing/under/rank/security
	suit = /obj/item/clothing/suit/armor/vest
	shoes = /obj/item/clothing/shoes/combat
	gloves = /obj/item/clothing/gloves/combat
	mask = /obj/item/clothing/mask/gas/sechailer/swat
	helmet = /obj/item/clothing/head/helmet/swat/nanotrasen
	back = /obj/item/weapon/storage/backpack/security
	has_id = 1
	id_job = "Private Security Force"
	id_access = "Security Officer"

/obj/effect/mob_spawn/human/commander/alive
	death = FALSE
	roundstart = FALSE
	mob_name = "Nanotrasen Commander"
	name = "sleeper"
	icon = 'icons/obj/Cryogenic2.dmi'
	icon_state = "sleeper"
	flavour_text = "You are a Nanotrasen Commander!"

/////////////////Spooky Undead//////////////////////

/obj/effect/mob_spawn/human/skeleton
	name = "skeletal remains"
	mob_name = "skeleton"
	mob_species = /datum/species/skeleton
	mob_gender = NEUTER

/obj/effect/mob_spawn/human/skeleton/alive
	death = FALSE
	roundstart = FALSE
	icon = 'icons/effects/blood.dmi'
	icon_state = "remains"
	flavour_text = "By unknown powers, your skeletal remains have been reanimated! Walk this mortal plain and terrorize all living adventurers who dare cross your path."

/obj/effect/mob_spawn/human/zombie
	name = "rotting corpse"
	mob_name = "zombie"
	mob_species = /datum/species/zombie

/obj/effect/mob_spawn/human/zombie/alive
	death = FALSE
	roundstart = FALSE
	icon = 'icons/effects/blood.dmi'
	icon_state = "remains"
	flavour_text = "By unknown powers, your rotting remains have been resurrected! Walk this mortal plain and terrorize all living adventurers who dare cross your path."

/obj/effect/mob_spawn/human/abductor
	name = "abductor"
	mob_name = "alien"
	mob_species = /datum/species/abductor
	uniform = /obj/item/clothing/under/color/grey
	shoes = /obj/item/clothing/shoes/combat

///Prisoner

/obj/effect/mob_spawn/human/prisoner_transport
	name = "prisoner sleeper"
	icon = 'icons/obj/Cryogenic2.dmi'
	icon_state = "sleeper"
	uniform = /obj/item/clothing/under/rank/prisoner
	mask = /obj/item/clothing/mask/breath
	shoes = /obj/item/clothing/shoes/sneakers/orange
	pocket1 = /obj/item/weapon/tank/internals/emergency_oxygen
	roundstart = FALSE
	death = FALSE
	flavour_text = {"You were a prisoner, sentenced to hard labour in one of Nanotrasen's harsh gulags, but judging by the explosive crash you just survived, fate may have other plans for. First thing is first though: Find a way to survive this mess."}

/obj/effect/mob_spawn/human/prisoner_transport/special(mob/living/new_spawn)
	var/crime = pick("distribution of contraband" , "unauthorized erotic action on duty", "embezzlement", "piloting under the influence", "dereliction of duty", "syndicate collaboration", "mutiny", "multiple homicides", "corporate espionage", "recieving bribes", "malpractice", "worship of prohbited life forms", "possession of profane texts", "murder", "arson", "insulting your manager", "grand theft", "conspiracy", "attempting to unionize", "vandalism", "gross incompetence")
	new_spawn << "You were convincted of: [crime]."


code:
//Exile implants will allow you to use the station gate, but not return home.
//This will allow security to exile badguys/for badguys to exile their kill targets

/obj/item/weapon/implant/exile
	name = "exile implant"
	desc = "Prevents you from returning from away missions"
	origin_tech = "materials=2;biotech=3;magnets=2;bluespace=3"
	activated = 0

/obj/item/weapon/implant/exile/get_data()
	var/dat = {"Implant Specifications:

				Name: Nanotrasen Employee Exile Implant

				Implant Details: The onboard gateway system has been modified to reject entry by individuals containing this implant
"}
	return dat

/obj/item/weapon/implanter/exile
	name = "implanter (exile)"

/obj/item/weapon/implanter/exile/New()
	imp = new /obj/item/weapon/implant/exile( src )
	..()

/obj/item/weapon/implantcase/exile
	name = "implant case - 'Exile'"
	desc = "A glass case containing an exile implant."

/obj/item/weapon/implantcase/exile/New()
	imp = new /obj/item/weapon/implant/exile(src)
	..()

/obj/structure/closet/secure_closet/exile
	name = "exile implants"
	req_access = list(access_hos)

/obj/structure/closet/secure_closet/exile/New()
	..()
	new /obj/item/weapon/implanter/exile(src)
	new /obj/item/weapon/implantcase/exile(src)
	new /obj/item/weapon/implantcase/exile(src)
	new /obj/item/weapon/implantcase/exile(src)
	new /obj/item/weapon/implantcase/exile(src)
	new /obj/item/weapon/implantcase/exile(src)