[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>
This commit is contained in:
SkyratBot
2021-05-21 14:24:41 +01:00
committed by GitHub
co-authored by vincentiusvin
parent d579160f1d
commit 3ea6b7cc2a
2 changed files with 108 additions and 157 deletions
@@ -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 @@
. += "<span class='notice'>-use a multitool with left-click to change the piping layer and right-click to change the piping color.</span>"
. += "<span class='notice'>The thermostat is set to [target_temperature]K ([(T0C-target_temperature)*-1]C).</span>"
if(in_range(user, src) || isobserver(user))
. += "<span class='notice'>The status display reads: Efficiency <b>[(heat_capacity/7500)*100]%</b>.</span>"
. += "<span class='notice'>Heat capacity at <b>[heat_capacity] Joules per Kelvin</b>.</span>"
. += "<span class='notice'>Temperature range <b>[min_temperature]K - [max_temperature]K ([(T0C-min_temperature)*-1]C - [(T0C-max_temperature)*-1]C)</b>.</span>"
/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, "<span class='notice'>You minimize the target temperature on [src] to [target_temperature] K.</span>")
else
target_temperature = max_temperature
investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
to_chat(user, "<span class='notice'>You maximize the target temperature on [src] to [target_temperature] K.</span>")
target_temperature = T20C
investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
to_chat(user, "<span class='notice'>You reset the target temperature on [src] to [target_temperature] K.</span>")
/** 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("<span class='warning'>The thermal exchange port's temperature has reached critical levels, shutting down...</span>")
visible_message("<span class='warning'>The heat reservoir has reached critical levels, shutting down...</span>")
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("<span class='warning'>The thermal exchange port's temperature has reached critical levels!</span>")
if(check_explosion(thermal_exchange_port.temperature))
visible_message("<span class='warning'>The heat reservoir has reached critical levels!</span>")
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("<span class='warning'>The enviroment's temperature has reached critical levels, shutting down...</span>")
update_appearance()
return
else if(enviroment.temperature > THERMOMACHINE_SAFE_TEMPERATURE && !safeties)
if((REALTIMEOFDAY - lastwarning) / 5 >= WARNING_DELAY)
lastwarning = REALTIMEOFDAY
visible_message("<span class='warning'>The enviroment's temperature has reached critical levels!</span>")
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, "<span class='notice'>Anchor [src] first!</span>")
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, "<span class='notice'>[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [tank]" : "You insert [tank] into [src]"].</span>")
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, "<span class='notice'>You change the circuitboard to layer [piping_layer].</span>")
@@ -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)
+29 -31
View File
@@ -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) => {
</Box>
</Modal>
);
const cooling_efficiency_infos = !!data.cooling &&(
<LabeledList.Item label="Cooling Efficiency">
<ProgressBar
value={data.efficiency}
minValue={.4225}
maxValue={1}
ranges={{
good: [.826, 1],
average: [.65, .825],
bad: [.4225, .64],
}}>
{Math.round(data.efficiency * 10000)/100 + " %"}
</ProgressBar>
</LabeledList.Item>
);
const cooling_enviroment_reservoir = !!data.cooling &&(
<LabeledList.Item label="Enviroment as heat reservoir">
<Button
content={data.use_env_heat ? 'On' : 'Off'}
selected={data.use_env_heat}
onClick={() => act('use_env_heat')} />
</LabeledList.Item>
);
return (
<Window
width={300}
@@ -36,6 +59,10 @@ export const ThermoMachine = (props, context) => {
format={value => toFixed(value, 2)} />
{' kPa'}
</LabeledList.Item>
<LabeledList.Item label="Mode">
{data.cooling? 'Cooling' : 'Heating'}
</LabeledList.Item>
{cooling_efficiency_infos}
</LabeledList>
</Section>
<Section
@@ -55,36 +82,7 @@ export const ThermoMachine = (props, context) => {
disabled={!data.hacked}
onClick={() => act('safeties')} />
</LabeledList.Item>
<LabeledList.Item label="Use tank gas">
<Button
content={data.tank_gas ? 'Push gas' : 'Empty'}
selected={data.tank_gas}
disabled={!data.tank_gas}
onClick={() => act('pumping')} />
</LabeledList.Item>
<LabeledList.Item label="Eject tank gas">
<Button
content={data.holding ? 'Eject tank' : 'Empty'}
disabled={!data.holding}
onClick={() => act('eject')} />
</LabeledList.Item>
<LabeledList.Item label="Use enviromental heat">
<Button
content={data.use_env_heat ? 'On' : 'Off'}
selected={data.use_env_heat}
onClick={() => act('use_env_heat')} />
</LabeledList.Item>
<LabeledList.Item label="Thermal setting">
<Button
content={data.auto_thermal_regulator ? 'Auto' : 'Off'}
selected={data.auto_thermal_regulator}
onClick={() => act('auto_thermal_regulator')} />
<Button
content={data.cooling ? 'Cooling' : 'Heating'}
disabled={data.auto_thermal_regulator}
selected={data.cooling}
onClick={() => act('cooling')} />
</LabeledList.Item>
{cooling_enviroment_reservoir}
<LabeledList.Item label="Target Temperature">
<NumberInput
animated