code:
// Every cycle, the pump uses the air in air_in to try and make air_out the perfect pressure.
//
// node1, air1, network1 correspond to input
// node2, air2, network2 correspond to output
//
// Thus, the two variables affect pump operation are set in New():
//   air1.volume
//     This is the volume of gas available to the pump that may be transfered to the output
//   air2.volume
//     Higher quantities of this cause more air to be perfected later
//     but overall network volume is also increased as this increases...

/obj/machinery/atmospherics/components/binary/pump
	icon_state = "pump_map-3"
	name = "gas pump"
	desc = "A pump that moves gas by pressure."
	can_unwrench = TRUE
	shift_underlay_only = FALSE
	construction_type = /obj/item/pipe/directional
	pipe_state = "pump"
	vent_movement = NONE
	///Pressure that the pump will reach when on
	var/target_pressure = ONE_ATMOSPHERE
	///Frequency for radio signaling
	var/frequency = 0
	///ID for radio signaling
	var/id = null
	///Connection to the radio processing
	var/datum/radio_frequency/radio_connection

/obj/machinery/atmospherics/components/binary/pump/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/usb_port, list(
		/obj/item/circuit_component/atmos_pump,
	))

/obj/machinery/atmospherics/components/binary/pump/CtrlClick(mob/user)
	if(can_interact(user))
		set_on(!on)
		//SEPTIC EDIT START
		warning_plasmafloyder(user, "turned [on ? "on" : "off"]")
		//SEPTIC EDIT END
		investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS)
		update_appearance()
	return ..()

/obj/machinery/atmospherics/components/binary/pump/AltClick(mob/user)
	if(can_interact(user))
		target_pressure = MAX_OUTPUT_PRESSURE
		investigate_log("was set to [target_pressure] kPa by [key_name(user)]", INVESTIGATE_ATMOS)
		//SEPTIC EDIT START
		warning_plasmafloyder(user, "maximized")
		//SEPTIC EDIT END
		balloon_alert(user, "pressure output set to [target_pressure] kPa")
		update_appearance()
	return ..()

/obj/machinery/atmospherics/components/binary/pump/Destroy()
	SSradio.remove_object(src,frequency)
	if(radio_connection)
		radio_connection = null
	return ..()

/obj/machinery/atmospherics/components/binary/pump/update_icon_nopipes()
	icon_state = (on && is_operational) ? "pump_on-[set_overlay_offset(piping_layer)]" : "pump_off-[set_overlay_offset(piping_layer)]"

/obj/machinery/atmospherics/components/binary/pump/process_atmos()
	if(!on || !is_operational)
		return

	var/datum/gas_mixture/air1 = airs[1]
	var/datum/gas_mixture/air2 = airs[2]

	if(air1.pump_gas_to(air2, target_pressure))
		update_parents()

/obj/machinery/atmospherics/components/binary/pump/proc/set_on(active)
	on = active
	SEND_SIGNAL(src, COMSIG_PUMP_SET_ON, on)

/**
 * Called in atmos_init(), used to change or remove the radio frequency from the component
 * Arguments:
 * * -new_frequency: the frequency that should be used for the radio to attach to the component, use 0 to remove the radio
 */
/obj/machinery/atmospherics/components/binary/pump/proc/set_frequency(new_frequency)
	SSradio.remove_object(src, frequency)
	frequency = new_frequency
	if(frequency)
		radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA)

/**
 * Called in atmos_init(), send the component status to the radio device connected
 */
/obj/machinery/atmospherics/components/binary/pump/proc/broadcast_status()
	if(!radio_connection)
		return

	var/datum/signal/signal = new(list(
		"tag" = id,
		"device" = "AGP",
		"power" = on,
		"target_output" = target_pressure,
		"sigtype" = "status"
	))
	radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)

/obj/machinery/atmospherics/components/binary/pump/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "AtmosPump", name)
		ui.open()

/obj/machinery/atmospherics/components/binary/pump/ui_data()
	var/data = list()
	data["on"] = on
	data["pressure"] = round(target_pressure)
	data["max_pressure"] = round(MAX_OUTPUT_PRESSURE)
	return data

/obj/machinery/atmospherics/components/binary/pump/ui_act(action, params)
	. = ..()
	if(.)
		return

	switch(action)
		if("power")
			set_on(!on)
			//SEPTIC EDIT START
			warning_plasmafloyder(usr, "turned [on ? "on" : "off"]")
			//SEPTIC EDIT END
			investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
			. = TRUE
		if("pressure")
			var/pressure = params["pressure"]
			if(pressure == "max")
				pressure = MAX_OUTPUT_PRESSURE
				. = TRUE
			else if(text2num(pressure) != null)
				pressure = text2num(pressure)
				. = TRUE
			if(.)
				target_pressure = clamp(pressure, 0, MAX_OUTPUT_PRESSURE)
				//SEPTIC EDIT START
				warning_plasmafloyder(usr, "set to [target_pressure]")
				//SEPTIC EDIT END
				investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", INVESTIGATE_ATMOS)
	update_appearance()

/obj/machinery/atmospherics/components/binary/pump/atmos_init()
	..()
	if(frequency)
		set_frequency(frequency)

/obj/machinery/atmospherics/components/binary/pump/receive_signal(datum/signal/signal)
	if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
		return

	var/old_on = on //for logging

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

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

	if("set_output_pressure" in signal.data)
		target_pressure = clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50)

	if(on != old_on)
		investigate_log("was turned [on ? "on" : "off"] by a remote signal", INVESTIGATE_ATMOS)

	if("status" in signal.data)
		broadcast_status()
		return

	broadcast_status()
	update_appearance()

/obj/machinery/atmospherics/components/binary/pump/can_unwrench(mob/user)
	. = ..()
	if(. && on && is_operational)
		to_chat(user, span_warning("You cannot unwrench [src], turn it off first!"))
		return FALSE

/obj/machinery/atmospherics/components/binary/pump/layer2
	piping_layer = 2
	icon_state= "pump_map-2"

/obj/machinery/atmospherics/components/binary/pump/layer4
	piping_layer = 4
	icon_state= "pump_map-4"

/obj/machinery/atmospherics/components/binary/pump/on
	on = TRUE
	icon_state = "pump_on_map-3"

/obj/machinery/atmospherics/components/binary/pump/on/layer2
	piping_layer = 2
	icon_state= "pump_on_map-2"

/obj/machinery/atmospherics/components/binary/pump/on/layer4
	piping_layer = 4
	icon_state= "pump_on_map-4"

/obj/item/circuit_component/atmos_pump
	display_name = "Atmospheric Binary Pump"
	desc = "The interface for communicating with a pump."

	///Set the target pressure of the pump
	var/datum/port/input/pressure_value
	///Activate the pump
	var/datum/port/input/on
	///Deactivate the pump
	var/datum/port/input/off
	///Signals the circuit to retrieve the pump's current pressure and temperature
	var/datum/port/input/request_data

	///Pressure of the input port
	var/datum/port/output/input_pressure
	///Pressure of the output port
	var/datum/port/output/output_pressure
	///Temperature of the input port
	var/datum/port/output/input_temperature
	///Temperature of the output port
	var/datum/port/output/output_temperature

	///Whether the pump is currently active
	var/datum/port/output/is_active
	///Send a signal when the pump is turned on
	var/datum/port/output/turned_on
	///Send a signal when the pump is turned off
	var/datum/port/output/turned_off

	///The component parent object
	var/obj/machinery/atmospherics/components/binary/pump/connected_pump

/obj/item/circuit_component/atmos_pump/populate_ports()
	pressure_value = add_input_port("New Pressure", PORT_TYPE_NUMBER, trigger = .proc/set_pump_pressure)
	on = add_input_port("Turn On", PORT_TYPE_SIGNAL, trigger = .proc/set_pump_on)
	off = add_input_port("Turn Off", PORT_TYPE_SIGNAL, trigger = .proc/set_pump_off)
	request_data = add_input_port("Request Port Data", PORT_TYPE_SIGNAL, trigger = .proc/request_pump_data)

	input_pressure = add_output_port("Input Pressure", PORT_TYPE_NUMBER)
	output_pressure = add_output_port("Output Pressure", PORT_TYPE_NUMBER)
	input_temperature = add_output_port("Input Temperature", PORT_TYPE_NUMBER)
	output_temperature = add_output_port("Output Temperature", PORT_TYPE_NUMBER)

	is_active = add_output_port("Active", PORT_TYPE_NUMBER)
	turned_on = add_output_port("Turned On", PORT_TYPE_SIGNAL)
	turned_off = add_output_port("Turned Off", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/atmos_pump/register_usb_parent(atom/movable/shell)
	. = ..()
	if(istype(shell, /obj/machinery/atmospherics/components/binary/pump))
		connected_pump = shell
		RegisterSignal(connected_pump, COMSIG_PUMP_SET_ON, .proc/handle_pump_activation)

/obj/item/circuit_component/atmos_pump/unregister_usb_parent(atom/movable/shell)
	UnregisterSignal(connected_pump, COMSIG_PUMP_SET_ON)
	connected_pump = null
	return ..()

/obj/item/circuit_component/atmos_pump/proc/handle_pump_activation(datum/source, active)
	SIGNAL_HANDLER
	is_active.set_output(active)
	if(active)
		turned_on.set_output(COMPONENT_SIGNAL)
	else
		turned_off.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/atmos_pump/proc/set_pump_pressure()
	CIRCUIT_TRIGGER
	if(!connected_pump)
		return
	connected_pump.target_pressure = pressure_value.value

/obj/item/circuit_component/atmos_pump/proc/set_pump_on()
	CIRCUIT_TRIGGER
	if(!connected_pump)
		return
	connected_pump.set_on(TRUE)

/obj/item/circuit_component/atmos_pump/proc/set_pump_off()
	CIRCUIT_TRIGGER
	if(!connected_pump)
		return
	connected_pump.set_on(FALSE)

/obj/item/circuit_component/atmos_pump/proc/request_pump_data()
	CIRCUIT_TRIGGER
	if(!connected_pump)
		return
	var/datum/gas_mixture/air_input = connected_pump.airs[1]
	var/datum/gas_mixture/air_output = connected_pump.airs[2]
	input_pressure.set_output(air_input.return_pressure())
	output_pressure.set_output(air_output.return_pressure())
	input_temperature.set_output(air_input.return_temperature())
	output_temperature.set_output(air_output.return_temperature())


code:
/obj/machinery/atmospherics/components/binary/temperature_gate
	icon_state = "tgate_map-3"
	name = "temperature gate"
	desc = "An activable gate that compares the input temperature with the interface set temperature to check if the gas can flow from the input side to the output side or not."
	can_unwrench = TRUE
	shift_underlay_only = FALSE
	construction_type = /obj/item/pipe/directional
	pipe_state = "tgate"
	///If the temperature of the mix before the gate is lower than this, the gas will flow (if inverted, if the temperature of the mix before the gate is higher than this)
	var/target_temperature = T0C
	///Minimum allowed temperature
	var/minimum_temperature = TCMB
	///Maximum allowed temperature to be set
	var/max_temperature = 4500
	///Check if the sensor should let gas pass if temperature in the mix is less/higher than the target one
	var/inverted = FALSE
	///Check if the gas is moving from one pipenet to the other
	var/is_gas_flowing = FALSE

/obj/machinery/atmospherics/components/binary/temperature_gate/CtrlClick(mob/user)
	if(can_interact(user))
		on = !on
		investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS)
		update_appearance()
	return ..()

