Files
CHOMPStation2/code/ATMOSPHERICS/_atmospherics_helpers.dm
2014-09-12 01:18:12 -04:00

423 lines
20 KiB
Plaintext

/*
Atmos processes
These procs generalize various processes used by atmos machinery, such as pumping, filtering, or scrubbing gas, allowing them to be reused elsewhere.
If no gas was moved/pumped/filtered/whatever, they return a negative number.
Otherwise they return the amount of energy needed to do whatever it is they do (equivalently power if done over 1 second).
In the case of free-flowing gas you can do things with gas and still use 0 power, hence the distinction between negative and non-negative return values.
*/
/obj/machinery/atmospherics/var/last_flow_rate = 0
/obj/machinery/portable_atmospherics/var/last_flow_rate = 0
/obj/machinery/atmospherics/var/debug = 0
/obj/machinery/atmospherics/verb/toggle_debug()
set name = "Toggle Debug Messages"
set category = "Debug"
set src in view()
debug = !debug
usr << "[src]: Debug messages toggled [debug? "on" : "off"]."
//Generalized gas pumping proc.
//Moves gas from one gas_mixture to another and returns the amount of power needed (assuming 1 second), or -1 if no gas was pumped.
//transfer_moles - Limits the amount of moles to transfer. The actual amount of gas moved may also be limited by available_power, if given.
//available_power - the maximum amount of power that may be used when moving gas. If null then the transfer is not limited by power.
/proc/pump_gas(var/obj/machinery/M, var/datum/gas_mixture/source, var/datum/gas_mixture/sink, var/transfer_moles = null, var/available_power = null)
if (source.total_moles < MINUMUM_MOLES_TO_PUMP) //if we cant transfer enough gas just stop to avoid further processing
return -1
if (!transfer_moles)
transfer_moles = source.total_moles
else
transfer_moles = min(source.total_moles, transfer_moles)
//Calculate the amount of energy required and limit transfer_moles based on available power
var/specific_power = calculate_specific_power(source, sink)/ATMOS_PUMP_EFFICIENCY //this has to be calculated before we modify any gas mixtures
if (!isnull(available_power) && specific_power > 0)
transfer_moles = min(transfer_moles, available_power / specific_power)
if (transfer_moles < MINUMUM_MOLES_TO_PUMP) //if we cant transfer enough gas just stop to avoid further processing
return -1
//Update flow rate meter
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
A.last_flow_rate = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
if (A.debug)
A.visible_message("[A]: source entropy: [round(source.specific_entropy(), 0.01)] J/Kmol --> sink entropy: [round(sink.specific_entropy(), 0.01)] J/Kmol")
A.visible_message("[A]: specific entropy change = [round(sink.specific_entropy() - source.specific_entropy(), 0.01)] J/Kmol")
A.visible_message("[A]: specific power = [round(specific_power, 0.1)] W/mol")
A.visible_message("[A]: moles transferred = [transfer_moles] mol")
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
P.last_flow_rate = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
var/datum/gas_mixture/removed = source.remove(transfer_moles)
if (!removed) //Just in case
return -1
var/power_draw = specific_power*transfer_moles
if (power_draw > 0)
removed.add_thermal_energy(power_draw * config.atmos_machine_heat) //1st law - energy is conserved
sink.merge(removed)
return power_draw
//Generalized gas scrubbing proc.
//Selectively moves specified gasses one gas_mixture to another and returns the amount of power needed (assuming 1 second), or -1 if no gas was filtered.
//filtering - A list of gasids to be scrubbed from source
//total_transfer_moles - Limits the amount of moles to scrub. The actual amount of gas scrubbed may also be limited by available_power, if given.
//available_power - the maximum amount of power that may be used when scrubbing gas. If null then the scrubbing is not limited by power.
/proc/scrub_gas(var/obj/machinery/M, var/list/filtering, var/datum/gas_mixture/source, var/datum/gas_mixture/sink, var/total_transfer_moles = null, var/available_power = null)
if (source.total_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
filtering = filtering & source.gas //only filter gasses that are actually there. DO NOT USE &=
//Determine the specific power of each filterable gas type, and the total amount of filterable gas (gasses selected to be scrubbed)
var/total_filterable_moles = 0 //the total amount of filterable gas
var/list/specific_power_gas = list() //the power required to remove one mole of pure gas, for each gas type
for (var/g in filtering)
if (source.gas[g] < MINUMUM_MOLES_TO_FILTER)
continue
var/specific_power = calculate_specific_power_gas(g, source, sink)/ATMOS_FILTER_EFFICIENCY
specific_power_gas[g] = specific_power
total_filterable_moles += source.gas[g]
if (total_filterable_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
//now that we know the total amount of filterable gas, we can calculate the amount of power needed to scrub one mole of gas
var/total_specific_power = 0 //the power required to remove one mole of filterable gas
for (var/g in filtering)
var/ratio = source.gas[g]/total_filterable_moles //this converts the specific power per mole of pure gas to specific power per mole of scrubbed gas
total_specific_power = specific_power_gas[g]*ratio
//Figure out how much of each gas to filter
if (!total_transfer_moles)
total_transfer_moles = total_filterable_moles
else
total_transfer_moles = min(total_transfer_moles, total_filterable_moles)
//limit transfer_moles based on available power
if (!isnull(available_power) && total_specific_power > 0)
total_transfer_moles = min(total_transfer_moles, available_power/total_specific_power)
if (total_transfer_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
//Update flow rate var
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
A.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
P.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
var/power_draw = 0
for (var/g in filtering)
var/transfer_moles = source.gas[g]
//filter gas in proportion to the mole ratio
transfer_moles = min(transfer_moles, total_transfer_moles*(source.gas[g]/total_filterable_moles))
//use update=0. All the filtered gasses are supposed to be added simultaneously, so we update after the for loop.
source.adjust_gas(g, -transfer_moles, update=0)
sink.adjust_gas_temp(g, transfer_moles, source.temperature, update=0)
power_draw += specific_power_gas[g]*transfer_moles
//Remix the resulting gases
sink.update_values()
source.update_values()
if (power_draw > 0)
sink.add_thermal_energy(power_draw * config.atmos_machine_heat) //gotta conserve that energy
return power_draw
//Generalized gas filtering proc.
//Filtering is a bit different from scrubbing. Instead of selectively moving the targeted gas types from one gas mix to another, filtering splits
//the input gas into two outputs: one that contains /only/ the targeted gas types, and another that completely clean of the targeted gas types.
//filtering - A list of gasids to be filtered. These gasses get moved to sink_filtered, while the other gasses get moved to sink_clean.
//total_transfer_moles - Limits the amount of moles to input. The actual amount of gas filtered may also be limited by available_power, if given.
//available_power - the maximum amount of power that may be used when filtering gas. If null then the filtering is not limited by power.
/proc/filter_gas(var/obj/machinery/M, var/list/filtering, var/datum/gas_mixture/source, var/datum/gas_mixture/sink_filtered, var/datum/gas_mixture/sink_clean, var/total_transfer_moles = null, var/available_power = null)
if (source.total_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
filtering = filtering & source.gas //only filter gasses that are actually there. DO NOT USE &=
var/total_specific_power = 0 //the power required to remove one mole of input gas
var/total_filterable_moles = 0 //the total amount of filterable gas
var/total_unfilterable_moles = 0 //the total amount of non-filterable gas
var/list/specific_power_gas = list() //the power required to remove one mole of pure gas, for each gas type
for (var/g in source.gas)
if (source.gas[g] < MINUMUM_MOLES_TO_FILTER)
continue
if (g in filtering)
specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_filtered)/ATMOS_FILTER_EFFICIENCY
total_filterable_moles += source.gas[g]
else
specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_clean)/ATMOS_FILTER_EFFICIENCY
total_unfilterable_moles += source.gas[g]
var/ratio = source.gas[g]/source.total_moles //converts the specific power per mole of pure gas to specific power per mole of input gas mix
total_specific_power = specific_power_gas[g]*ratio
//Figure out how much of each gas to filter
if (!total_transfer_moles)
total_transfer_moles = source.total_moles
else
total_transfer_moles = min(total_transfer_moles, source.total_moles)
//limit transfer_moles based on available power
if (!isnull(available_power) && total_specific_power > 0)
total_transfer_moles = min(total_transfer_moles, available_power/total_specific_power)
if (total_transfer_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
//Update flow rate var
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
A.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
P.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
var/datum/gas_mixture/removed = source.remove(total_transfer_moles)
if (!removed) //Just in case
return -1
var/filtered_power_used = 0 //power used to move filterable gas to sink_filtered
var/unfiltered_power_used = 0 //power used to move unfilterable gas to sink_clean
for (var/g in removed.gas)
var/power_used = specific_power_gas[g]*removed.gas[g]
if (g in filtering)
//use update=0. All the filtered gasses are supposed to be added simultaneously, so we update after the for loop.
sink_filtered.adjust_gas_temp(g, removed.gas[g], removed.temperature, update=0)
removed.adjust_gas(g, -removed.gas[g], update=0)
filtered_power_used += power_used
else
unfiltered_power_used += power_used
sink_filtered.update_values()
removed.update_values()
sink_clean.merge(removed)
//1LTD energy is conserved
if (filtered_power_used > 0)
sink_filtered.add_thermal_energy(filtered_power_used * config.atmos_machine_heat)
if (unfiltered_power_used > 0)
sink_clean.add_thermal_energy(unfiltered_power_used * config.atmos_machine_heat)
return filtered_power_used + unfiltered_power_used
//For omni devices. Instead filtering is an associative list mapping gasids to gas mixtures.
//I don't like the copypasta, but I decided to keep both versions of gas filtering as filter_gas is slightly faster (doesn't create as many temporary lists, doesn't call update_values() as much)
//filter_gas can be removed and replaced with this proc if need be.
/proc/filter_gas_multi(var/obj/machinery/M, var/list/filtering, var/datum/gas_mixture/source, var/datum/gas_mixture/sink_clean, var/total_transfer_moles = null, var/available_power = null)
if (source.total_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
filtering = filtering & source.gas //only filter gasses that are actually there. DO NOT USE &=
var/total_specific_power = 0 //the power required to remove one mole of input gas
var/total_filterable_moles = 0 //the total amount of filterable gas
var/total_unfilterable_moles = 0 //the total amount of non-filterable gas
var/list/specific_power_gas = list() //the power required to remove one mole of pure gas, for each gas type
for (var/g in source.gas)
if (source.gas[g] < MINUMUM_MOLES_TO_FILTER)
continue
if (g in filtering)
var/datum/gas_mixture/sink_filtered = filtering[g]
specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_filtered)/ATMOS_FILTER_EFFICIENCY
total_filterable_moles += source.gas[g]
else
specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_clean)/ATMOS_FILTER_EFFICIENCY
total_unfilterable_moles += source.gas[g]
var/ratio = source.gas[g]/source.total_moles //converts the specific power per mole of pure gas to specific power per mole of input gas mix
total_specific_power = specific_power_gas[g]*ratio
//Figure out how much of each gas to filter
if (!total_transfer_moles)
total_transfer_moles = source.total_moles
else
total_transfer_moles = min(total_transfer_moles, source.total_moles)
//limit transfer_moles based on available power
if (!isnull(available_power) && total_specific_power > 0)
total_transfer_moles = min(total_transfer_moles, available_power/total_specific_power)
if (total_transfer_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
//Update Flow Rate var
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
A.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
P.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
var/datum/gas_mixture/removed = source.remove(total_transfer_moles)
if (!removed) //Just in case
return -1
var/list/filtered_power_used = list() //power used to move filterable gas to the filtered gas mixes
var/unfiltered_power_used = 0 //power used to move unfilterable gas to sink_clean
for (var/g in removed.gas)
var/power_used = specific_power_gas[g]*removed.gas[g]
if (g in filtering)
var/datum/gas_mixture/sink_filtered = filtering[g]
//use update=0. All the filtered gasses are supposed to be added simultaneously, so we update after the for loop.
sink_filtered.adjust_gas_temp(g, removed.gas[g], removed.temperature, update=1)
removed.adjust_gas(g, -removed.gas[g], update=0)
if (power_used)
filtered_power_used[sink_filtered] = power_used
else
unfiltered_power_used += power_used
removed.update_values()
sink_clean.merge(removed)
//1st LTD energy is conserved
var/power_draw = unfiltered_power_used
for (var/datum/gas_mixture/sink_filtered in filtered_power_used)
power_draw += filtered_power_used[sink_filtered]
sink_filtered.add_thermal_energy(filtered_power_used[sink_filtered] * config.atmos_machine_heat)
if (unfiltered_power_used > 0)
sink_clean.add_thermal_energy(unfiltered_power_used * config.atmos_machine_heat)
return power_draw
//Similar deal as the other atmos process procs.
//mix_sources maps input gas mixtures to mix ratios. The mix ratios MUST add up to 1.
/proc/mix_gas(var/obj/machinery/M, var/list/mix_sources, var/datum/gas_mixture/sink, var/total_transfer_moles = null, var/available_power = null)
if (!mix_sources.len)
return -1
var/total_specific_power = 0 //the power needed to mix one mole of gas
var/total_mixing_moles = null //the total amount of gas that can be mixed, given our mix ratios
var/total_input_volume = 0 //for flow rate calculation
var/total_input_moles = 0 //for flow rate calculation
var/list/source_specific_power = list()
for (var/datum/gas_mixture/source in mix_sources)
if (source.total_moles < MINUMUM_MOLES_TO_FILTER)
return -1 //either mix at the set ratios or mix no gas at all
var/mix_ratio = mix_sources[source]
if (!mix_ratio)
continue //this gas is not being mixed in
//mixing rate is limited by the source with the least amount of available gas
var/this_mixing_moles = source.total_moles/mix_ratio
if (isnull(total_mixing_moles) || total_mixing_moles > this_mixing_moles)
total_mixing_moles = this_mixing_moles
source_specific_power[source] = calculate_specific_power(source, sink)*mix_ratio/ATMOS_FILTER_EFFICIENCY
total_specific_power += source_specific_power[source]
total_input_volume += source.volume
total_input_moles += source.total_moles
if (total_mixing_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
if (!total_transfer_moles)
total_transfer_moles = total_mixing_moles
else
total_transfer_moles = min(total_mixing_moles, total_transfer_moles)
//limit transfer_moles based on available power
if (!isnull(available_power) && total_specific_power > 0)
total_transfer_moles = min(total_transfer_moles, available_power / total_specific_power)
if (total_transfer_moles < MINUMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
return -1
//Update flow rate var
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
A.last_flow_rate = (total_transfer_moles/total_input_moles)*total_input_volume //group_multiplier gets divided out here
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
P.last_flow_rate = (total_transfer_moles/total_input_moles)*total_input_volume //group_multiplier gets divided out here
var/total_power_draw = 0
for (var/datum/gas_mixture/source in mix_sources)
var/mix_ratio = mix_sources[source]
if (!mix_ratio)
continue
var/transfer_moles = total_transfer_moles * mix_ratio
var/datum/gas_mixture/removed = source.remove(transfer_moles)
var/power_draw = transfer_moles * source_specific_power[source]
removed.add_thermal_energy(power_draw * config.atmos_machine_heat) //conservation of energy
total_power_draw += power_draw
sink.merge(removed)
return total_power_draw
/*
Helper procs for various things.
*/
//Calculates the amount of power needed to move one mole from source to sink.
/proc/calculate_specific_power(datum/gas_mixture/source, datum/gas_mixture/sink)
//Calculate the amount of energy required
var/air_temperature = (sink.temperature > 0)? sink.temperature : source.temperature
var/specific_entropy = sink.specific_entropy() - source.specific_entropy() //sink is gaining moles, source is loosing
var/specific_power = 0 // W/mol
//If specific_entropy is < 0 then power is required to move gas
if (specific_entropy < 0)
specific_power = -specific_entropy*air_temperature //how much power we need per mole
return specific_power
//Calculates the amount of power needed to move one mole of a certain gas from source to sink.
/proc/calculate_specific_power_gas(var/gasid, datum/gas_mixture/source, datum/gas_mixture/sink)
//Calculate the amount of energy required
var/air_temperature = (sink.temperature > 0)? sink.temperature : source.temperature
var/specific_entropy = sink.specific_entropy_gas(gasid) - source.specific_entropy_gas(gasid) //sink is gaining moles, source is loosing
var/specific_power = 0 // W/mol
//If specific_entropy is < 0 then power is required to move gas
if (specific_entropy < 0)
specific_power = -specific_entropy*air_temperature //how much power we need per mole
return specific_power
//This proc handles power usages.
//Calling update_use_power() or use_power() too often will result in lag since updating area power can be costly.
//This proc implements an approximation scheme that will cause area power updates to be triggered less often.
//By having atmos machinery use this proc it is easy to change the power usage approximation for all atmos machines
/obj/machinery/proc/handle_power_draw(var/usage_amount)
//This code errs on the side of using more power. Using this will mean that sometimes atmos machines use more power than they need, but won't get power for free.
if (usage_amount > idle_power_usage)
update_use_power(2)
else
if (use_power >= 2)
use_power = 1 //Don't update here. We will use more power than we are supposed to, but trigger less area power updates.
else
update_use_power(1)
switch (use_power)
if (0) return 0
if (1) return idle_power_usage
if (2 to INFINITY) return max(idle_power_usage, usage_amount)