diff --git a/code/ATMOSPHERICS/atmospherics_helpers.dm b/code/ATMOSPHERICS/atmospherics_helpers.dm index f1b04540a8..6c1ec58100 100644 --- a/code/ATMOSPHERICS/atmospherics_helpers.dm +++ b/code/ATMOSPHERICS/atmospherics_helpers.dm @@ -11,6 +11,19 @@ return specific_power +//Calculates the amount of power needed to move one mole of a certain gas from source to sink. +/obj/machinery/atmospherics/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) //environment is gaining moles, air_contents is loosing + var/specific_power = 0 // W/mol + + //If specific_entropy is < 0 then transfer_moles is limited by how powerful the pump is + 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 so that we only have to call use_power() when the pump is loaded but not at full load. /obj/machinery/atmospherics/proc/handle_pump_power_draw(var/usage_amount) if (usage_amount > active_power_usage - 5) diff --git a/code/ATMOSPHERICS/components/binary_devices/pump.dm b/code/ATMOSPHERICS/components/binary_devices/pump.dm index 50c20337d8..f73c4f0bc8 100644 --- a/code/ATMOSPHERICS/components/binary_devices/pump.dm +++ b/code/ATMOSPHERICS/components/binary_devices/pump.dm @@ -61,15 +61,11 @@ Thus, the two variables affect pump operation are set in New(): update_underlays() /obj/machinery/atmospherics/binary/pump/process() - //reset these each iteration - last_power_draw = 0 - last_flow_rate = 0 - - if(stat & (NOPOWER|BROKEN)) - return - if(!on) + if((stat & (NOPOWER|BROKEN)) || !on) update_use_power(0) - return 0 + last_power_draw = 0 + last_flow_rate = 0 + return var/datum/gas_mixture/source = air1 var/datum/gas_mixture/sink = air2 @@ -78,17 +74,19 @@ Thus, the two variables affect pump operation are set in New(): //Calculate necessary moles to transfer using PV=nRT if(pressure_delta > 0.01 && (source.total_moles > 0) && (source.temperature > 0 || sink.temperature > 0)) - //Figure out how much gas to transfer + + var/transfer_moles = source.total_moles + /* TODO Uncomment this once we have a good way to get the volume of a pipe network. + //Figure out how much gas to transfer to meet the target pressure. var/air_temperature = (sink.temperature > 0)? sink.temperature : source.temperature - var/output_volume = sink.volume + var/output_volume = sink.volume * sink.group_multiplier //Return the number of moles that would have to be transfered to bring sink to the target pressure var/transfer_moles = pressure_delta*output_volume/(air_temperature * R_IDEAL_GAS_EQUATION) + */ - //Actually transfer the gas - - //Calculate the amount of energy required + //Calculate the amount of energy required and limit transfer_moles based on available power var/specific_power = calculate_specific_power(source, sink) //this has to be calculated before we modify any gas mixtures if (specific_power > 0) transfer_moles = min(transfer_moles, active_power_usage / specific_power) @@ -99,11 +97,11 @@ Thus, the two variables affect pump operation are set in New(): last_flow_rate = (removed.total_moles/(removed.total_moles + source.total_moles))*source.volume if (power_draw > 0) - sink.add_thermal_energy(power_draw) - handle_power_draw(power_draw) + removed.add_thermal_energy(power_draw) //1st law - energy is conserved + handle_pump_power_draw(power_draw) last_power_draw = power_draw else - handle_power_draw(idle_power_usage) + handle_pump_power_draw(idle_power_usage) last_power_draw = idle_power_usage sink.merge(removed) @@ -115,22 +113,12 @@ Thus, the two variables affect pump operation are set in New(): network2.update = 1 else update_use_power(0) + last_power_draw = 0 + last_flow_rate = 0 return 1 return 1 -//This proc handles power usages so that we only have to call use_power() when the pump is loaded but not at full load. -/obj/machinery/atmospherics/binary/pump/proc/handle_power_draw(var/usage_amount) - if (usage_amount > active_power_usage - 5) - update_use_power(2) - else - update_use_power(1) - - if (usage_amount > idle_power_usage) - use_power(usage_amount) //in practice it's pretty rare that we will get here, so calling use_power() is alright. - - last_power_draw = usage_amount - //Radio remote control /obj/machinery/atmospherics/binary/pump/proc/set_frequency(new_frequency) diff --git a/code/ATMOSPHERICS/components/unary/vent_pump.dm b/code/ATMOSPHERICS/components/unary/vent_pump.dm index 01b2654101..6e76ddcbd0 100644 --- a/code/ATMOSPHERICS/components/unary/vent_pump.dm +++ b/code/ATMOSPHERICS/components/unary/vent_pump.dm @@ -18,7 +18,7 @@ use_power = 1 idle_power_usage = 150 //internal circuitry, friction losses and stuff active_power_usage = 7500 //This also doubles as a measure of how powerful the pump is, in Watts. 7500 W ~ 10 HP - + var/area/initial_loc level = 1 var/area_uid @@ -125,24 +125,25 @@ update_icon() update_underlays() +/obj/machinery/atmospherics/unary/vent_pump/proc/can_pump() + if(stat & (NOPOWER|BROKEN)) + return 0 + if(!on) + return 0 + if(welded) + return 0 + return 1 + /obj/machinery/atmospherics/unary/vent_pump/process() ..() - //reset these each iteration - last_power_draw = 0 - last_flow_rate = 0 - - if(stat & (NOPOWER|BROKEN)) - return if (!node) on = 0 - if(!on) + if(!can_pump()) update_use_power(0) + last_power_draw = 0 + last_flow_rate = 0 return 0 - - if(welded) - return 0 - var/datum/gas_mixture/environment = loc.return_air() var/environment_pressure = environment.return_pressure() @@ -161,7 +162,7 @@ //Unfortunately there's no good way to get the volume of the room, so assume 10 tiles //We will overshoot in small rooms when dealing with huge pressures but it won't be so bad - var/output_volume = environment.volume * 10 + var/output_volume = environment.volume * environment.group_multiplier var/air_temperature = environment.temperature? environment.volume : air_contents.temperature var/transfer_moles = pressure_delta*output_volume/(air_temperature * R_IDEAL_GAS_EQUATION) @@ -172,7 +173,7 @@ if(pressure_checks & PRESSURE_CHECK_INTERNAL) pressure_delta = min(pressure_delta, internal_pressure_bound - air_contents.return_pressure()) //increasing the pressure here - var/output_volume = air_contents.volume + var/output_volume = air_contents.volume * air_contents.group_multiplier var/air_temperature = air_contents.temperature? air_contents.temperature : environment.temperature var/transfer_moles = pressure_delta*output_volume/(air_temperature * R_IDEAL_GAS_EQUATION) @@ -182,6 +183,8 @@ if(network) network.update = 1 else + last_power_draw = 0 + last_flow_rate = 0 update_use_power(0) return 1 @@ -215,15 +218,6 @@ //merge the removed gas into the sink sink.merge(removed) - - /* Uncomment this in case it actually matters whether we call assume_air() or just merge with the returned air directly - if (istype(sink, /datum/gas_mixture) - var/datum/gas_mixture/M = sink - M.merge(removed) - else if (istype(sink, /turf) - var/turf/T = sink - T.assume_air(removed) - */ //Radio remote control diff --git a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm index 9506da2459..ebdea2fc98 100644 --- a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm +++ b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm @@ -5,6 +5,8 @@ name = "Air Scrubber" desc = "Has a valve and pump attached to it" use_power = 1 + idle_power_usage = 150 //internal circuitry, friction losses and stuff + active_power_usage = 7500 //This also doubles as a measure of how powerful the pump is, in Watts. 7500 W ~ 10 HP level = 1 @@ -15,11 +17,8 @@ var/on = 0 var/scrubbing = 1 //0 = siphoning, 1 = scrubbing - var/scrub_CO2 = 1 - var/scrub_Toxins = 0 - var/scrub_N2O = 0 + var/list/scrubbing_gas = list() - var/volume_rate = 120 var/panic = 0 //is this scrubber panicked? var/area_uid @@ -90,9 +89,9 @@ "power" = on, "scrubbing" = scrubbing, "panic" = panic, - "filter_co2" = scrub_CO2, - "filter_phoron" = scrub_Toxins, - "filter_n2o" = scrub_N2O, + "filter_co2" = ("carbon_dioxide" in scrubbing_gas), + "filter_phoron" = ("phoron" in scrubbing_gas), + "filter_n2o" = ("sleeping_agent" in scrubbing_gas), "sigtype" = "status" ) if(!initial_loc.air_scrub_names[id_tag]) @@ -119,57 +118,79 @@ on = 0 //broadcast_status() if(!on) + update_use_power(0) return 0 - var/datum/gas_mixture/environment = loc.return_air() + if ((environment.total_moles == 0) || (environment.temperature == 0 && air_contents.temperature == 0)) + update_use_power(0) + return - if(scrubbing) - if((environment.gas["phoron"]>0.001) || (environment.gas["carbon_dioxide"]>0.001) || (environment.gas["oxygen_agent_b"]>0.001) || (environment.gas["sleeping_agent"]>0.001)) - var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles - - //Take a gas sample - var/datum/gas_mixture/removed = loc.remove_air(transfer_moles) - if (isnull(removed)) //in space - return - - //Filter it - var/datum/gas_mixture/filtered_out = new - filtered_out.temperature = removed.temperature - if(scrub_Toxins) - filtered_out.gas["phoron"] = removed.gas["phoron"] - removed.gas["phoron"] = 0 - if(scrub_CO2) - filtered_out.gas["carbon_dioxide"] = removed.gas["carbon_dioxide"] - removed.gas["carbon_dioxide"] = 0 - if(scrub_N2O) - filtered_out.gas["sleeping_agent"] = removed.gas["sleeping_agent"] - removed.gas["sleeping_agent"] = 0 - if(removed.gas["oxygen_agent_b"]) - filtered_out.gas["oxygen_agent_b"] = removed.gas["oxygen_agent_b"] - removed.gas["oxygen_agent_b"] = 0 - - //Remix the resulting gases - air_contents.merge(filtered_out) - - loc.assume_air(removed) - - if(network) - network.update = 1 - - else //Just siphoning all air - if (air_contents.return_pressure()>=50*ONE_ATMOSPHERE) + var/power_draw + if(scrubbing) + //Filter it + var/total_specific_power = 0 //the power required to remove one mole of filterable gas + var/total_filterable_moles = 0 + var/list/specific_power_gas = list() + for (var/g in scrubbing_gas) + if (environment.gas[g] < 0.1) + continue //don't bother + + var/specific_power = calculate_specific_power_gas(g, environment, air_contents) + specific_power_gas[g] = specific_power + total_specific_power += specific_power + total_filterable_moles += environment.gas[g] + + if (total_filterable_moles == 0) + update_use_power(0) return + + //Calculate the amount of energy required and limit transfer_moles based on available power + power_draw = 0 + var/total_transfer_moles = total_filterable_moles + if (total_specific_power > 0) + total_transfer_moles = min(total_transfer_moles, active_power_usage/total_specific_power) + + for (var/g in scrubbing_gas) + var/transfer_moles = environment.gas[g] + if (specific_power_gas[g] > 0) + //if our flow rate is limited by available power, the proportion of the filtered gas is based on mole ratio + transfer_moles = min(transfer_moles, total_transfer_moles*(environment.gas[g]/total_filterable_moles)) + + environment.gas[g] -= transfer_moles + air_contents.gas[g] += transfer_moles + power_draw += specific_power_gas[g]*transfer_moles + + //Remix the resulting gases + air_contents.update_values() + environment.update_values() - var/transfer_moles = environment.total_moles*(volume_rate/environment.volume) + else //Just siphon all air + var/transfer_moles = environment.total_moles - var/datum/gas_mixture/removed = loc.remove_air(transfer_moles) + //Calculate the amount of energy required + var/specific_power = calculate_specific_power(environment, air_contents) //this has to be calculated before we modify any gas mixtures + if (specific_power > 0) + transfer_moles = min(transfer_moles, active_power_usage / specific_power) + + if (transfer_moles < 0.01) + update_use_power(0) + return //don't bother - air_contents.merge(removed) - - if(network) - network.update = 1 + power_draw = specific_power*transfer_moles + air_contents.merge(environment.remove(transfer_moles)) + if (power_draw > 0) + air_contents.add_thermal_energy(power_draw) + //last_power_draw = power_draw + handle_pump_power_draw(power_draw) + else + //last_power_draw = idle_power_usage + handle_pump_power_draw(idle_power_usage) + + if(network) + network.update = 1 + return 1 /obj/machinery/atmospherics/unary/vent_scrubber/hide(var/i) //to make the little pipe section invisible, the icon changes. @@ -191,39 +212,39 @@ if(panic) on = 1 scrubbing = 0 - volume_rate = 2000 else scrubbing = 1 - volume_rate = initial(volume_rate) if(signal.data["toggle_panic_siphon"] != null) panic = !panic if(panic) on = 1 scrubbing = 0 - volume_rate = 2000 else scrubbing = 1 - volume_rate = initial(volume_rate) if(signal.data["scrubbing"] != null) scrubbing = text2num(signal.data["scrubbing"]) if(signal.data["toggle_scrubbing"]) scrubbing = !scrubbing - if(signal.data["co2_scrub"] != null) - scrub_CO2 = text2num(signal.data["co2_scrub"]) - if(signal.data["toggle_co2_scrub"]) - scrub_CO2 = !scrub_CO2 + var/list/toggle = list() + + if(!isnull(signal.data["co2_scrub"]) && text2num(signal.data["co2_scrub"]) != ("carbon_dioxide" in scrubbing_gas)) + toggle += "carbon_dioxide" + else if(signal.data["toggle_co2_scrub"]) + toggle += "carbon_dioxide" - if(signal.data["tox_scrub"] != null) - scrub_Toxins = text2num(signal.data["tox_scrub"]) - if(signal.data["toggle_tox_scrub"]) - scrub_Toxins = !scrub_Toxins + if(!isnull(signal.data["tox_scrub"]) && text2num(signal.data["tox_scrub"]) != ("phoron" in scrubbing_gas)) + toggle += "phoron" + else if(signal.data["toggle_tox_scrub"]) + toggle += "phoron" - if(signal.data["n2o_scrub"] != null) - scrub_N2O = text2num(signal.data["n2o_scrub"]) - if(signal.data["toggle_n2o_scrub"]) - scrub_N2O = !scrub_N2O + if(!isnull(signal.data["n2o_scrub"]) && text2num(signal.data["n2o_scrub"]) != ("sleeping_agent" in scrubbing_gas)) + toggle += "sleeping_agent" + else if(signal.data["toggle_n2o_scrub"]) + toggle += "sleeping_agent" + + scrubbing_gas ^= toggle if(signal.data["init"] != null) name = signal.data["init"] diff --git a/code/ZAS/_gas_mixture_xgm.dm b/code/ZAS/_gas_mixture_xgm.dm index e063950835..8d2e8d9c3e 100644 --- a/code/ZAS/_gas_mixture_xgm.dm +++ b/code/ZAS/_gas_mixture_xgm.dm @@ -89,8 +89,11 @@ for(var/g in gas) . += gas_data.specific_heat[g] * gas[g] -//Adds or removes thermal energy +//Adds or removes thermal energy. Returns the actual thermal energy change, as in the case of removing energy we can't go below TCMB. /datum/gas_mixture/proc/add_thermal_energy(var/thermal_energy) + if (temperature < TCMB || total_moles == 0) + return 0 + var/heat_capacity = heat_capacity() if (thermal_energy < 0) var/thermal_energy_limit = -(temperature - TCMB)*heat_capacity //ensure temperature does not go below TCMB @@ -103,27 +106,28 @@ return heat_capacity()*(new_temperature - temperature) //Technically vacuum doesn't have a specific entropy. Just use a really big number (infinity would be ideal) here so that it's easy to add gas to vacuum and hard to take gas out. -#define SPECIFIC_ENTROPY_VACUUM 15000 +#define SPECIFIC_ENTROPY_VACUUM 150000 -//Returns the ideal gas specific entropy of the whole mix +//Returns the ideal gas specific entropy of the whole mix. This is the entropy per mole of /mixed/ gas. /datum/gas_mixture/proc/specific_entropy() if (!gas.len || total_moles == 0) return SPECIFIC_ENTROPY_VACUUM . = 0 for(var/g in gas) - . += specific_entropy_gas(g) + var/ratio = gas[g] / total_moles + . += ratio * specific_entropy_gas(g) . /= total_moles -//Returns the ideal gas specific entropy of a specific gas in the mix +//Returns the ideal gas specific entropy of a specific gas in the mix. This is the entropy per mole of /pure/ gas. +//It's important not to get that mixed up with the mixed entropy, which takes into account mole ratios (I did, it was bad). /datum/gas_mixture/proc/specific_entropy_gas(var/gasid) - if (!(gasid in gas) || total_moles == 0) + if (!(gasid in gas) || gas[gasid] == 0) return SPECIFIC_ENTROPY_VACUUM //that gas isn't here - var/ratio = gas[gasid] / total_moles var/molar_mass = gas_data.molar_mass[gasid] var/specific_heat = gas_data.specific_heat[gasid] - return R_IDEAL_GAS_EQUATION * ratio * ( log( IDEAL_GAS_ENTROPY_CONSTANT * volume / gas[gasid] * sqrt( ( molar_mass * specific_heat * temperature ) ** 3 ) + 1 ) + 5/2 ) + return R_IDEAL_GAS_EQUATION * ( log( (IDEAL_GAS_ENTROPY_CONSTANT*volume/gas[gasid]) * sqrt((molar_mass*specific_heat*temperature)**3) + 1 ) + 5/2 ) //Updates the total_moles count and trims any empty gases. /datum/gas_mixture/proc/update_values() diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm index b0ddbd9f97..29aa09e633 100644 --- a/code/game/machinery/alarm.dm +++ b/code/game/machinery/alarm.dm @@ -802,7 +802,7 @@ Toxins: [phoron_percent]%
sensor_data += {" [long_name][state]
Operating: -[data["power"]?"on":"off"] Flow Rate: [data["flow_rate"]] L/s +[data["power"]?"on":"off"]
Pressure checks: external