/obj/machinery/atmospherics/components/binary/temperature_gate/AltClick(mob/user)
	if(can_interact(user))
		target_temperature = max_temperature
		investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
		balloon_alert(user, "target temperature set to [target_temperature] K")
		update_appearance()
	return ..()

/obj/machinery/atmospherics/components/binary/temperature_gate/examine(mob/user)
	. = ..()
	. += "This device will let gas flow if the temperature of the gas in the input is [inverted ? "higher" : "lower"] than the temperature set in the interface."
	if(inverted)
		. += "The device settings can be restored if a multitool is used on it."
	else
		. += "The sensor's settings can be changed by using a multitool on the device."

/obj/machinery/atmospherics/components/binary/temperature_gate/update_icon_nopipes()
	if(on && is_operational && is_gas_flowing)
		icon_state = "tgate_flow-[set_overlay_offset(piping_layer)]"
	else if(on && is_operational && !is_gas_flowing)
		icon_state = "tgate_on-[set_overlay_offset(piping_layer)]"
	else
		icon_state = "tgate_off-[set_overlay_offset(piping_layer)]"

/obj/machinery/atmospherics/components/binary/temperature_gate/process_atmos()
	if(!on || !is_operational)
		return

	var/datum/gas_mixture/air1 = airs[1]
	var/datum/gas_mixture/air2 = airs[2]

	if(!inverted)
		if(air1.temperature < target_temperature)
			if(air1.release_gas_to(air2, air1.return_pressure()))
				update_parents()
				is_gas_flowing = TRUE
		else
			is_gas_flowing = FALSE
	else
		if(air1.temperature > target_temperature)
			if(air1.release_gas_to(air2, air1.return_pressure()))
				update_parents()
				is_gas_flowing = TRUE
		else
			is_gas_flowing = FALSE
	update_icon_nopipes()

/obj/machinery/atmospherics/components/binary/temperature_gate/relaymove(mob/living/user, direction)
	if(!on || direction != dir)
		return
	. = ..()

/obj/machinery/atmospherics/components/binary/temperature_gate/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "AtmosTempGate", name)
		ui.open()

/obj/machinery/atmospherics/components/binary/temperature_gate/ui_data()
	var/data = list()
	data["on"] = on
	data["temperature"] = round(target_temperature)
	data["min_temperature"] = round(minimum_temperature)
	data["max_temperature"] = round(max_temperature)
	return data

/obj/machinery/atmospherics/components/binary/temperature_gate/ui_act(action, params)
	. = ..()
	if(.)
		return
	switch(action)
		if("power")
			on = !on
			investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
			. = TRUE
		if("temperature")
			var/temperature = params["temperature"]
			if(temperature == "max")
				temperature = max_temperature
				. = TRUE
			else if(text2num(temperature) != null)
				temperature = text2num(temperature)
				. = TRUE
			if(.)
				target_temperature = clamp(minimum_temperature, temperature, max_temperature)
				investigate_log("was set to [target_temperature] K by [key_name(usr)]", INVESTIGATE_ATMOS)
	update_appearance()

/obj/machinery/atmospherics/components/binary/temperature_gate/can_unwrench(mob/user)
	. = ..()
	if(. && on && is_operational)
		to_chat(user, span_warning("You cannot unwrench [src], turn it off first!"))
		return FALSE

/obj/machinery/atmospherics/components/binary/temperature_gate/multitool_act(mob/living/user, obj/item/multitool/I)
	. = ..()
	if (istype(I))
		inverted = !inverted
		if(inverted)
			to_chat(user, span_notice("You set the [src]'s sensors to release gases when the temperature is higher than the setted one."))
		else
			to_chat(user, span_notice("You set the [src]'s sensors to the default settings."))
	return TRUE

repo_name: AnalWerewolf/sundown

license: mit


code:
/obj/machinery/atmospherics/components/binary/temperature_pump
	icon_state = "tpump_map-3"
	name = "temperature pump"
	desc = "A pump that moves heat from one pipeline to another. The input will get cooler, and the output will get hotter."
	can_unwrench = TRUE
	shift_underlay_only = FALSE
	construction_type = /obj/item/pipe/directional
	pipe_state = "tpump"
	vent_movement = NONE
	///Percent of the heat delta to transfer
	var/heat_transfer_rate = 0
	///Maximum allowed transfer percentage
	var/max_heat_transfer_rate = 100

/obj/machinery/atmospherics/components/binary/temperature_pump/CtrlClick(mob/user)
	if(can_interact(user))
		on = !on
		investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS)
		update_appearance()
	return ..()

/obj/machinery/atmospherics/components/binary/temperature_pump/AltClick(mob/user)
	if(can_interact(user) && !(heat_transfer_rate == max_heat_transfer_rate))
		heat_transfer_rate = max_heat_transfer_rate
		investigate_log("was set to [heat_transfer_rate]% by [key_name(user)]", INVESTIGATE_ATMOS)
		balloon_alert(user, "transfer rate set to [heat_transfer_rate]%")
		update_appearance()
	return ..()

/obj/machinery/atmospherics/components/binary/temperature_pump/update_icon_nopipes()
	icon_state = "tpump_[on && is_operational ? "on" : "off"]-[set_overlay_offset(piping_layer)]"

/obj/machinery/atmospherics/components/binary/temperature_pump/process_atmos()
	if(!on || !is_operational)
		return

	var/datum/gas_mixture/air_input = airs[1]
	var/datum/gas_mixture/air_output = airs[2]

	if(!QUANTIZE(air_input.total_moles()) || !QUANTIZE(air_output.total_moles())) //Don't transfer if there's no gas
		return
	var/datum/gas_mixture/remove_input = air_input.remove_ratio(0.9)
	var/datum/gas_mixture/remove_output = air_output.remove_ratio(0.9)

	var/coolant_temperature_delta = remove_input.temperature - remove_output.temperature

	if(coolant_temperature_delta > 0)
		var/input_capacity = remove_input.heat_capacity()
		var/output_capacity = remove_output.heat_capacity()

		var/cooling_heat_amount = (heat_transfer_rate * 0.01) * coolant_temperature_delta * (input_capacity * output_capacity / (input_capacity + output_capacity))
		remove_input.temperature = max(remove_input.temperature - (cooling_heat_amount / input_capacity), TCMB)
		remove_output.temperature = max(remove_output.temperature + (cooling_heat_amount / output_capacity), TCMB)
		update_parents()

	var/power_usage = 200

	air_input.merge(remove_input)
	air_output.merge(remove_output)

	if(power_usage)
		use_power(power_usage)

/obj/machinery/atmospherics/components/binary/temperature_pump/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "AtmosTempPump", name)
		ui.open()

/obj/machinery/atmospherics/components/binary/temperature_pump/ui_data()
	var/data = list()
	data["on"] = on
	data["rate"] = round(heat_transfer_rate)
	data["max_heat_transfer_rate"] = round(max_heat_transfer_rate)
	return data

/obj/machinery/atmospherics/components/binary/temperature_pump/ui_act(action, params)
	. = ..()
	if(.)
		return
	switch(action)
		if("power")
			on = !on
			investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
			. = TRUE
		if("rate")
			var/rate = params["rate"]
			if(rate == "max")
				rate = max_heat_transfer_rate
				. = TRUE
			else if(text2num(rate) != null)
				rate = text2num(rate)
				. = TRUE
			if(.)
				heat_transfer_rate = clamp(rate, 0, max_heat_transfer_rate)
				investigate_log("was set to [heat_transfer_rate]% by [key_name(usr)]", INVESTIGATE_ATMOS)
	update_appearance()


code:
#define THERMOMACHINE_SAFE_TEMPERATURE 500000
#define THERMOMACHINE_POWER_CONVERSION 0.01

/obj/machinery/atmospherics/components/binary/thermomachine
	icon = 'icons/obj/atmospherics/components/thermomachine.dmi'
	icon_state = "thermo_base"

	name = "Temperature control unit"
	desc = "Heats or cools gas in connected pipes."

	density = TRUE
	max_integrity = 300
	armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 100, RAD = 100, FIRE = 80, ACID = 30)
	layer = OBJ_LAYER
	circuit = /obj/item/circuitboard/machine/thermomachine

	hide = TRUE

	move_resist = MOVE_RESIST_DEFAULT
	vent_movement = NONE
	pipe_flags = PIPING_ONE_PER_TURF

	greyscale_config = /datum/greyscale_config/thermomachine
	greyscale_colors = COLOR_VIBRANT_LIME

	set_dir_on_move = FALSE

	var/min_temperature = T20C //actual temperature will be defined by RefreshParts() and by the cooling var
	var/max_temperature = T20C //actual temperature will be defined by RefreshParts() and by the cooling var
	var/target_temperature = T20C
	var/heat_capacity = 0
	var/interactive = TRUE // So mapmakers can disable interaction.
	var/cooling = TRUE
	var/base_heating = 140
	var/base_cooling = 170
	var/use_enviroment_heat = FALSE
	var/skipping_work = FALSE
	var/safeties = TRUE
	var/lastwarning
	var/color_index = 1

	// Efficiency dictates how much we throttle the heat exchange process.
	var/efficiency = 1
	///Efficiency minimum amount, min 0.25, max 1 (works best on higher laser tiers)
	var/parts_efficiency = 1

/obj/machinery/atmospherics/components/binary/thermomachine/Initialize(mapload)
	. = ..()
	RefreshParts()
	update_appearance()

/obj/machinery/atmospherics/components/binary/thermomachine/is_connectable()
	if(!anchored || panel_open)
		return FALSE
	. = ..()

/obj/machinery/atmospherics/components/binary/thermomachine/get_node_connects()
	return list(dir, turn(dir, 180))

/obj/machinery/atmospherics/components/binary/thermomachine/on_construction(obj_color, set_layer)
	var/obj/item/circuitboard/machine/thermomachine/board = circuit
	if(board)
		piping_layer = board.pipe_layer
		set_layer = piping_layer

	if(check_pipe_on_turf())
		deconstruct(TRUE)
		return
	return..()

/obj/machinery/atmospherics/components/binary/thermomachine/RefreshParts()
	var/calculated_bin_rating
	for(var/obj/item/stock_parts/matter_bin/bin in component_parts)
		calculated_bin_rating += bin.rating
	heat_capacity = 7500 * ((calculated_bin_rating - 1) ** 2)
	min_temperature = T20C
	max_temperature = T20C
	var/calculated_laser_rating
	for(var/obj/item/stock_parts/micro_laser/laser in component_parts)
		calculated_laser_rating += laser.rating
	min_temperature = max(T0C - (base_cooling + calculated_laser_rating * 15), TCMB) //73.15K with T1 stock parts
	max_temperature = T20C + (base_heating * calculated_laser_rating) //573.15K with T1 stock parts
	parts_efficiency = min(calculated_laser_rating * 0.125, 1)

