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 &&( + +