From 3ea6b7cc2a00f55fef2071b4bc26ee4675718cb0 Mon Sep 17 00:00:00 2001
From: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Date: Fri, 21 May 2021 15:24:41 +0200
Subject: [PATCH] [MIRROR] Thermomachine process_atmos cleanup, several feature
debloating, some changes to motor heat, some UI additions. Basically
thermomachines, you get the point. (#5846)
* Thermomachine process_atmos cleanup, several feature debloating, some changes to motor heat, some UI additions. Basically thermomachines, you get the point. (#58967)
I rewrote parts of thermomachines process_atmos code to make it cleaner and more readable. Basic functionality and math should be the same.
I also removed temperature regulator related stuff and make it automatic instead, I think this reduces the bloat on the UI and unnecessary complexities.
Most of the changes are non player-facing, but there were a few math changes here and there to either prevent exploits or make more sense. These might impact gameplay, but not in a major way. One that might make a bit more difference is the power usage on very high temperature deltas, since I unified both enviroment_efficiency and normal efficiency together.
The examine message should now directly mention the heat capacity instead of efficiency to avoid confusion.
Wrench actions also call update_appearance now.
UI now displays cooling efficiency.
Enviromental heat and the aforementioned efficiency is displayed on cooling mode.
Old tank pumping into heat exchange has been removed.
Motor heat has been doubled and switch to only heat up the reservoir side. This is to fix gas temps hovering above but never reaching the target temperature.
The hovering above a certain temp occurs because there are cases where the temperature difference are above 1.5kelvins but the heat amount*efficiency part of the code is also equal or nearing 2500 joules. This is most evident on low heat capacity mixes, most notably toxins.
process_atmos: Tidier code, documentation, math checks.
regulator: Simpler operation
motor_heat: Allows for toxins to work and cool gases properly again.
efficiency display: More information, lets players understand whats wrong and how much their freezer compares to the old one before complaining.
tank pump removal: UI bloat, underused mechanic, not really evident without codediving
* Thermomachine process_atmos cleanup, several feature debloating, some changes to motor heat, some UI additions. Basically thermomachines, you get the point.
Co-authored-by: vincentiusvin <54709710+vincentiusvin@users.noreply.github.com>
---
.../binary_devices/thermomachine.dm | 205 +++++++-----------
.../packages/tgui/interfaces/ThermoMachine.js | 60 +++--
2 files changed, 108 insertions(+), 157 deletions(-)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/binary_devices/thermomachine.dm
index 89accea8432..1eea0d09533 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/thermomachine.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/thermomachine.dm
@@ -31,14 +31,15 @@
var/cooling = TRUE
var/base_heating = 140
var/base_cooling = 170
- var/obj/item/tank/holding
var/use_enviroment_heat = FALSE
var/skipping_work = FALSE
- var/auto_thermal_regulator = FALSE
var/safeties = TRUE
var/lastwarning
var/color_index = 1
+ // Efficiency dictates how much we throttle the heat exchange process.
+ var/efficiency = 1
+
/obj/machinery/atmospherics/components/binary/thermomachine/Initialize()
. = ..()
RefreshParts()
@@ -99,9 +100,6 @@
. = ..()
. += getpipeimage(icon, "pipe", dir, COLOR_LIME, piping_layer)
. += getpipeimage(icon, "pipe", turn(dir, 180), COLOR_MOSTLY_PURE_RED, piping_layer)
- if(holding)
- var/mutable_appearance/holding = mutable_appearance(icon, "holding")
- . += holding
if(skipping_work && on)
var/mutable_appearance/skipping = mutable_appearance(icon, "blinking")
. += skipping
@@ -115,21 +113,25 @@
. += "-use a multitool with left-click to change the piping layer and right-click to change the piping color."
. += "The thermostat is set to [target_temperature]K ([(T0C-target_temperature)*-1]C)."
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Efficiency [(heat_capacity/7500)*100]%."
+ . += "Heat capacity at [heat_capacity] Joules per Kelvin."
. += "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
- if(cooling)
- target_temperature = min_temperature
- investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
- to_chat(user, "You minimize the target temperature on [src] to [target_temperature] K.")
- else
- target_temperature = max_temperature
- investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
- to_chat(user, "You maximize the target temperature on [src] to [target_temperature] K.")
+ target_temperature = T20C
+ investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
+ to_chat(user, "You reset the target temperature on [src] 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
@@ -141,100 +143,90 @@
on = FALSE
update_appearance()
return
- var/datum/gas_mixture/enviroment = local_turf.return_air()
+ // The gas we want to cool/heat
var/datum/gas_mixture/main_port = airs[1]
- var/datum/gas_mixture/thermal_exchange_port = airs[2]
- var/main_heat_capacity = main_port.heat_capacity()
- var/thermal_heat_capacity = thermal_exchange_port.heat_capacity()
- var/temperature_delta = main_port.temperature - target_temperature
- if(auto_thermal_regulator)
- cooling = temperature_delta > 0
- else
- temperature_delta = cooling ? max(temperature_delta, 0) : min(temperature_delta, 0) //no cheesy strats
- var/motor_heat = 2500
- if(abs(temperature_delta) < 1.5) //allow the machine to work more finely
+ // 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()
- var/heat_amount = temperature_delta * (main_heat_capacity * heat_capacity / (main_heat_capacity + heat_capacity))
- var/efficiency = 1
- var/temperature_difference = 0
- var/skip_tick = TRUE
- if(!use_enviroment_heat && main_port.total_moles() > 0.01)
- if(cooling && thermal_exchange_port.total_moles() > 0.01 && nodes[2] && (thermal_exchange_port.temperature <= THERMOMACHINE_SAFE_TEMPERATURE || !safeties))
- thermal_exchange_port.temperature = max(thermal_exchange_port.temperature + heat_amount / thermal_heat_capacity + motor_heat / thermal_heat_capacity, TCMB)
- else if(cooling && (!thermal_exchange_port.total_moles() || !nodes[2]))
- skipping_work = skip_tick
- update_appearance()
- update_parents()
+ 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
+
+ if(cooling)
+ var/datum/gas_mixture/exchange_target
+ // 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() < 0.01)
+ skipping_work = TRUE
return
- if(thermal_exchange_port.temperature > THERMOMACHINE_SAFE_TEMPERATURE && safeties)
+
+ // 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)
+
+ if (exchange_target.temperature > THERMOMACHINE_SAFE_TEMPERATURE && safeties)
on = FALSE
- visible_message("The thermal exchange port's temperature has reached critical levels, shutting down...")
+ visible_message("The heat reservoir has reached critical levels, shutting down...")
update_appearance()
return
- else if(thermal_exchange_port.temperature > THERMOMACHINE_SAFE_TEMPERATURE && !safeties)
+
+ else if(exchange_target.temperature > THERMOMACHINE_SAFE_TEMPERATURE && !safeties)
if((REALTIMEOFDAY - lastwarning) / 5 >= WARNING_DELAY)
lastwarning = REALTIMEOFDAY
- visible_message("The thermal exchange port's temperature has reached critical levels!")
- if(check_explosion(thermal_exchange_port.temperature))
+ visible_message("The heat reservoir has reached critical levels!")
+ if(check_explosion(exchange_target.temperature))
explode()
- return PROCESS_KILL //we dying anyway, so let's stop processing
- temperature_difference = thermal_exchange_port.temperature - main_port.temperature
- temperature_difference = cooling ? temperature_difference : 0
- if(temperature_difference > 0)
- efficiency = max(1 - log(10, temperature_difference) * 0.08, 0.65)
- main_port.temperature = max(main_port.temperature - (heat_amount * efficiency)/ main_heat_capacity + motor_heat / main_heat_capacity, TCMB)
- skip_tick = FALSE
- if(use_enviroment_heat && main_port.total_moles() > 0.01)
- var/enviroment_efficiency = 1
- if(cooling && enviroment.total_moles() > 0.01 && (thermal_exchange_port.temperature <= THERMOMACHINE_SAFE_TEMPERATURE || !safeties))
- var/enviroment_heat_capacity = enviroment.heat_capacity()
- if(enviroment.total_moles())
- enviroment_efficiency = clamp(log(1.55, enviroment.total_moles()) * 0.15, 0.65, 1)
- enviroment.temperature = max(enviroment.temperature + heat_amount / enviroment_heat_capacity, TCMB)
- air_update_turf(FALSE, FALSE)
- else if(cooling && !enviroment.total_moles())
- skipping_work = skip_tick
- update_appearance()
- update_parents()
- return
- if(enviroment.temperature > THERMOMACHINE_SAFE_TEMPERATURE && safeties)
- on = FALSE
- visible_message("The enviroment's temperature has reached critical levels, shutting down...")
- update_appearance()
- return
- else if(enviroment.temperature > THERMOMACHINE_SAFE_TEMPERATURE && !safeties)
- if((REALTIMEOFDAY - lastwarning) / 5 >= WARNING_DELAY)
- lastwarning = REALTIMEOFDAY
- visible_message("The enviroment's temperature has reached critical levels!")
- if(check_explosion(enviroment.temperature))
- explode()
- return PROCESS_KILL //we dying anyway, so let's stop processing
- temperature_difference = enviroment.temperature - main_port.temperature
- temperature_difference = cooling ? temperature_difference : 0
- if(temperature_difference > 0)
- efficiency = max(1 - log(10, temperature_difference) * 0.08, 0.65)
- main_port.temperature = max(main_port.temperature - (heat_amount * efficiency * enviroment_efficiency) / main_heat_capacity + motor_heat / main_heat_capacity, TCMB)
- skip_tick = FALSE
+ 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)
- skipping_work = skip_tick
+ main_port.temperature = max((THERMAL_ENERGY(main_port) + (heat_amount * efficiency)) / main_port.heat_capacity(), TCMB)
heat_amount = abs(heat_amount)
var/power_usage = 0
- if(temperature_delta > 1)
+ if(abs(temperature_target_delta) > 1)
power_usage = (heat_amount * 0.35 + idle_power_usage) ** (1.25 - (5e7 * efficiency) / (max(5e7, heat_amount)))
else
power_usage = idle_power_usage
if(power_usage > 1e6)
power_usage *= efficiency
+
use_power(power_usage)
update_appearance()
update_parents()
/obj/machinery/atmospherics/components/binary/thermomachine/attackby(obj/item/item, mob/user, params)
- if(!on && !holding && item.tool_behaviour == TOOL_SCREWDRIVER)
+ if(!on && item.tool_behaviour == TOOL_SCREWDRIVER)
if(!anchored)
to_chat(user, "Anchor [src] first!")
return
@@ -246,16 +238,6 @@
if(default_deconstruction_crowbar(item))
return
- if(istype(item, /obj/item/tank))
- var/obj/item/tank/tank = item
- if(!user.transferItemToLoc(tank, src))
- return FALSE
- to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [tank]" : "You insert [tank] into [src]"].")
- investigate_log("had its internal [holding] swapped with [tank] by [key_name(user)].", INVESTIGATE_ATMOS)
- replace_tank(user, tank)
- update_appearance()
- 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, "You change the circuitboard to layer [piping_layer].")
@@ -267,6 +249,7 @@
if(!..())
return FALSE
SetInitDirections()
+ update_appearance()
return TRUE
/obj/machinery/atmospherics/components/binary/thermomachine/proc/change_pipe_connection(disconnect)
@@ -358,17 +341,6 @@
obj_flags |= EMAGGED
safeties = FALSE
-/obj/machinery/atmospherics/components/binary/thermomachine/proc/replace_tank(mob/living/user, obj/item/tank/new_tank)
- if(!user)
- return FALSE
- if(holding)
- user.put_in_hands(holding)
- holding = null
- if(new_tank)
- holding = new_tank
- update_appearance()
- return TRUE
-
/obj/machinery/atmospherics/components/binary/thermomachine/proc/check_explosion(temperature)
if(temperature < THERMOMACHINE_SAFE_TEMPERATURE + 2000)
return FALSE
@@ -378,11 +350,11 @@
/obj/machinery/atmospherics/components/binary/thermomachine/proc/explode()
explosion(loc, 0, 0, 3, 3, TRUE)
var/datum/gas_mixture/main_port = airs[1]
- var/datum/gas_mixture/thermal_exchange_port = airs[2]
+ var/datum/gas_mixture/exchange_target = airs[2]
if(main_port)
loc.assume_air(main_port.remove_ratio(1))
- if(thermal_exchange_port)
- loc.assume_air(thermal_exchange_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)
@@ -411,15 +383,10 @@
var/datum/gas_mixture/air1 = airs[1]
data["temperature"] = air1.temperature
data["pressure"] = air1.return_pressure()
+ data["efficiency"] = efficiency
- data["holding"] = holding ? TRUE : FALSE
- data["tank_gas"] = FALSE
- if(holding)
- var/datum/gas_mixture/holding_mix = holding.return_air()
- data["tank_gas"] = !!holding_mix.total_moles()
data["use_env_heat"] = use_enviroment_heat
data["skipping_work"] = skipping_work
- data["auto_thermal_regulator"] = auto_thermal_regulator
data["safeties"] = safeties
var/hacked = (obj_flags & EMAGGED) ? TRUE : FALSE
data["hacked"] = hacked
@@ -456,23 +423,9 @@
if(.)
target_temperature = clamp(target, min_temperature, max_temperature)
investigate_log("was set to [target_temperature] K by [key_name(usr)]", INVESTIGATE_ATMOS)
- if("pumping")
- if(holding && nodes[2])
- var/datum/gas_mixture/thermal_exchange_port = airs[2]
- var/datum/gas_mixture/holding_mix = holding.return_air()
- var/datum/gas_mixture/remove = holding_mix.remove_ratio(1)
- thermal_exchange_port.merge(remove)
- . = TRUE
- if("eject")
- if(holding)
- replace_tank(usr)
- . = TRUE
if("use_env_heat")
use_enviroment_heat = !use_enviroment_heat
. = TRUE
- if("auto_thermal_regulator")
- auto_thermal_regulator = !auto_thermal_regulator
- . = TRUE
if("safeties")
safeties = !safeties
investigate_log("[key_name(usr)] turned off the [src] safeties", INVESTIGATE_ATMOS)
diff --git a/tgui/packages/tgui/interfaces/ThermoMachine.js b/tgui/packages/tgui/interfaces/ThermoMachine.js
index 49536c95511..87043b8cd26 100644
--- a/tgui/packages/tgui/interfaces/ThermoMachine.js
+++ b/tgui/packages/tgui/interfaces/ThermoMachine.js
@@ -1,6 +1,6 @@
import { toFixed } from 'common/math';
import { useBackend } from '../backend';
-import { AnimatedNumber, Box, Button, LabeledList, Modal, NumberInput, Section } from '../components';
+import { AnimatedNumber, Box, Button, LabeledList, Modal, NumberInput, Section, ProgressBar } from '../components';
import { Window } from '../layouts';
export const ThermoMachine = (props, context) => {
@@ -16,6 +16,29 @@ export const ThermoMachine = (props, context) => {
);
+ const cooling_efficiency_infos = !!data.cooling &&(
+
+
+ {Math.round(data.efficiency * 10000)/100 + " %"}
+
+
+ );
+ const cooling_enviroment_reservoir = !!data.cooling &&(
+
+
+ );
return (
{
format={value => toFixed(value, 2)} />
{' kPa'}
+
+ {data.cooling? 'Cooling' : 'Heating'}
+
+ {cooling_efficiency_infos}
{
disabled={!data.hacked}
onClick={() => act('safeties')} />
-
-
-
-
-
-
-
-
+ {cooling_enviroment_reservoir}