/obj/machinery/atmospherics/components/binary/thermomachine/update_icon_state()
	switch(target_temperature)
		if(BODYTEMP_HEAT_WARNING_3 to INFINITY)
			greyscale_colors = COLOR_RED
		if(BODYTEMP_HEAT_WARNING_2 to BODYTEMP_HEAT_WARNING_3)
			greyscale_colors = COLOR_ORANGE
		if(BODYTEMP_HEAT_WARNING_1 to BODYTEMP_HEAT_WARNING_2)
			greyscale_colors = COLOR_YELLOW
		if(BODYTEMP_COLD_WARNING_1 to BODYTEMP_HEAT_WARNING_1)
			greyscale_colors = COLOR_VIBRANT_LIME
		if(BODYTEMP_COLD_WARNING_2 to BODYTEMP_COLD_WARNING_1)
			greyscale_colors = COLOR_CYAN
		if(BODYTEMP_COLD_WARNING_3 to BODYTEMP_COLD_WARNING_2)
			greyscale_colors = COLOR_BLUE
		else
			greyscale_colors = COLOR_VIOLET

	set_greyscale(colors=greyscale_colors)

	if(panel_open)
		icon_state = "thermo-open"
		return ..()
	if(on && is_operational)
		if(skipping_work)
			icon_state = "thermo_1_blinking"
		else
			icon_state = "thermo_1"
		return ..()
	icon_state = "thermo_base"
	return ..()

/obj/machinery/atmospherics/components/binary/thermomachine/update_overlays()
	. = ..()
	if(!initial(icon))
		return
	var/mutable_appearance/thermo_overlay = new(initial(icon))
	. += get_pipe_image(thermo_overlay, "pipe", dir, COLOR_LIME, piping_layer)
	. += get_pipe_image(thermo_overlay, "pipe", turn(dir, 180), COLOR_MOSTLY_PURE_RED, piping_layer)

/obj/machinery/atmospherics/components/binary/thermomachine/examine(mob/user)
	. = ..()
	if(obj_flags & EMAGGED)
		. += span_notice("Something seems wrong with [src]'s thermal safeties.")
	. += span_notice("With the panel open:")
	. += span_notice("-use a wrench with left-click to rotate [src] and right-click to unanchor it.")
	. += span_notice("-use a multitool with left-click to change the piping layer and right-click to change the piping color.")
	. += span_notice("The thermostat is set to [target_temperature]K ([(T0C-target_temperature)*-1]C).")
	if(in_range(user, src) || isobserver(user))
		. += span_notice("Heat capacity at [heat_capacity] Joules per Kelvin.")
		. += span_notice("Temperature range [min_temperature]K - [max_temperature]K ([(T0C-min_temperature)*-1]C - [(T0C-max_temperature)*-1]C).")

/obj/machinery/atmospherics/components/binary/thermomachine/AltClick(mob/living/user)
	if(!can_interact(user))
		return
	target_temperature = T20C
	investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
	balloon_alert(user, "temperature reset to [target_temperature] K")

/** Performs heat calculation for the freezer. The full equation for this whole process is:
 * T3 = (C1*T1  +  (C1*C2)/(C1+C2)*(T2-T1)*E) / C1.
 * T4 = (C1*T1  -  (C1*C2)/(C1+C2)*(T2-T1)*E  +  M) / C1.
 * C1 is main port heat capacity, T1 is the temp.
 * C2 and T2 is for the heat capacity of the freezer and temperature that we desire respectively.
 * T3 is the temperature we get, T4 is the exchange target (heat reservoir).
 * M is the motor heat.
 * E is the efficiency variable. At E=1 and M=0 it works out to be ((C1*T1)+(C2*T2))/(C1+C2).
 */
/obj/machinery/atmospherics/components/binary/thermomachine/process_atmos()
	if(!is_operational || !on)  //if it has no power or its switched off, dont process atmos
		on = FALSE
		update_appearance()
		return

	var/turf/local_turf = get_turf(src)
	if(!local_turf)
		on = FALSE
		update_appearance()
		return

	// The gas we want to cool/heat
	var/datum/gas_mixture/main_port = airs[1]
	var/datum/gas_mixture/exchange_target = airs[2]

	// The difference between target and what we need to heat/cool. Positive if heating, negative if cooling.
	var/temperature_target_delta = target_temperature - main_port.temperature

	// This variable holds the (C1*C2)/(C1+C2)*(T2-T1) part of the equation.
	var/heat_amount = temperature_target_delta * (main_port.heat_capacity() * heat_capacity / (main_port.heat_capacity() + heat_capacity))

	// Motor heat is the heat added to both ports of the thermomachine at every tick.
	var/motor_heat = 5000
	if(abs(temperature_target_delta) < 5) //Allow the machine to work more finely on lower temperature differences.
		motor_heat = 0

	// Automatic Switching. Longer if check to prevent unecessary update_appearances.
	if ((cooling && temperature_target_delta > 0) || (!cooling && temperature_target_delta < 0))
		cooling = temperature_target_delta <= 0 // Thermomachines that reached the target will default to cooling.
		update_appearance()

	skipping_work = FALSE

	if (main_port.total_moles() < 0.01)
		skipping_work = TRUE
		return

	// Efficiency should be a proc level variable, but we need it for the ui.
	// This is to reset the value when we are heating.
	efficiency = 1

	var/mole_efficiency = 1
	var/mole_eff_main_port = 1
	var/mole_eff_thermal_port = 1
	if(cooling)
		// Exchange target is the thing we are paired with, be it enviroment or the red port.
		if(use_enviroment_heat)
			exchange_target = local_turf.return_air()
		else
			exchange_target = airs[2]

		if(exchange_target.total_moles() < 5)
			mole_eff_thermal_port = 0.1
		else
			mole_eff_thermal_port = max(1 - (1 / (exchange_target.total_moles() + 1)) * 5, 0.1)

	if(main_port.total_moles() < 5)
		mole_eff_main_port = 0.1
	else
		mole_eff_main_port = max(1 - (1 / (main_port.total_moles() + 1)) * 5, 0.1)

	mole_efficiency = min(mole_eff_main_port, mole_eff_thermal_port)

	if(cooling)
		if (exchange_target.total_moles() < 0.01)
			skipping_work = TRUE
			return

		// The hotter the heat reservoir is, the larger the malus.
		var/temperature_exchange_delta = exchange_target.temperature - main_port.temperature
		// Log 1 is already 0, going any lower will result in a negative number.
		efficiency = clamp(1 - log(10, max(1, temperature_exchange_delta)) * 0.08, 0.65, 1)
		// We take an extra efficiency malus for enviroments where the mol is too low.
		// Cases of log(0) will be caught by the early return above.
		if (use_enviroment_heat)
			efficiency *= clamp(log(1.55, exchange_target.total_moles()) * 0.15, 0.65, 1)

		efficiency *= mole_efficiency
		efficiency = max(efficiency, parts_efficiency)

		if (exchange_target.temperature > THERMOMACHINE_SAFE_TEMPERATURE && safeties)
			on = FALSE
			visible_message(span_warning("The heat reservoir has reached critical levels, shutting down..."))
			update_appearance()
			return

		else if(exchange_target.temperature > THERMOMACHINE_SAFE_TEMPERATURE && !safeties)
			if((REALTIMEOFDAY - lastwarning) / 5 >= WARNING_DELAY)
				lastwarning = REALTIMEOFDAY
				visible_message(span_warning("The heat reservoir has reached critical levels!"))
				if(check_explosion(exchange_target.temperature))
					explode()
					return PROCESS_KILL //We're dying anyway, so let's stop processing

		exchange_target.temperature = max((THERMAL_ENERGY(exchange_target) - (heat_amount * efficiency) + motor_heat) / exchange_target.heat_capacity(), TCMB)

	if(!cooling)
		efficiency *= mole_efficiency
		efficiency = max(efficiency, parts_efficiency)

	main_port.temperature = max((THERMAL_ENERGY(main_port) + (heat_amount * efficiency)) / main_port.heat_capacity(), TCMB)

	heat_amount = min(abs(heat_amount), 1e8) * THERMOMACHINE_POWER_CONVERSION
	var/power_usage = 0
	var/power_efficiency = max(efficiency, 0.4)
	if(abs(temperature_target_delta)  > 1)
		power_usage = (heat_amount * 0.05 + idle_power_usage) ** (1.05 - (5e7 * power_efficiency) / (max(5e7, heat_amount)))
	else
		power_usage = idle_power_usage

	use_power(power_usage)
	update_appearance()
	update_parents()

/obj/machinery/atmospherics/components/binary/thermomachine/attackby(obj/item/item, mob/user, params)
	if(!on && item.tool_behaviour == TOOL_SCREWDRIVER)
		if(!anchored)
			to_chat(user, span_notice("Anchor [src] first!"))
			return
		if(default_deconstruction_screwdriver(user, "thermo-open", "thermo-0", item))
			change_pipe_connection(panel_open)
			return
	if(default_change_direction_wrench(user, item))
		return
	if(default_deconstruction_crowbar(item))
		return

	if(panel_open && item.tool_behaviour == TOOL_MULTITOOL)
		piping_layer = (piping_layer >= PIPING_LAYER_MAX) ? PIPING_LAYER_MIN : (piping_layer + 1)
		to_chat(user, span_notice("You change the circuitboard to layer [piping_layer]."))
		update_appearance()
		return
	return ..()

/obj/machinery/atmospherics/components/binary/thermomachine/default_change_direction_wrench(mob/user, obj/item/I)
	if(!..())
		return FALSE
	set_init_directions()
	update_appearance()
	return TRUE

/obj/machinery/atmospherics/components/binary/thermomachine/proc/change_pipe_connection(disconnect)
	if(disconnect)
		disconnect_pipes()
		return
	connect_pipes()

/obj/machinery/atmospherics/components/binary/thermomachine/proc/connect_pipes()
	var/obj/machinery/atmospherics/node1 = nodes[1]
	var/obj/machinery/atmospherics/node2 = nodes[2]
	atmos_init()
	node1 = nodes[1]
	if(node1)
		node1.atmos_init()
		node1.add_member(src)
	node2 = nodes[2]
	if(node2)
		node2.atmos_init()
		node2.add_member(src)
	SSair.add_to_rebuild_queue(src)

/obj/machinery/atmospherics/components/binary/thermomachine/proc/disconnect_pipes()
	var/obj/machinery/atmospherics/node1 = nodes[1]
	var/obj/machinery/atmospherics/node2 = nodes[2]
	if(node1)
		if(src in node1.nodes) //Only if it's actually connected. On-pipe version would is one-sided.
			node1.disconnect(src)
		nodes[1] = null
	if(node2)
		if(src in node2.nodes) //Only if it's actually connected. On-pipe version would is one-sided.
			node2.disconnect(src)
		nodes[2] = null
	if(parents[1])
		nullify_pipenet(parents[1])
	if(parents[2])
		nullify_pipenet(parents[2])

/obj/machinery/atmospherics/components/binary/thermomachine/attackby_secondary(obj/item/item, mob/user, params)
	. = ..()
	if(panel_open && item.tool_behaviour == TOOL_WRENCH && !check_pipe_on_turf())
		if(default_unfasten_wrench(user, item))
			return SECONDARY_ATTACK_CONTINUE_CHAIN
	if(panel_open && item.tool_behaviour == TOOL_MULTITOOL)
		color_index = (color_index >= GLOB.pipe_paint_colors.len) ? (color_index = 1) : (color_index = 1 + color_index)
		pipe_color = GLOB.pipe_paint_colors[GLOB.pipe_paint_colors[color_index]]
		visible_message("You set [src] pipe color to [GLOB.pipe_color_name[pipe_color]].")
		update_appearance()
		return SECONDARY_ATTACK_CONTINUE_CHAIN
	return SECONDARY_ATTACK_CONTINUE_CHAIN

/obj/machinery/atmospherics/components/binary/thermomachine/proc/check_pipe_on_turf()
	for(var/obj/machinery/atmospherics/device in get_turf(src))
		if(device == src)
			continue
		if(device.piping_layer == piping_layer)
			visible_message(span_warning("A pipe is hogging the ports, remove the obstruction or change the machine piping layer."))
			return TRUE
	return FALSE

/obj/machinery/atmospherics/components/binary/thermomachine/multitool_act(mob/living/user, obj/item/multitool/multitool)
	if(!istype(multitool))
		return
	if(panel_open && !anchored)
		piping_layer = (piping_layer >= PIPING_LAYER_MAX) ? PIPING_LAYER_MIN : (piping_layer + 1)
		to_chat(user, span_notice("You change the circuitboard to layer [piping_layer]."))
		update_appearance()

/obj/machinery/atmospherics/components/binary/thermomachine/emag_act(mob/user)
	. = ..()
	if(!(obj_flags & EMAGGED))
		if(!do_after(user, 1 SECONDS, src))
			return
		var/datum/effect_system/spark_spread/sparks = new
		sparks.set_up(5, 0, src)
		sparks.attach(src)
		sparks.start()
		obj_flags |= EMAGGED
		user.visible_message(span_warning("You emag [src], overwriting thermal safety restrictions."))
		log_game("[key_name(user)] emagged [src] at [AREACOORD(src)], overwriting thermal safety restrictions.")

/obj/machinery/atmospherics/components/binary/thermomachine/emp_act()
	. = ..()
	if(!(obj_flags & EMAGGED))
		var/datum/effect_system/spark_spread/sparks = new
		sparks.set_up(5, 0, src)
		sparks.attach(src)
		sparks.start()
		obj_flags |= EMAGGED
		safeties = FALSE

/obj/machinery/atmospherics/components/binary/thermomachine/proc/check_explosion(temperature)
	if(temperature < THERMOMACHINE_SAFE_TEMPERATURE + 2000)
		return FALSE
	if(prob(log(6, temperature) * 10)) //75% at 500000, 100% at 1e8
		return TRUE

/obj/machinery/atmospherics/components/binary/thermomachine/proc/explode()
	explosion(loc, 0, 0, 3, 3, TRUE, explosion_cause = src)
	var/datum/gas_mixture/main_port = airs[1]
	var/datum/gas_mixture/exchange_target = airs[2]
	if(main_port)
		loc.assume_air(main_port.remove_ratio(1))
	if(exchange_target)
		loc.assume_air(exchange_target.remove_ratio(1))
	qdel(src)

/obj/machinery/atmospherics/components/binary/thermomachine/ui_status(mob/user)
	if(interactive)
		return ..()
	return UI_CLOSE

/obj/machinery/atmospherics/components/binary/thermomachine/ui_interact(mob/user, datum/tgui/ui)
	if(panel_open)
		return
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "ThermoMachine", name)
		ui.open()

/obj/machinery/atmospherics/components/binary/thermomachine/ui_data(mob/user)
	var/list/data = list()
	data["on"] = on
	data["cooling"] = cooling

	data["min"] = min_temperature
	data["max"] = max_temperature
	data["target"] = target_temperature
	data["initial"] = initial(target_temperature)

	var/datum/gas_mixture/air1 = airs[1]
	data["temperature"] = air1.temperature
	data["pressure"] = air1.return_pressure()
	data["efficiency"] = efficiency

	data["use_env_heat"] = use_enviroment_heat
	data["skipping_work"] = skipping_work
	data["safeties"] = safeties
	var/hacked = (obj_flags & EMAGGED) ? TRUE : FALSE
	data["hacked"] = hacked
	return data

/obj/machinery/atmospherics/components/binary/thermomachine/ui_act(action, params)
	. = ..()
	if(.)
		return

	switch(action)
		if("power")
			on = !on
			update_use_power(on ? ACTIVE_POWER_USE : IDLE_POWER_USE)
			investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
			. = TRUE
		if("cooling")
			cooling = !cooling
			investigate_log("was changed to [cooling ? "cooling" : "heating"] by [key_name(usr)]", INVESTIGATE_ATMOS)
			. = TRUE
		if("target")
			var/target = params["target"]
			var/adjust = text2num(params["adjust"])
			if(target == "input")
				target = input("Set new target ([min_temperature]-[max_temperature] K):", name, target_temperature) as num|null
				if(!isnull(target))
					. = TRUE
			else if(adjust)
				target = target_temperature + adjust
				. = TRUE
			else if(text2num(target) != null)
				target = text2num(target)
				. = TRUE
			if(.)
				target_temperature = clamp(target, min_temperature, max_temperature)
				investigate_log("was set to [target_temperature] K by [key_name(usr)]", INVESTIGATE_ATMOS)
		if("use_env_heat")
			use_enviroment_heat = !use_enviroment_heat
			. = TRUE
		if("safeties")
			safeties = !safeties
			investigate_log("[key_name(usr)] turned off the [src] safeties", INVESTIGATE_ATMOS)
			. = TRUE

	update_appearance()

/obj/machinery/atmospherics/components/binary/thermomachine/CtrlClick(mob/living/user)
	if(!panel_open)
		if(!can_interact(user))
			return
		on = !on
		investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS)
		update_appearance()
		return
	. = ..()

/obj/machinery/atmospherics/components/binary/thermomachine/freezer
	cooling = TRUE

/obj/machinery/atmospherics/components/binary/thermomachine/freezer/on
	on = TRUE
	icon_state = "thermo_base_1"

/obj/machinery/atmospherics/components/binary/thermomachine/freezer/on/Initialize(mapload)
	. = ..()
	if(target_temperature == initial(target_temperature))
		target_temperature = min_temperature

/obj/machinery/atmospherics/components/binary/thermomachine/freezer/on/coldroom
	name = "Cold room temperature control unit"
	icon_state = "thermo_base_1"
	greyscale_colors = COLOR_CYAN
	cooling = TRUE

/obj/machinery/atmospherics/components/binary/thermomachine/freezer/on/coldroom/Initialize(mapload)
	. = ..()
	target_temperature = COLD_ROOM_TEMP

/obj/machinery/atmospherics/components/binary/thermomachine/heater
	cooling = FALSE

/obj/machinery/atmospherics/components/binary/thermomachine/heater/on
	on = TRUE
	icon_state = "thermo_base_1"

#undef THERMOMACHINE_SAFE_TEMPERATURE
#undef THERMOMACHINE_POWER_CONVERSION


code:
/*
It's like a regular ol' straight pipe, but you can turn it on and off.
*/
#define MANUAL_VALVE "m"
#define DIGITAL_VALVE "d"

/obj/machinery/atmospherics/components/binary/valve
	icon_state = "mvalve_map-3"
	name = "manual valve"
	desc = "A pipe with a valve that can be used to disable flow of gas through it."
	can_unwrench = TRUE
	shift_underlay_only = FALSE
	interaction_flags_machine = INTERACT_MACHINE_OFFLINE | INTERACT_MACHINE_OPEN //Intentionally no allow_silicon flag
	pipe_flags = PIPING_CARDINAL_AUTONORMALIZE | PIPING_BRIDGE
	construction_type = /obj/item/pipe/binary
	pipe_state = "mvalve"
	custom_reconcilation = TRUE
	///Type of valve (manual or digital), used to set the icon of the component in update_icon_nopipes()
	var/valve_type = MANUAL_VALVE
	///Bool to stop interactions while the opening/closing animation is going
	var/switching = FALSE

/obj/machinery/atmospherics/components/binary/valve/update_icon_nopipes(animation = FALSE)
	normalize_cardinal_directions()
	if(animation)
		flick("[valve_type]valve_[on][!on]-[set_overlay_offset(piping_layer)]", src)
	icon_state = "[valve_type]valve_[on ? "on" : "off"]-[set_overlay_offset(piping_layer)]"

/**
 * Called by finish_interact(), switch between open and closed, reconcile the air between two pipelines
 */
/obj/machinery/atmospherics/components/binary/valve/proc/set_open(to_open)
	if(on == to_open)
		return
	SEND_SIGNAL(src, COMSIG_VALVE_SET_OPEN, to_open)
	. = on
	on = to_open
	if(on)
		update_icon_nopipes()
		update_parents()
		var/datum/pipeline/parent1 = parents[1]
		parent1.reconcile_air()
		investigate_log("was opened by [usr ? key_name(usr) : "a remote signal"]", INVESTIGATE_ATMOS)
		vent_movement |= VENTCRAWL_ALLOWED
	else
		update_icon_nopipes()
		investigate_log("was closed by [usr ? key_name(usr) : "a remote signal"]", INVESTIGATE_ATMOS)
		vent_movement &= ~VENTCRAWL_ALLOWED

// This is what handles the actual functionality of combining 2 pipenets when the valve is open
// Basically when a pipenet updates it will consider both sides to be the same for the purpose of the gas update
/obj/machinery/atmospherics/components/binary/valve/return_pipenets_for_reconcilation(datum/pipeline/requester)
	. = ..()
	if(!on)
		return
	. += parents[1]
	. += parents[2]

/obj/machinery/atmospherics/components/binary/valve/interact(mob/user)
	add_fingerprint(usr)
	if(switching)
		return
	update_icon_nopipes(TRUE)
	switching = TRUE
	addtimer(CALLBACK(src, .proc/finish_interact), 1 SECONDS)

/**
 * Called by iteract() after a 1 second timer, calls toggle(), allows another interaction with the component.
 */
/obj/machinery/atmospherics/components/binary/valve/proc/finish_interact()
	set_open(!on)
	switching = FALSE

/obj/machinery/atmospherics/components/binary/valve/digital // can be controlled by AI
	icon_state = "dvalve_map-3"

	name = "digital valve"
	desc = "A digitally controlled valve."
	valve_type = DIGITAL_VALVE
	pipe_state = "dvalve"

	interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OFFLINE | INTERACT_MACHINE_OPEN | INTERACT_MACHINE_OPEN_SILICON

/obj/machinery/atmospherics/components/binary/valve/digital/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/usb_port, list(/obj/item/circuit_component/digital_valve))

/obj/item/circuit_component/digital_valve
	display_name = "Digital Valve"
	desc = "The interface for communicating with a digital valve."

	var/obj/machinery/atmospherics/components/binary/valve/digital/attached_valve

	/// Opens the digital valve
	var/datum/port/input/open
	/// Closes the digital valve
	var/datum/port/input/close

	/// Whether the valve is currently open
	var/datum/port/output/is_open
	/// Sent when the valve is opened
	var/datum/port/output/opened
	/// Sent when the valve is closed
	var/datum/port/output/closed

/obj/item/circuit_component/digital_valve/populate_ports()
	open = add_input_port("Open", PORT_TYPE_SIGNAL)
	close = add_input_port("Close", PORT_TYPE_SIGNAL)

	is_open = add_output_port("Is Open", PORT_TYPE_NUMBER)
	opened = add_output_port("Opened", PORT_TYPE_SIGNAL)
	closed = add_output_port("Closed", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/digital_valve/register_usb_parent(atom/movable/shell)
	. = ..()
	if(istype(shell, /obj/machinery/atmospherics/components/binary/valve/digital))
		attached_valve = shell
		RegisterSignal(attached_valve, COMSIG_VALVE_SET_OPEN, .proc/handle_valve_toggled)

/obj/item/circuit_component/digital_valve/unregister_usb_parent(atom/movable/shell)
	UnregisterSignal(attached_valve, COMSIG_VALVE_SET_OPEN)
	attached_valve = null
	return ..()

/obj/item/circuit_component/digital_valve/proc/handle_valve_toggled(datum/source, on)
	SIGNAL_HANDLER
	is_open.set_output(on)
	if(on)
		opened.set_output(COMPONENT_SIGNAL)
	else
		closed.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/digital_valve/input_received(datum/port/input/port)

	if(!attached_valve)
		return

	if(COMPONENT_TRIGGERED_BY(open, port) && !attached_valve.on)
		attached_valve.set_open(TRUE)
	if(COMPONENT_TRIGGERED_BY(close, port) && attached_valve.on)
		attached_valve.set_open(FALSE)

/obj/machinery/atmospherics/components/binary/valve/digital/update_icon_nopipes(animation)
	if(!is_operational)
		normalize_cardinal_directions()
		icon_state = "dvalve_nopower-[set_overlay_offset(piping_layer)]"
		return
	return..()

/obj/machinery/atmospherics/components/binary/valve/layer2
	piping_layer = 2
	icon_state = "mvalve_map-2"

/obj/machinery/atmospherics/components/binary/valve/layer4
	piping_layer = 4
	icon_state = "mvalve_map-4"

/obj/machinery/atmospherics/components/binary/valve/on
	on = TRUE

/obj/machinery/atmospherics/components/binary/valve/on/layer2
	piping_layer = 2
	icon_state = "mvalve_map-2"

/obj/machinery/atmospherics/components/binary/valve/on/layer4
	piping_layer = 4
	icon_state = "mvalve_map-4"

/obj/machinery/atmospherics/components/binary/valve/digital/layer2
	piping_layer = 2
	icon_state = "dvalve_map-2"

/obj/machinery/atmospherics/components/binary/valve/digital/layer4
	piping_layer = 4
	icon_state = "dvalve_map-4"

/obj/machinery/atmospherics/components/binary/valve/digital/on
	on = TRUE

/obj/machinery/atmospherics/components/binary/valve/digital/on/layer2
	piping_layer = 2
	icon_state = "dvalve_map-2"

/obj/machinery/atmospherics/components/binary/valve/digital/on/layer4
	piping_layer = 4
	icon_state = "dvalve_map-4"

#undef MANUAL_VALVE
#undef DIGITAL_VALVE


code:
// Every cycle, the pump uses the air in air_in to try and make air_out the perfect pressure.
//
// node1, air1, network1 correspond to input
// node2, air2, network2 correspond to output
//
// Thus, the two variables affect pump operation are set in New():
//   air1.volume
//     This is the volume of gas available to the pump that may be transfered to the output
//   air2.volume
//     Higher quantities of this cause more air to be perfected later
//     but overall network volume is also increased as this increases...

/obj/machinery/atmospherics/components/binary/volume_pump
	icon_state = "volpump_map-3"
	name = "volumetric gas pump"
	desc = "A pump that moves gas by volume."
	can_unwrench = TRUE
	shift_underlay_only = FALSE
	construction_type = /obj/item/pipe/directional
	pipe_state = "volumepump"
	vent_movement = NONE
	///Transfer rate of the component in L/s
	var/transfer_rate = MAX_TRANSFER_RATE
	///Check if the component has been overclocked
	var/overclocked = FALSE
	///Frequency for radio signaling
	var/frequency = 0
	///ID for radio signaling
	var/id = null
	///Connection to the radio processing
	var/datum/radio_frequency/radio_connection

/obj/machinery/atmospherics/components/binary/volume_pump/CtrlClick(mob/user)
	if(can_interact(user))
		on = !on
		//SEPTIC EDIT START
		warning_plasmafloyder(user, "turned [on ? "on" : "off"]")
		//SEPTIC EDIT END
		investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS)
		update_appearance()
	return ..()

/obj/machinery/atmospherics/components/binary/volume_pump/AltClick(mob/user)
	if(can_interact(user))
		transfer_rate = MAX_TRANSFER_RATE
		investigate_log("was set to [transfer_rate] L/s by [key_name(user)]", INVESTIGATE_ATMOS)
		//SEPTIC EDIT START
		warning_plasmafloyder(user, "maximized")
		//SEPTIC EDIT END
		balloon_alert(user, "volume output set to [transfer_rate] L/s")
		update_appearance()
	return ..()

/obj/machinery/atmospherics/components/binary/volume_pump/Destroy()
	SSradio.remove_object(src,frequency)
	return ..()

/obj/machinery/atmospherics/components/binary/volume_pump/update_icon_nopipes()
	icon_state = on && is_operational ? "volpump_on-[set_overlay_offset(piping_layer)]" : "volpump_off-[set_overlay_offset(piping_layer)]"

/obj/machinery/atmospherics/components/binary/volume_pump/process_atmos()
	if(!on || !is_operational)
		return

	var/datum/gas_mixture/air1 = airs[1]
	var/datum/gas_mixture/air2 = airs[2]

// Pump mechanism just won't do anything if the pressure is too high/too low unless you overclock it.

	var/input_starting_pressure = air1.return_pressure()
	var/output_starting_pressure = air2.return_pressure()

	if((input_starting_pressure < 0.01) || ((output_starting_pressure > 9000))&&!overclocked)
		return

	if(overclocked && (output_starting_pressure-input_starting_pressure > 1000))//Overclocked pumps can only force gas a certain amount.
		return

	var/transfer_ratio = transfer_rate / air1.volume

	var/datum/gas_mixture/removed = air1.remove_ratio(transfer_ratio)

	if(!removed.total_moles())
		return

	if(overclocked)//Some of the gas from the mixture leaks to the environment when overclocked
		var/turf/open/T = loc
		if(istype(T))
			var/datum/gas_mixture/leaked = removed.remove_ratio(VOLUME_PUMP_LEAK_AMOUNT)
			T.assume_air(leaked)

	air2.merge(removed)

	update_parents()

/obj/machinery/atmospherics/components/binary/volume_pump/examine(mob/user)
	. = ..()
	if(overclocked)
		. += "Its warning light is on[on ? " and it's spewing gas!" : "."]"

/**
 * Called in atmos_init(), used to change or remove the radio frequency from the component
 * Arguments:
 * * -new_frequency: the frequency that should be used for the radio to attach to the component, use 0 to remove the radio
 */
/obj/machinery/atmospherics/components/binary/volume_pump/proc/set_frequency(new_frequency)
	SSradio.remove_object(src, frequency)
	frequency = new_frequency
	if(frequency)
		radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA)

/**
 * Called in atmos_init(), send the component status to the radio device connected
 */
/obj/machinery/atmospherics/components/binary/volume_pump/proc/broadcast_status()
	if(!radio_connection)
		return

	var/datum/signal/signal = new(list(
		"tag" = id,
		"device" = "APV",
		"power" = on,
		"transfer_rate" = transfer_rate,
		"sigtype" = "status"
	))
	radio_connection.post_signal(src, signal)

/obj/machinery/atmospherics/components/binary/volume_pump/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "AtmosPump", name)
		ui.open()

/obj/machinery/atmospherics/components/binary/volume_pump/ui_data()
	var/data = list()
	data["on"] = on
	data["rate"] = round(transfer_rate)
	data["max_rate"] = round(MAX_TRANSFER_RATE)
	return data

/obj/machinery/atmospherics/components/binary/volume_pump/atmos_init()
	. = ..()

	set_frequency(frequency)

/obj/machinery/atmospherics/components/binary/volume_pump/ui_act(action, params)
	. = ..()
	if(.)
		return
	switch(action)
		if("power")
			on = !on
			//SEPTIC EDIT START
			warning_plasmafloyder(usr, "turned [on ? "on" : "off"]")
			//SEPTIC EDIT END
			investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS)
			. = TRUE
		if("rate")
			var/rate = params["rate"]
			if(rate == "max")
				rate = MAX_TRANSFER_RATE
				. = TRUE
			else if(text2num(rate) != null)
				rate = text2num(rate)
				. = TRUE
			if(.)
				transfer_rate = clamp(rate, 0, MAX_TRANSFER_RATE)
				//SEPTIC EDIT START
				warning_plasmafloyder(usr, "set to [transfer_rate] L/s")
				//SEPTIC EDIT END
				investigate_log("was set to [transfer_rate] L/s by [key_name(usr)]", INVESTIGATE_ATMOS)
	update_appearance()

/obj/machinery/atmospherics/components/binary/volume_pump/receive_signal(datum/signal/signal)
	if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
		return

	var/old_on = on //for logging

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

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

	if("set_transfer_rate" in signal.data)
		var/datum/gas_mixture/air1 = airs[1]
		transfer_rate = clamp(text2num(signal.data["set_transfer_rate"]),0,air1.volume)

	if(on != old_on)
		investigate_log("was turned [on ? "on" : "off"] by a remote signal", INVESTIGATE_ATMOS)

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

	broadcast_status()
	update_appearance()

/obj/machinery/atmospherics/components/binary/volume_pump/can_unwrench(mob/user)
	. = ..()
	if(. && on && is_operational)
		to_chat(user, span_warning("You cannot unwrench [src], turn it off first!"))
		return FALSE

/obj/machinery/atmospherics/components/binary/volume_pump/multitool_act(mob/living/user, obj/item/I)
	if(!overclocked)
		overclocked = TRUE
		to_chat(user, "The pump makes a grinding noise and air starts to hiss out as you disable its pressure limits.")
	else
		overclocked = FALSE
		to_chat(user, "The pump quiets down as you turn its limiters back on.")
	return TRUE

// mapping

/obj/machinery/atmospherics/components/binary/volume_pump/layer2
	piping_layer = 2
	icon_state = "volpump_map-2"

/obj/machinery/atmospherics/components/binary/volume_pump/layer2
	piping_layer = 2
	icon_state = "volpump_map-2"

/obj/machinery/atmospherics/components/binary/volume_pump/on
	on = TRUE
	icon_state = "volpump_on_map"

/obj/machinery/atmospherics/components/binary/volume_pump/on/layer2
	piping_layer = 2
	icon_state = "volpump_map-2"

/obj/machinery/atmospherics/components/binary/volume_pump/on/layer4
	piping_layer = 4
	icon_state = "volpump_map-4"


code:
// So much of atmospherics.dm was used solely by components, so separating this makes things all a lot cleaner.
// On top of that, now people can add component-speciic procs/vars if they want!

/obj/machinery/atmospherics/components
	hide = FALSE
	///Is the component welded?
	var/welded = FALSE
	///Should the component should show the pipe underneath it?
	var/showpipe = TRUE
	///When the component is on a non default layer should we shift everything? Or just the underlay pipe
	var/shift_underlay_only = TRUE
	///Stores the component pipeline
	var/list/datum/pipeline/parents
	///If this is queued for a rebuild this var signifies whether parents should be updated after it's done
	var/update_parents_after_rebuild = FALSE
	///Stores the component gas mixture
	var/list/datum/gas_mixture/airs
	///Handles whether the custom reconcilation handling should be used
	var/custom_reconcilation = FALSE

/obj/machinery/atmospherics/components/New()
	parents = new(device_type)
	airs = new(device_type)

	..()

	for(var/i in 1 to device_type)
		if(airs[i])
			continue
		var/datum/gas_mixture/component_mixture = new
		component_mixture.volume = 200
		airs[i] = component_mixture

/obj/machinery/atmospherics/components/Initialize(mapload)
	. = ..()

	if(hide)
		RegisterSignal(src, COMSIG_OBJ_HIDE, .proc/hide_pipe)

// Iconnery

/**
 * Called by update_icon(), used individually by each component to determine the icon state without the pipe in consideration
 */
/obj/machinery/atmospherics/components/proc/update_icon_nopipes()
	return

/**
 * Called in Initialize(), set the showpipe var to true or false depending on the situation, calls update_icon()
 */
/obj/machinery/atmospherics/components/proc/hide_pipe(datum/source, covered)
	SIGNAL_HANDLER
	showpipe = !covered
	update_appearance()

/obj/machinery/atmospherics/components/update_icon()
	update_icon_nopipes()

	underlays.Cut()

	color = null
	plane = showpipe ? GAME_PLANE : FLOOR_PLANE

	if(!showpipe)
		return ..()

	var/connected = 0 //Direction bitset

	var/underlay_pipe_layer = shift_underlay_only ? piping_layer : 3

	for(var/i in 1 to device_type) //adds intact pieces
		if(!nodes[i])
			continue
		var/obj/machinery/atmospherics/node = nodes[i]
		var/node_dir = get_dir(src, node)
		var/mutable_appearance/pipe_appearance = mutable_appearance('icons/obj/atmospherics/pipes/pipe_underlays.dmi', "intact_[node_dir]_[underlay_pipe_layer]")
		pipe_appearance.color = node.pipe_color
		underlays += pipe_appearance
		connected |= node_dir

	for(var/direction in GLOB.cardinals)
		if((initialize_directions & direction) && !(connected & direction))
			var/mutable_appearance/pipe_appearance = mutable_appearance('icons/obj/atmospherics/pipes/pipe_underlays.dmi', "exposed_[direction]_[underlay_pipe_layer]")
			pipe_appearance.color = pipe_color
			underlays += pipe_appearance

	if(!shift_underlay_only)
		PIPING_LAYER_SHIFT(src, piping_layer)
	return ..()

// Pipenet stuff; housekeeping

/obj/machinery/atmospherics/components/nullify_node(i)
	if(parents[i])
		nullify_pipenet(parents[i])
	airs[i] = null
	return ..()

/obj/machinery/atmospherics/components/on_construction()
	. = ..()
	update_parents()

/obj/machinery/atmospherics/components/rebuild_pipes()
	. = ..()
	if(update_parents_after_rebuild)
		update_parents()

/obj/machinery/atmospherics/components/get_rebuild_targets()
	var/list/to_return = list()
	for(var/i in 1 to device_type)
		if(parents[i])
			continue
		parents[i] = new /datum/pipeline()
		to_return += parents[i]
	return to_return

/**
 * Called by nullify_node(), used to remove the pipeline the component is attached to
 * Arguments:
 * * -reference: the pipeline the component is attached to
 */
/obj/machinery/atmospherics/components/proc/nullify_pipenet(datum/pipeline/reference)
	if(!reference)
		CRASH("nullify_pipenet(null) called by [type] on [COORD(src)]")

	for (var/i in 1 to parents.len)
		if (parents[i] == reference)
			reference.other_airs -= airs[i] // Disconnects from the pipeline side
			parents[i] = null // Disconnects from the machinery side.

	reference.other_atmos_machines -= src

	/**
	 *  We explicitly qdel pipeline when this particular pipeline
	 *  is projected to have no member and cause GC problems.
	 *  We have to do this because components don't qdel pipelines
	 *  while pipes must and will happily wreck and rebuild everything
	 * again every time they are qdeleted.
	 */

	if(!length(reference.other_atmos_machines) && !length(reference.members))
		if(QDESTROYING(reference))
			CRASH("nullify_pipenet() called on qdeleting [reference]")
		qdel(reference)

/obj/machinery/atmospherics/components/return_pipenet_airs(datum/pipeline/reference)
	var/list/returned_air = list()

	for (var/i in 1 to parents.len)
		if (parents[i] == reference)
			returned_air += airs[i]
	return returned_air

/obj/machinery/atmospherics/components/pipeline_expansion(datum/pipeline/reference)
	if(reference)
		return list(nodes[parents.Find(reference)])
	return ..()

/obj/machinery/atmospherics/components/set_pipenet(datum/pipeline/reference, obj/machinery/atmospherics/target_component)
	parents[nodes.Find(target_component)] = reference

/obj/machinery/atmospherics/components/return_pipenet(obj/machinery/atmospherics/target_component = nodes[1]) //returns parents[1] if called without argument
	return parents[nodes.Find(target_component)]

/obj/machinery/atmospherics/components/replace_pipenet(datum/pipeline/Old, datum/pipeline/New)
	parents[parents.Find(Old)] = New

/obj/machinery/atmospherics/components/unsafe_pressure_release(mob/user, pressures)
	. = ..()

	var/turf/current_turf = get_turf(src)
	if(!current_turf)
		return
	//Remove the gas from airs and assume it
	var/datum/gas_mixture/environment = current_turf.return_air()
	var/lost = null
	var/times_lost = 0
	for(var/i in 1 to device_type)
		var/datum/gas_mixture/air = airs[i]
		lost += pressures*environment.volume/(air.temperature * R_IDEAL_GAS_EQUATION)
		times_lost++
	var/shared_loss = lost/times_lost

	var/datum/gas_mixture/to_release
	for(var/i in 1 to device_type)
		var/datum/gas_mixture/air = airs[i]
		if(!to_release)
			to_release = air.remove(shared_loss)
			continue
		to_release.merge(air.remove(shared_loss))
	current_turf.assume_air(to_release)

// Helpers

/**
 * Called in most atmos processes and gas handling situations, update the parents pipelines of the devices connected to the source component
 * This way gases won't get stuck
 */
/obj/machinery/atmospherics/components/proc/update_parents()
	if(!SSair.initialized)
		return
	if(rebuilding)
		update_parents_after_rebuild = TRUE
		return
	for(var/i in 1 to device_type)
		var/datum/pipeline/parent = parents[i]
		if(!parent)
			WARNING("Component is missing a pipenet! Rebuilding...")
			SSair.add_to_rebuild_queue(src)
		else
			parent.update = TRUE

/obj/machinery/atmospherics/components/return_pipenets()
	. = list()
	for(var/i in 1 to device_type)
		. += return_pipenet(nodes[i])

/// When this machine is in a pipenet that is reconciling airs, this proc can add pipelines to the calculation.
/// Can be either a list of pipenets or a single pipenet.
/obj/machinery/atmospherics/components/proc/return_pipenets_for_reconcilation(datum/pipeline/requester)
	return list()

/// When this machine is in a pipenet that is reconciling airs, this proc can add airs to the calculation.
/// Can be either a list of airs or a single air mix.
/obj/machinery/atmospherics/components/proc/return_airs_for_reconcilation(datum/pipeline/requester)
	return list()

// UI Stuff

/obj/machinery/atmospherics/components/ui_status(mob/user)
	if(allowed(user))
		return ..()
	to_chat(user, span_danger("Access denied."))
	return UI_CLOSE

// Tool acts

/obj/machinery/atmospherics/components/return_analyzable_air()
	return airs

/obj/machinery/atmospherics/components/paint(paint_color)
	if(paintable)
		add_atom_colour(paint_color, FIXED_COLOUR_PRIORITY)
		pipe_color = paint_color
		update_node_icon()
	return paintable


code:
/**
 * This section contain the hfr core with all the variables and the Initialize() and Destroy() procs
 */
/obj/machinery/atmospherics/components/unary/hypertorus/core
	name = "HFR core"
	desc = "This is the Hypertorus Fusion Reactor core, an advanced piece of technology to finely tune the reaction inside of the machine. It has I/O for cooling gases."
	icon = 'icons/obj/atmospherics/components/hypertorus.dmi'
	icon_state = "core_off"
	circuit = /obj/item/circuitboard/machine/HFR_core
	use_power = IDLE_POWER_USE
	idle_power_usage = 50
	///Vars for the state of the icon of the object (open, off, active)
	icon_state_open = "core_open"
	icon_state_off = "core_off"
	icon_state_active = "core_active"

	/**
	 * Processing checks
	 */

	///Checks if the user has started the machine
	var/start_power = FALSE
	///Checks for the cooling to start
	var/start_cooling = FALSE
	///Checks for the fuel to be injected
	var/start_fuel = FALSE
	///Checks for the moderators to be injected
	var/start_moderator = FALSE

	/**
	 * Hypertorus internal objects and gasmixes
	 */

	///Stores the informations of the interface machine
	var/obj/machinery/hypertorus/interface/linked_interface
	///Stores the information of the moderator input
	var/obj/machinery/atmospherics/components/unary/hypertorus/moderator_input/linked_moderator
	///Stores the information of the fuel input
	var/obj/machinery/atmospherics/components/unary/hypertorus/fuel_input/linked_input
	///Stores the information of the waste output
	var/obj/machinery/atmospherics/components/unary/hypertorus/waste_output/linked_output
	///Stores the information of the corners of the machine
	var/list/corners = list()
	///Stores the three inputs/outputs of the HFR, needed for cracking the parts
	var/list/machine_parts = list()
	///Stores the information of the fusion gasmix
	var/datum/gas_mixture/internal_fusion
	///Stores the information of the moderators gasmix
	var/datum/gas_mixture/moderator_internal
	///Set the filtering list of the waste remove
	var/list/moderator_scrubbing = list(
		/datum/gas/helium,
		)
	///Set the amount of moles per tick should be removed from the moderator by filtering
	var/moderator_filtering_rate = 100
	///Stores the current fuel mix that the user has selected
	var/datum/hfr_fuel/selected_fuel

	/**
	 * Fusion vars
	 */

	///E=mc^2 with some addition to allow it gameplaywise
	var/energy = 0
	///Temperature of the center of the fusion reaction
	var/core_temperature = T20C
	/**Power emitted from the center of the fusion reaction: Internal power = densityH2 * densityTrit(Pi * (2 * rH2 * rTrit)**2) * Energy
	* density is calculated with moles/volume, rH2 and rTrit are values calculated with moles/(radius of the gas)
	both of the density can be varied by the power_modifier
	**/
	var/internal_power = 0
	/**The effective power transmission of the fusion reaction, power_output = efficiency * (internal_power - conduction - radiation)
	* Conduction is the heat value that is transmitted by the molecular interactions and it gets removed from the internal_power lowering the effective output
	* Radiation is the irradiation released by the fusion reaction, it comprehends all wavelenghts in the spectrum, it lowers the effective output of the reaction
	**/
	var/power_output = 0
	///Instability effects how chaotic the behavior of the reaction is
	var/instability = 0
	///Amount of radiation that the machine can output
	var/rad_power = 0
	///Difference between the gases temperature and the internal temperature of the reaction
	var/delta_temperature = 0
	///Energy from the reaction lost from the molecule colliding between themselves.
	var/conduction = 0
	///The remaining wavelength that actually can do damage to mobs.
	var/radiation = 0
	///Efficiency of the reaction, it increases with the amount of plasma
	var/efficiency = 0
	///Hotter air is easier to heat up and cool down
	var/heat_limiter_modifier = 0
	///How much the reaction can cool itself
	var/heat_output_max = 0
	///How much the reaction can heat itself
	var/heat_output_min = 0
	///The amount of heat that is finally emitted, based on the power output. Min and max are variables that depends of the modifier
	var/heat_output = 0

	///Check if the user want to remove the waste gases
	var/waste_remove = FALSE
	///User controlled variable to control the flow of the fusion by changing the contact of the material
	var/heating_conductor = 100
	///User controlled variable to control the flow of the fusion by changing the volume of the gasmix by controlling the power of the magnetic fields
	var/magnetic_constrictor  = 100
	///User controlled variable to control the flow of the fusion by changing the instability of the reaction
	var/current_damper = 0
	///Stores the current fusion mix power level
	var/power_level = 0
	///Stores the iron content produced by the fusion
	var/iron_content = 0
	///User controlled variable to control the flow of the fusion by changing the amount of fuel injected
	var/fuel_injection_rate = 25
	///User controlled variable to control the flow of the fusion by changing the amount of moderators injected
	var/moderator_injection_rate = 25

	///Integrity of the machine, if reaches 900 the machine will explode
	var/critical_threshold_proximity = 0
	///Store the integrity for calculations
	var/critical_threshold_proximity_archived = 0
	///Our "Shit is no longer fucked" message. We send it when critical_threshold_proximity is less then critical_threshold_proximity_archived
	var/safe_alert = "Main containment field returning to safe operating parameters."
	///The point at which we should start sending messeges about the critical_threshold_proximity to the engi channels.
	var/warning_point = 50
	///The alert we send when we've reached warning_point
	var/warning_alert = "Danger! Magnetic containment field faltering!"
	///The point at which we start sending messages to the common channel
	var/emergency_point = 700
	///The alert we send when we've reached emergency_point
	var/emergency_alert = "HYPERTORUS MELTDOWN IMMINENT."
	///The point at which we melt
	var/melting_point = 900
	///Boolean used for logging if we've passed the emergency point
	var/has_reached_emergency = FALSE
	///Time in 1/10th of seconds since the last sent warning
	var/lastwarning = 0

	///Our internal radio
	var/obj/item/radio/radio
	///The key our internal radio uses
	var/radio_key = /obj/item/encryptionkey/headset_eng
	///The engineering channel
	var/engineering_channel = "Engineering"
	///The common channel
	var/common_channel = null

	///Our soundloop
	var/datum/looping_sound/hypertorus/soundloop
	///cooldown tracker for accent sounds
	var/last_accent_sound = 0

	///These vars store the temperatures to be used in the GUI
	var/fusion_temperature_archived = 0
	var/fusion_temperature = 0
	var/moderator_temperature_archived = 0
	var/moderator_temperature = 0
	var/coolant_temperature_archived = 0
	var/coolant_temperature = 0
	var/output_temperature_archived = 0
	var/output_temperature = 0
	///Time between current and _archived temperatures
	var/temperature_period = 1
	///Var used in the meltdown phase
	var/final_countdown = FALSE

/obj/machinery/atmospherics/components/unary/hypertorus/core/Initialize(mapload)
	. = ..()
	internal_fusion = new
	internal_fusion.volume = 5000
	moderator_internal = new
	moderator_internal.volume = 10000

	radio = new(src)
	radio.keyslot = new radio_key
	radio.listening = 0
	radio.recalculateChannels()
	investigate_log("has been created.", INVESTIGATE_HYPERTORUS)

/obj/machinery/atmospherics/components/unary/hypertorus/core/Destroy()
	unregister_signals(TRUE)
	if(internal_fusion)
		internal_fusion = null
	if(moderator_internal)
		moderator_internal = null
	if(linked_input)
		QDEL_NULL(linked_input)
	if(linked_output)
		QDEL_NULL(linked_output)
	if(linked_moderator)
		QDEL_NULL(linked_moderator)
	if(linked_interface)
		QDEL_NULL(linked_interface)
	for(var/obj/machinery/hypertorus/corner/corner in corners)
		QDEL_NULL(corner)
	QDEL_NULL(radio)
	QDEL_NULL(soundloop)
	machine_parts = null
	return..()


code:
///Speed of light, in m/s
#define LIGHT_SPEED 299792458
///Calculation between the plank constant and the lambda of the lightwave
#define PLANCK_LIGHT_CONSTANT 2e-16
///Radius of the h2 calculated based on the amount of number of atom in a mole (and some addition for balancing issues)
#define CALCULATED_H2RADIUS 120e-4
///Radius of the trit calculated based on the amount of number of atom in a mole (and some addition for balancing issues)
#define CALCULATED_TRITRADIUS 230e-3
///Power conduction in the void, used to calculate the efficiency of the reaction
#define VOID_CONDUCTION 1e-2
///Mole count required (tritium/hydrogen) to start a fusion reaction
#define FUSION_MOLE_THRESHOLD 25
///Used to reduce the gas_power to a more useful amount
#define INSTABILITY_GAS_POWER_FACTOR 0.003
///Used to calculate the toroidal_size for the instability
#define TOROID_VOLUME_BREAKEVEN 1000
///Constant used when calculating the chance of emitting a radioactive particle
#define PARTICLE_CHANCE_CONSTANT (-20000000)
///Conduction of heat inside the fusion reactor
#define METALLIC_VOID_CONDUCTIVITY 0.38
///Conduction of heat near the external cooling loop
#define HIGH_EFFICIENCY_CONDUCTIVITY 0.975
///Sets the minimum amount of power the machine uses
#define MIN_POWER_USAGE 50000
///Sets the multiplier for the damage
#define DAMAGE_CAP_MULTIPLIER 0.005
///Sets the range of the hallucinations
#define HALLUCINATION_HFR(P) (min(7, round(abs(P) ** 0.25)))
///Chance in percentage points per fusion level of iron accumulation when operating at unsafe levels
#define IRON_CHANCE_PER_FUSION_LEVEL 17
///Amount of iron accumulated per second whenever we fail our saving throw, using the chance above
#define IRON_ACCUMULATED_PER_SECOND 0.005
///Maximum amount of iron that can be healed per second. Calculated to mostly keep up with fusion level 5.
#define IRON_OXYGEN_HEAL_PER_SECOND (IRON_ACCUMULATED_PER_SECOND * (100 - IRON_CHANCE_PER_FUSION_LEVEL) / 100)
///Amount of oxygen in moles required to fully remove 100% iron content. Currently about 2409mol. Calculated to consume at most 10mol/s.
#define OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL (10 / IRON_OXYGEN_HEAL_PER_SECOND)

//If integrity percent remaining is less than these values, the monitor sets off the relevant alarm.
#define HYPERTORUS_MELTING_PERCENT 5
#define HYPERTORUS_EMERGENCY_PERCENT 25
#define HYPERTORUS_DANGER_PERCENT 50
#define HYPERTORUS_WARNING_PERCENT 100

#define WARNING_TIME_DELAY 60
///to prevent accent sounds from layering
#define HYPERTORUS_ACCENT_SOUND_MIN_COOLDOWN 3 SECONDS

#define HYPERTORUS_COUNTDOWN_TIME 30 SECONDS

//
// Damage source: Too much mass in the fusion mix at high fusion levels
//

// Currently, this is 2700 moles at 1 Kelvin, linearly scaling down to a maximum of 1800 safe moles at 1e8 degrees kelvin
// Settings:
/// Start taking overfull damage at this power level
#define HYPERTORUS_OVERFULL_MIN_POWER_LEVEL 6
/// Take 0 damage beneath this much fusion mass at 1 degree Kelvin
#define HYPERTORUS_OVERFULL_MAX_SAFE_COLD_FUSION_MOLES 2700
/// Take 0 damage beneath this much fusion mass at FUSION_TEMPERATURE_MAX degrees Kelvin
#define HYPERTORUS_OVERFULL_MAX_SAFE_HOT_FUSION_MOLES 1800
// From there, how quickly should things get bad?
/// Every 200 moles, 1 point of damage per second
#define HYPERTORUS_OVERFULL_MOLAR_SLOPE (1/200)
// Derived:
// Given these settings, derive the rest of the equation.
// Damage is the dependent variable, fusion_moles and damage_source_temperature are the independent variables
// So the equation takes the form:
//   damage = molar_slope * fusion_moles + temperature_slope * damage_source_temperature + constant
// Derive these constants here for readability
// Derive the temperature slope from the molar slope
#define HYPERTORUS_OVERFULL_TEMPERATURE_SLOPE (HYPERTORUS_OVERFULL_MOLAR_SLOPE * (HYPERTORUS_OVERFULL_MAX_SAFE_COLD_FUSION_MOLES - HYPERTORUS_OVERFULL_MAX_SAFE_HOT_FUSION_MOLES) / (FUSION_MAXIMUM_TEMPERATURE - 1))
// Derive the constant to set damage = 0 at our desired thresholds above
#define HYPERTORUS_OVERFULL_CONSTANT (-(HYPERTORUS_OVERFULL_MOLAR_SLOPE * HYPERTORUS_OVERFULL_MAX_SAFE_HOT_FUSION_MOLES + HYPERTORUS_OVERFULL_TEMPERATURE_SLOPE * FUSION_MAXIMUM_TEMPERATURE))

//
// Heal source: Small enough mass in the fusion mix
//

// Settings:
/// Start healing when fusion mass is below this threshold
#define HYPERTORUS_SUBCRITICAL_MOLES 1200
/// Heal one point per second per this many moles under the threshold
#define HYPERTORUS_SUBCRITICAL_SCALE 400

//
// Heal source: Cold enough coolant
//

// Settings:
/// Heal up to this many points of damage per second at 1 degree kelvin
#define HYPERTORUS_COLD_COOLANT_MAX_RESTORE 2.5
/// Start healing below this temperature
#define HYPERTORUS_COLD_COOLANT_THRESHOLD (10 ** 5)
// Derived:
#define HYPERTORUS_COLD_COOLANT_SCALE (HYPERTORUS_COLD_COOLANT_MAX_RESTORE / log(10, HYPERTORUS_COLD_COOLANT_THRESHOLD))

//
// Damage source: Iron content
//

// Settings:
/// Start taking damage over this threshold, up to a maximum of (1 - HYPERTORUS_MAX_SAFE_IRON) per tick at 100% iron
#define HYPERTORUS_MAX_SAFE_IRON 0.35

//
// Damage source: Extreme levels of mass in fusion mix at any power level
//

// Note: Ignores the damage cap!
// Settings:
/// Start taking damage over this threshold
#define HYPERTORUS_HYPERCRITICAL_MOLES 10000
/// Take this much damage per mole over the threshold per second
#define HYPERTORUS_HYPERCRITICAL_SCALE 0.002
/// Take at most this much damage per second
#define HYPERTORUS_HYPERCRITICAL_MAX_DAMAGE 20

// If the moderator goes hypercritical, it cracks and starts to spill
// If our pressure is weak, it can still spill, just weakly and infrequently
// Even a small amount is still extremely hazardous with fusion temperatures
#define HYPERTORUS_WEAK_SPILL_RATE 0.0005
#define HYPERTORUS_WEAK_SPILL_CHANCE 1
/// Start spilling superhot moderator gas when over this pressure threshold
#define HYPERTORUS_MEDIUM_SPILL_PRESSURE 10000
/// How much we should spill initially
#define HYPERTORUS_MEDIUM_SPILL_INITIAL 0.25
/// How much of the moderator mix we should spill per second until mended
#define HYPERTORUS_MEDIUM_SPILL_RATE 0.01
/// If the moderator gas goes over this threshold, REALLY spill it
#define HYPERTORUS_STRONG_SPILL_PRESSURE 12000
/// How much we should spill initially
#define HYPERTORUS_STRONG_SPILL_INITIAL 0.75
/// How much of the moderator mix we should spill per second until mended
#define HYPERTORUS_STRONG_SPILL_RATE 0.05

//
// Explosion flags for use in fuel recipes
//
#define HYPERTORUS_FLAG_BASE_EXPLOSION (1<<0)
#define HYPERTORUS_FLAG_MEDIUM_EXPLOSION (1<<1)
#define HYPERTORUS_FLAG_DEVASTATING_EXPLOSION (1<<2)
#define HYPERTORUS_FLAG_RADIATION_PULSE (1<<3)
#define HYPERTORUS_FLAG_EMP (1<<4)
#define HYPERTORUS_FLAG_MINIMUM_SPREAD (1<<5)
#define HYPERTORUS_FLAG_MEDIUM_SPREAD (1<<6)
#define HYPERTORUS_FLAG_BIG_SPREAD (1<<7)
#define HYPERTORUS_FLAG_MASSIVE_SPREAD (1<<8)
#define HYPERTORUS_FLAG_CRITICAL_MELTDOWN (1<<9)


code:
///Global list of recipes for atmospheric machines to use
GLOBAL_LIST_INIT(hfr_fuels_list, hfr_fuels_create_list())

/*
 * Global proc to build the gas recipe global list
 */
/proc/hfr_fuels_create_list()
	. = list()
	for(var/fuel_mix_path in subtypesof(/datum/hfr_fuel))
		var/datum/hfr_fuel/fuel_mix = new fuel_mix_path()

		.[fuel_mix.id] = fuel_mix

/datum/hfr_fuel
	///Id for the mix
	var/id = ""
	///The gases that are going to be used as fuel ("GasX + GasY fuel")
	var/name = ""
	///Multiplier for the minimum heat output of the HFR (min 0.01)
	var/negative_temperature_multiplier = 1
	///Multiplier for the maximum heat output of the HFR (min 0.01)
	var/positive_temperature_multiplier = 1
	///Multiplier for the energy released (min 0.01)
	var/energy_concentration_multiplier = 1
	///Multiplier for the fuel consumption (min 0.01)
	var/fuel_consumption_multiplier = 1
	///Multiplier for the gas production (min 0.01)
	var/gas_production_multiplier = 1
	///Max allowed temperature multiplier, don't go overboard with this (mix 0.5, max 0.3)
	var/temperature_change_multiplier = 1
	///These are the main fuels, only 2 gases that are the ones being consumed by the fusion reaction (eg. H2 and trit)
	var/requirements = list()
	///Gases that gets produced directly in the internal gasmix
	var/primary_products = list()
	///Gases that gets produced in the moderator gasmix or directly ejected (must be 6 gases), the order indicate at what power level the gases are going to be made (from power level 1 to 6)
	var/secondary_products = list()
	///Flags to decide what behaviour the meltdown will have depending on the fuel mix used
	var/meltdown_flags = HYPERTORUS_FLAG_BASE_EXPLOSION

/datum/hfr_fuel/plasma_oxy_fuel
	id = "plasma_o2_fuel"
	name = "Plasma + Oxygen fuel"
	negative_temperature_multiplier = 2.5
	positive_temperature_multiplier = 0.1
	energy_concentration_multiplier = 10
	fuel_consumption_multiplier = 3.3
	gas_production_multiplier = 1.4
	temperature_change_multiplier = 0.6
	requirements = list(/datum/gas/plasma, /datum/gas/oxygen)
	primary_products = list(/datum/gas/carbon_dioxide, /datum/gas/water_vapor)
	secondary_products = list(/datum/gas/carbon_dioxide, /datum/gas/water_vapor, /datum/gas/freon, /datum/gas/nitrous_oxide, /datum/gas/pluoxium, /datum/gas/halon)
	meltdown_flags = HYPERTORUS_FLAG_BASE_EXPLOSION | HYPERTORUS_FLAG_MINIMUM_SPREAD

/datum/hfr_fuel/hydrogen_oxy_fuel
	id = "h2_o2_fuel"
	name = "Hydrogen + Oxygen fuel"
	negative_temperature_multiplier = 2
	positive_temperature_multiplier = 0.6
	energy_concentration_multiplier = 3
	fuel_consumption_multiplier = 1.1
	gas_production_multiplier = 0.9
	temperature_change_multiplier = 0.75
	requirements = list(/datum/gas/hydrogen, /datum/gas/oxygen)
	primary_products = list(/datum/gas/helium, /datum/gas/nitrogen)
	secondary_products = list(/datum/gas/helium, /datum/gas/plasma, /datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/bz, /datum/gas/hypernoblium)
	meltdown_flags = HYPERTORUS_FLAG_BASE_EXPLOSION | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_MEDIUM_SPREAD

/datum/hfr_fuel/tritium_oxy_fuel
	id = "t2_o2_fuel"
	name = "Tritium + Oxygen fuel"
	negative_temperature_multiplier = 2.1
	positive_temperature_multiplier = 0.5
	energy_concentration_multiplier = 2
	fuel_consumption_multiplier = 1.2
	gas_production_multiplier = 0.8
	temperature_change_multiplier = 0.8
	requirements = list(/datum/gas/tritium, /datum/gas/oxygen)
	primary_products = list(/datum/gas/helium, /datum/gas/pluoxium)
	secondary_products = list(/datum/gas/helium, /datum/gas/plasma, /datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/bz, /datum/gas/hypernoblium)
	meltdown_flags = HYPERTORUS_FLAG_BASE_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_MEDIUM_SPREAD

/datum/hfr_fuel/hydrogen_tritium_fuel
	id = "h2_t2_fuel"
	name = "Hydrogen + Tritium fuel"
	negative_temperature_multiplier = 1
	positive_temperature_multiplier = 1
	energy_concentration_multiplier = 1
	fuel_consumption_multiplier = 1
	gas_production_multiplier = 1
	temperature_change_multiplier = 0.85
	requirements = list(/datum/gas/hydrogen, /datum/gas/tritium)
	primary_products = list(/datum/gas/helium)
	secondary_products = list(/datum/gas/helium, /datum/gas/plasma, /datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/bz, /datum/gas/hypernoblium)
	meltdown_flags = HYPERTORUS_FLAG_MEDIUM_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_MEDIUM_SPREAD

/datum/hfr_fuel/hypernob_hydrogen_fuel
	id = "hypernob_hydrogen_fuel"
	name = "Hypernoblium + Hydrogen fuel"
	negative_temperature_multiplier = 0.2
	positive_temperature_multiplier = 2.2
	energy_concentration_multiplier = 0.2
	fuel_consumption_multiplier = 0.55
	gas_production_multiplier = 1.4
	temperature_change_multiplier = 0.9
	requirements = list(/datum/gas/hypernoblium, /datum/gas/hydrogen)
	primary_products = list(/datum/gas/antinoblium)
	secondary_products = list(/datum/gas/antinoblium, /datum/gas/helium, /datum/gas/proto_nitrate, /datum/gas/zauker, /datum/gas/healium, /datum/gas/miasma)
	meltdown_flags = HYPERTORUS_FLAG_DEVASTATING_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_BIG_SPREAD

/datum/hfr_fuel/hypernob_trit_fuel
	id = "hypernob_trit_fuel"
	name = "Hypernoblium + Tritium fuel"
	negative_temperature_multiplier = 0.1
	positive_temperature_multiplier = 2.5
	energy_concentration_multiplier = 0.1
	fuel_consumption_multiplier = 0.45
	gas_production_multiplier = 1.7
	temperature_change_multiplier = 0.95
	requirements = list(/datum/gas/hypernoblium, /datum/gas/tritium)
	primary_products = list(/datum/gas/antinoblium)
	secondary_products = list(/datum/gas/antinoblium, /datum/gas/helium, /datum/gas/proto_nitrate, /datum/gas/zauker, /datum/gas/healium, /datum/gas/miasma)
	meltdown_flags = HYPERTORUS_FLAG_DEVASTATING_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_BIG_SPREAD

/datum/hfr_fuel/hypernob_antinob_fuel
	id = "hypernob_antinob_fuel"
	name = "Hypernoblium + Antinoblium fuel"
	negative_temperature_multiplier = 0.01
	positive_temperature_multiplier = 3.5
	energy_concentration_multiplier = 2
	fuel_consumption_multiplier = 0.01
	gas_production_multiplier = 3
	temperature_change_multiplier = 1.2
	requirements = list(/datum/gas/hypernoblium, /datum/gas/antinoblium)
	primary_products = list(/datum/gas/helium)
	secondary_products = list(/datum/gas/plasma, /datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/proto_nitrate, /datum/gas/stimulum, /datum/gas/miasma)
	meltdown_flags = HYPERTORUS_FLAG_DEVASTATING_EXPLOSION | HYPERTORUS_FLAG_RADIATION_PULSE | HYPERTORUS_FLAG_EMP | HYPERTORUS_FLAG_MASSIVE_SPREAD | HYPERTORUS_FLAG_CRITICAL_MELTDOWN