From f765755400cbda9b6b8d132bb657563db794654b Mon Sep 17 00:00:00 2001 From: NataKilar Date: Fri, 29 Apr 2022 16:53:51 -0400 Subject: [PATCH 1/5] Adds stirling engines --- .../weapons/circuitboards/machinery/power.dm | 13 +- .../objects/items/weapons/tanks/tank_types.dm | 8 +- .../game/objects/items/weapons/tanks/tanks.dm | 1 + .../components/unary/heat_exchanger.dm | 2 +- .../designs/general/designs_engineering.dm | 11 +- .../designs/general/designs_general.dm | 3 + .../fabrication/designs/pipe/pipe_datums.dm | 2 +- code/modules/power/stirling.dm | 218 ++++++++++++++++++ icons/obj/items/tanks/tank_stirling.dmi | Bin 0 -> 710 bytes nebula.dme | 1 + 10 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 code/modules/power/stirling.dm create mode 100644 icons/obj/items/tanks/tank_stirling.dmi diff --git a/code/game/objects/items/weapons/circuitboards/machinery/power.dm b/code/game/objects/items/weapons/circuitboards/machinery/power.dm index 46a04d8e228..1c7cfd813f4 100644 --- a/code/game/objects/items/weapons/circuitboards/machinery/power.dm +++ b/code/game/objects/items/weapons/circuitboards/machinery/power.dm @@ -153,4 +153,15 @@ /obj/item/stock_parts/matter_bin/super = 2, /obj/item/stock_parts/console_screen = 1, /obj/item/stack/cable_coil = 5 - ) \ No newline at end of file + ) + +/obj/item/stock_parts/circuitboard/unary_atmos/stirling + name = "circuit board (stirling engine)" + build_path = /obj/machinery/atmospherics/binary/stirling + board_type = "machine" + origin_tech = "{'engineering':2,'powerstorage':1}" + req_components = list( + /obj/item/stack/cable_coil = 20, + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 2 + ) \ No newline at end of file diff --git a/code/game/objects/items/weapons/tanks/tank_types.dm b/code/game/objects/items/weapons/tanks/tank_types.dm index 7be8ca8a186..180ae551553 100644 --- a/code/game/objects/items/weapons/tanks/tank_types.dm +++ b/code/game/objects/items/weapons/tanks/tank_types.dm @@ -22,6 +22,9 @@ desc = "A tank of oxygen. This one is yellow." icon = 'icons/obj/items/tanks/tank_yellow.dmi' +/obj/item/tank/oxygen/empty + starting_pressure = list() + /* * Air */ @@ -45,6 +48,9 @@ slot_flags = null starting_pressure = list(/decl/material/gas/hydrogen = 3*ONE_ATMOSPHERE) +/obj/item/tank/hydrogen/empty + starting_pressure = list() + /* * Emergency Oxygen */ @@ -96,4 +102,4 @@ icon = 'icons/obj/items/tanks/tank_red.dmi' distribute_pressure = ONE_ATMOSPHERE*O2STANDARD starting_pressure = list(/decl/material/gas/nitrogen = 10*ONE_ATMOSPHERE) - volume = 180 + volume = 180 \ No newline at end of file diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm index 387be5f56af..067989824ff 100644 --- a/code/game/objects/items/weapons/tanks/tanks.dm +++ b/code/game/objects/items/weapons/tanks/tanks.dm @@ -23,6 +23,7 @@ var/global/list/global/tank_gauge_cache = list() name = "tank" icon = 'icons/obj/items/tanks/tank_blue.dmi' icon_state = ICON_STATE_WORLD + material = /decl/material/solid/metal/steel var/gauge_icon = "indicator_tank" var/gauge_cap = 6 diff --git a/code/modules/atmospherics/components/unary/heat_exchanger.dm b/code/modules/atmospherics/components/unary/heat_exchanger.dm index 8176ba5ad70..c788114760a 100644 --- a/code/modules/atmospherics/components/unary/heat_exchanger.dm +++ b/code/modules/atmospherics/components/unary/heat_exchanger.dm @@ -10,7 +10,7 @@ var/obj/machinery/atmospherics/unary/heat_exchanger/partner = null var/update_cycle - connect_types = CONNECT_TYPE_REGULAR + connect_types = CONNECT_TYPE_REGULAR | CONNECT_TYPE_FUEL build_icon_state = "heunary" frame_type = /obj/item/pipe diff --git a/code/modules/fabrication/designs/general/designs_engineering.dm b/code/modules/fabrication/designs/general/designs_engineering.dm index ef0c17b8a0a..4c07cdc1a47 100644 --- a/code/modules/fabrication/designs/general/designs_engineering.dm +++ b/code/modules/fabrication/designs/general/designs_engineering.dm @@ -118,4 +118,13 @@ path = /obj/item/frame/wall_router /datum/fabricator_recipe/engineering/wall_relay - path = /obj/item/frame/wall_relay \ No newline at end of file + path = /obj/item/frame/wall_relay + +/datum/fabricator_recipe/engineering/oxygen_tank + path = /obj/item/tank/oxygen/empty + +/datum/fabricator_recipe/engineering/hydrogen_tank + path = /obj/item/tank/hydrogen/empty + +/datum/fabricator_recipe/engineering/stirling_piston + path = /obj/item/tank/stirling/empty \ No newline at end of file diff --git a/code/modules/fabrication/designs/general/designs_general.dm b/code/modules/fabrication/designs/general/designs_general.dm index 841e0573696..5d011bf9b29 100644 --- a/code/modules/fabrication/designs/general/designs_general.dm +++ b/code/modules/fabrication/designs/general/designs_general.dm @@ -153,3 +153,6 @@ fabricator_types = list( FABRICATOR_CLASS_GENERAL ) + +/datum/fabricator_recipe/emergency_tank + path = /obj/item/tank/emergency diff --git a/code/modules/fabrication/designs/pipe/pipe_datums.dm b/code/modules/fabrication/designs/pipe/pipe_datums.dm index 2d28467bbd8..58ca5c3bde8 100644 --- a/code/modules/fabrication/designs/pipe/pipe_datums.dm +++ b/code/modules/fabrication/designs/pipe/pipe_datums.dm @@ -252,7 +252,7 @@ /datum/fabricator_recipe/pipe/he/exchanger name = "heat exchanger" desc = "a heat exchanger" - connect_types = CONNECT_TYPE_REGULAR + connect_types = CONNECT_TYPE_REGULAR|CONNECT_TYPE_FUEL build_icon_state = "heunary" constructed_path = /obj/machinery/atmospherics/unary/heat_exchanger pipe_class = PIPE_CLASS_UNARY diff --git a/code/modules/power/stirling.dm b/code/modules/power/stirling.dm new file mode 100644 index 00000000000..344923457f3 --- /dev/null +++ b/code/modules/power/stirling.dm @@ -0,0 +1,218 @@ +#define HEAT_TRANSFER 5000 +#define MAX_FREQUENCY 60 +#define MIN_DELTA_T 5 + +/obj/machinery/atmospherics/binary/stirling + name = "stirling engine" + desc = "A mechanical stirling generator. It generates power dependent on the temperature differential between input gas and a radiative heatsink" + icon = 'icons/obj/power.dmi' + icon_state = "stirling" + density = 1 + anchored = 1 + layer = STRUCTURE_LAYER + use_power = POWER_USE_OFF + base_type = /obj/machinery/atmospherics/binary/stirling + construct_state = /decl/machine_construction/default/panel_closed + uncreated_component_parts = null + stat_immune = NOSCREEN | NOINPUT | NOPOWER // Mechanical machine and display. + connect_types = CONNECT_TYPE_REGULAR|CONNECT_TYPE_FUEL + + var/obj/item/tank/stirling/inserted_cylinder + + var/cycle_frequency = MAX_FREQUENCY/2 + + var/max_power = 100000 + var/genlev = 0 + var/lastgen = 0 + var/skipped_cycle = FALSE + + var/sound_id + var/datum/sound_token/sound_token + +/obj/machinery/atmospherics/binary/stirling/Destroy() + QDEL_NULL(inserted_cylinder) + QDEL_NULL(sound_token) + . = ..() + +/obj/machinery/atmospherics/binary/stirling/Process() + ..() + + // Some temperature equilibrium between the gas lines. + var/air1_eq = air1.get_thermal_energy_change(air2.temperature) + var/air2_eq = air2.get_thermal_energy_change(air1.temperature) + + var/heat_transfer = clamp(HEAT_TRANSFER*(air1.temperature - air2.temperature), min(air1_eq, air2_eq), max(air1_eq, air2_eq)) + if(heat_transfer) + air1.add_thermal_energy(-heat_transfer) + air2.add_thermal_energy(heat_transfer) + + if(!istype(inserted_cylinder)) + return + + if(use_power != POWER_USE_ACTIVE) + update_icon() + update_sound() + return + + var/datum/gas_mixture/working_volume = inserted_cylinder.air_contents + + if(stat & (BROKEN) || !air1.total_moles || !air2.total_moles || !working_volume.total_moles) + stop_engine() + return + + // A rough approximation of a Stirling cycle with perfect regeneration e.g. Carnot efficiency. + var/working_heatcap = working_volume.heat_capacity() + var/line1_heatcap = air1.heat_capacity() + var/line2_heatcap = air2.heat_capacity() + + // Bring the internal tank in thermal equilibrium with the hottest line. The temperature/pressure + // is kept at the maximum constantly. + var/equil_transfer + + if(air1.temperature >= air2.temperature) + equil_transfer = (working_heatcap*line1_heatcap)/(working_heatcap + line1_heatcap)*(air1.temperature - working_volume.temperature) + air1.add_thermal_energy(-equil_transfer) + else + equil_transfer = (working_heatcap*line2_heatcap)/(working_heatcap + line2_heatcap)*(air2.temperature - working_volume.temperature) + air2.add_thermal_energy(-equil_transfer) + + working_volume.add_thermal_energy(equil_transfer) + + // The cycle requires a minimum temperature to overcome friction. + if(abs(air1.temperature - air2.temperature) < MIN_DELTA_T) + if(skipped_cycle) // Allow some leeway since networks can take some time to update. + stop_engine() + skipped_cycle = TRUE + return + skipped_cycle = FALSE + + // Positive/negative Work from volume expansion/compression. Maximum volume is 1.5 tank volume. The volume of the tank is not actually adjusted. + var/work_coefficient = working_volume.get_total_moles()*R_IDEAL_GAS_EQUATION*log(1.5) + var/work_done = work_coefficient*abs(air1.temperature - air2.temperature)*cycle_frequency + + var/reversed = air1.temperature < air2.temperature ? 1 : -1 + + var/air1_dq = reversed*work_coefficient*air1.temperature*cycle_frequency + var/air2_dq = -reversed*work_coefficient*air2.temperature*cycle_frequency + + var/power_generated = 0.75*work_done + // Excessive power is transferred as heat to the opposite side, reducing efficiency. + if(power_generated > max_power) + if(reversed) + air1_dq += 0.75*(max_power - power_generated) + else + air2_dq += 0.75*(max_power - power_generated) + power_generated = max_power + if(prob(5)) + spark_at(src, cardinal_only = TRUE) + + air1.add_thermal_energy(air1_dq) + air2.add_thermal_energy(air2_dq) + + generate_power(power_generated) + lastgen = power_generated + genlev = max(0, min(round(4*power_generated / max_power), 4)) + + update_icon() + update_sound() + update_networks() + +/obj/machinery/atmospherics/binary/stirling/examine(mob/user, distance) + . = ..() + if(distance > 1) + return + + to_chat(user, "\The [src] is generating [round(lastgen/1000, 0.1)] kW") + if(!inserted_cylinder) + to_chat(user, "There is no piston cylinder inserted into \the [src].") + +/obj/machinery/atmospherics/binary/stirling/attackby(var/obj/item/W, var/mob/user) + if((istype(W, /obj/item/tank/stirling))) + if(inserted_cylinder) + return + if(!user.unEquip(W, src)) + return + to_chat(user, SPAN_NOTICE("You insert \the [W] into \the [src].")) + inserted_cylinder = W + update_icon() + return TRUE + + if(isCrowbar(W) && inserted_cylinder) + inserted_cylinder.dropInto(get_turf(src)) + to_chat(user, SPAN_NOTICE("You remove \the [inserted_cylinder] from \the [src].")) + inserted_cylinder = null + stop_engine() + return TRUE + + if(panel_open && isWrench(W)) + var/target_frequency = input(user, "Enter the cycle frequency you would like \the [src] to operate at ([MAX_FREQUENCY/4] - [MAX_FREQUENCY] Hz)", "Stirling Frequency", cycle_frequency) as num | null + if(!CanPhysicallyInteract(user) || !target_frequency) + return + cycle_frequency = round(clamp(target_frequency, MAX_FREQUENCY/4, MAX_FREQUENCY)) + to_chat(usr, SPAN_NOTICE("You adjust \the [src] to operate at a frequency of [cycle_frequency] Hz.")) + return TRUE + + . = ..() + +/obj/machinery/atmospherics/binary/stirling/attack_hand(mob/user) + if(!(stat & BROKEN) && use_power != POWER_USE_ACTIVE) + if(!inserted_cylinder) + to_chat(user, SPAN_WARNING("You must insert a stirling piston cylinder into \the [src] before you can start it!")) + return + to_chat(user, "You start trying to manually rev up \the [src].") + if(do_after(user, 2 SECONDS, src) && use_power != POWER_USE_ACTIVE && inserted_cylinder && !(stat & BROKEN)) + visible_message("[user] pulls on the starting cord of \the [src], revving it up!", "You pull on the starting cord of \the [src], revving it up!") + playsound(src.loc, 'sound/machines/engine.ogg', 35, 1) + update_use_power(POWER_USE_ACTIVE) + return + . = ..() + +/obj/machinery/atmospherics/binary/stirling/on_update_icon() + cut_overlays() + if (stat & (BROKEN)) + return + if (genlev != 0) + add_overlay(emissive_overlay('icons/obj/power.dmi', "stirling-op[genlev]")) + +/obj/machinery/atmospherics/binary/stirling/proc/update_sound() + if(!sound_id) + sound_id = "[type]_[sequential_id(/obj/machinery/atmospherics/binary/stirling)]" + if(use_power == POWER_USE_ACTIVE) + var/volume = 10 + 15*genlev + if(!sound_token) + sound_token = play_looping_sound(src, sound_id, 'sound/machines/engine.ogg', volume = volume) + sound_token.SetVolume(volume) + else if(sound_token) + QDEL_NULL(sound_token) + +/obj/machinery/atmospherics/binary/stirling/proc/stop_engine() + skipped_cycle = FALSE + if(use_power == POWER_USE_ACTIVE) + visible_message(SPAN_WARNING("\The [src] sputters to a violent halt!")) + update_use_power(POWER_USE_IDLE) + update_sound() + update_icon() + +/obj/machinery/atmospherics/binary/stirling/dismantle() + if(inserted_cylinder) + inserted_cylinder.dropInto(get_turf(src)) + . = ..() + +/obj/item/tank/stirling + name = "stirling piston cylinder" + desc = "A piston cylinder designed for use in a stirling engine. It must be charged with gas before it can be used. Rated for temperatures up to 1000 C" + icon = 'icons/obj/items/tanks/tank_stirling.dmi' + gauge_icon = null + obj_flags = OBJ_FLAG_CONDUCTIBLE + slot_flags = null + starting_pressure = list(/decl/material/gas/hydrogen = 2*ONE_ATMOSPHERE) + + volume = 30 + failure_temp = 1000 + +/obj/item/tank/stirling/empty + starting_pressure = list() + +#undef HEAT_TRANSFER +#undef MAX_FREQUENCY +#undef MIN_DELTA_T \ No newline at end of file diff --git a/icons/obj/items/tanks/tank_stirling.dmi b/icons/obj/items/tanks/tank_stirling.dmi new file mode 100644 index 0000000000000000000000000000000000000000..5280aef46eed70bf45c0a3df2d13c681deb45126 GIT binary patch literal 710 zcmV;%0y+JOP)fFDZ*Bkpc$`yKaB_9`^iy#0_2eo`Eh^5;&r`5fFwryM z;w;ZhDainGjE%TBGg33tGfE(w;*!LYR3KBSJijO>MTv_uC9|j)$T#HTOe;#vO@*-G zsxnG*6Z7&jQxuZ&Qz~_fbMi~#bK)}+^N@6!5UsNauTEtJS3ei9R{&q@KPhsV#CZS! z0kcU&K~z|U?Uz4J!!Q`d4P%-CI>83C7w8c{;tViz0+?)7-D5?p?Acre2Ifjg-HM{# zfXm>jN&5Y>B2Senzp0<|o}Zga{;5ixmM&uhlZ!b2?f5>*fByI~jjEVFO~wx~1+(I9 zb`ue}zOwo*!hiN*3mx+nmd{oj6!;DfJ7v}vyu+2zUuf7DSYNP)E2|prSCa}}pq#D( zgIu7T1)gPr144mvCD@?h0a~E4FEHdae&~3CQa;dbTd5ou)XuO1U$M^T`wt7%kOP&P z7x;?J{^YLM;)1*|ZovxtMy3texgr5w3V!cwk3@e2^(!4{=WLBNeUMu@5Y|>aQBqRU zvBdZ=;NX0qA`Zp}rWL+g7$2C3gYrQLSmdC55CZt}qn zoCtjA0tR8A!->ELC9Zckk@z65yFMR!MP$9hNm5eMNoW)s_!)){R{c3>so;=l`MQI` z7hm9hgwn5I$d|(bSHh{O1_J Date: Sun, 1 May 2022 13:06:31 -0400 Subject: [PATCH 2/5] Adjusts stirling power use --- code/modules/power/stirling.dm | 40 +++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/code/modules/power/stirling.dm b/code/modules/power/stirling.dm index 344923457f3..e7c251ba28e 100644 --- a/code/modules/power/stirling.dm +++ b/code/modules/power/stirling.dm @@ -10,7 +10,6 @@ density = 1 anchored = 1 layer = STRUCTURE_LAYER - use_power = POWER_USE_OFF base_type = /obj/machinery/atmospherics/binary/stirling construct_state = /decl/machine_construction/default/panel_closed uncreated_component_parts = null @@ -20,10 +19,13 @@ var/obj/item/tank/stirling/inserted_cylinder var/cycle_frequency = MAX_FREQUENCY/2 + var/active = FALSE var/max_power = 100000 var/genlev = 0 - var/lastgen = 0 + var/last_genlev + + var/last_gen = 0 var/skipped_cycle = FALSE var/sound_id @@ -48,10 +50,7 @@ if(!istype(inserted_cylinder)) return - - if(use_power != POWER_USE_ACTIVE) - update_icon() - update_sound() + if(!active) return var/datum/gas_mixture/working_volume = inserted_cylinder.air_contents @@ -110,11 +109,12 @@ air2.add_thermal_energy(air2_dq) generate_power(power_generated) - lastgen = power_generated + last_gen = power_generated genlev = max(0, min(round(4*power_generated / max_power), 4)) - - update_icon() - update_sound() + if(genlev != last_genlev) + update_icon() + update_sound() + last_genlev = genlev update_networks() /obj/machinery/atmospherics/binary/stirling/examine(mob/user, distance) @@ -122,7 +122,7 @@ if(distance > 1) return - to_chat(user, "\The [src] is generating [round(lastgen/1000, 0.1)] kW") + to_chat(user, "\The [src] is generating [round(last_gen/1000, 0.1)] kW") if(!inserted_cylinder) to_chat(user, "There is no piston cylinder inserted into \the [src].") @@ -155,15 +155,15 @@ . = ..() /obj/machinery/atmospherics/binary/stirling/attack_hand(mob/user) - if(!(stat & BROKEN) && use_power != POWER_USE_ACTIVE) + if(!(stat & BROKEN) && !active) if(!inserted_cylinder) to_chat(user, SPAN_WARNING("You must insert a stirling piston cylinder into \the [src] before you can start it!")) return to_chat(user, "You start trying to manually rev up \the [src].") - if(do_after(user, 2 SECONDS, src) && use_power != POWER_USE_ACTIVE && inserted_cylinder && !(stat & BROKEN)) + if(do_after(user, 2 SECONDS, src) && !active && inserted_cylinder && !(stat & BROKEN)) visible_message("[user] pulls on the starting cord of \the [src], revving it up!", "You pull on the starting cord of \the [src], revving it up!") playsound(src.loc, 'sound/machines/engine.ogg', 35, 1) - update_use_power(POWER_USE_ACTIVE) + active = TRUE return . = ..() @@ -177,7 +177,7 @@ /obj/machinery/atmospherics/binary/stirling/proc/update_sound() if(!sound_id) sound_id = "[type]_[sequential_id(/obj/machinery/atmospherics/binary/stirling)]" - if(use_power == POWER_USE_ACTIVE) + if(active) var/volume = 10 + 15*genlev if(!sound_token) sound_token = play_looping_sound(src, sound_id, 'sound/machines/engine.ogg', volume = volume) @@ -187,9 +187,9 @@ /obj/machinery/atmospherics/binary/stirling/proc/stop_engine() skipped_cycle = FALSE - if(use_power == POWER_USE_ACTIVE) + if(active) visible_message(SPAN_WARNING("\The [src] sputters to a violent halt!")) - update_use_power(POWER_USE_IDLE) + active = FALSE update_sound() update_icon() @@ -200,7 +200,7 @@ /obj/item/tank/stirling name = "stirling piston cylinder" - desc = "A piston cylinder designed for use in a stirling engine. It must be charged with gas before it can be used. Rated for temperatures up to 1000 C" + desc = "A piston cylinder designed for use in a stirling engine. It must be charged with gas before it can be used." icon = 'icons/obj/items/tanks/tank_stirling.dmi' gauge_icon = null obj_flags = OBJ_FLAG_CONDUCTIBLE @@ -210,6 +210,10 @@ volume = 30 failure_temp = 1000 +/obj/item/tank/stirling/Initialize() + . = ..() + desc += "It's rated for temperatures up to [failure_temp] C." + /obj/item/tank/stirling/empty starting_pressure = list() From 0347199be57dd5565bd88b588b6518abd4c7e772 Mon Sep 17 00:00:00 2001 From: NataKilar Date: Wed, 29 Jun 2022 20:52:03 -0400 Subject: [PATCH 3/5] Miscellaneous stirling fixes --- code/modules/power/stirling.dm | 12 ++++++------ icons/obj/power.dmi | Bin 55096 -> 58207 bytes 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/code/modules/power/stirling.dm b/code/modules/power/stirling.dm index e7c251ba28e..80d4176fdd6 100644 --- a/code/modules/power/stirling.dm +++ b/code/modules/power/stirling.dm @@ -4,11 +4,11 @@ /obj/machinery/atmospherics/binary/stirling name = "stirling engine" - desc = "A mechanical stirling generator. It generates power dependent on the temperature differential between input gas and a radiative heatsink" + desc = "A mechanical stirling generator. It generates power dependent on the temperature differential between two gas lines." icon = 'icons/obj/power.dmi' icon_state = "stirling" - density = 1 - anchored = 1 + density = TRUE + anchored = TRUE layer = STRUCTURE_LAYER base_type = /obj/machinery/atmospherics/binary/stirling construct_state = /decl/machine_construction/default/panel_closed @@ -55,7 +55,7 @@ var/datum/gas_mixture/working_volume = inserted_cylinder.air_contents - if(stat & (BROKEN) || !air1.total_moles || !air2.total_moles || !working_volume.total_moles) + if((stat & BROKEN) || !air1.total_moles || !air2.total_moles || !working_volume?.total_moles) stop_engine() return @@ -137,14 +137,14 @@ update_icon() return TRUE - if(isCrowbar(W) && inserted_cylinder) + if(IS_CROWBAR(W) && inserted_cylinder) inserted_cylinder.dropInto(get_turf(src)) to_chat(user, SPAN_NOTICE("You remove \the [inserted_cylinder] from \the [src].")) inserted_cylinder = null stop_engine() return TRUE - if(panel_open && isWrench(W)) + if(panel_open && IS_WRENCH(W)) var/target_frequency = input(user, "Enter the cycle frequency you would like \the [src] to operate at ([MAX_FREQUENCY/4] - [MAX_FREQUENCY] Hz)", "Stirling Frequency", cycle_frequency) as num | null if(!CanPhysicallyInteract(user) || !target_frequency) return diff --git a/icons/obj/power.dmi b/icons/obj/power.dmi index 41602bdee026ca65e4092826a995e137a9109d55..b6d63b9f09c03c0a8719508d94c104bebcf25219 100644 GIT binary patch delta 13376 zcmb`ubx>T-69>492lwDkAP|BF*Wd&Xgy8N3w;*qkV8IDaumB+hcXxM!2X}X0*yT3g z-(6kR-CtLARj+nm&AvDDneOSHPxtgTlp(M7A;&$(HNijwfj}Jgs}mp)GI2aJ10b0Y zK;`fG;9%xyS8;>pLyT&6A_QUMUAktZKEJ8R0O_-RAH;gY?(2n>DI2%fFdxXO#ZXR$sbi6)l0a~|TC*AiQWTqo!)9sR(AOHS8Fx>wQ5iv%5tc9<+0 zRnf!%#@rP+$|cR{^7@N!tei1WANgEQ5i7~L@1RU+(MaTDPKsTW2j)Q$3iB2VNo>mc zGlJE_KY_hO^aoy4Rnl#0-+f+uF$WFA^JMA08?!s&vDlgqxCA{~N(3q6ktw|#spc6` ziLKuVIi=uf?cnXSH_vo_!903gT$)p$@k_&-A(kmCo-smAPo6NlTJ8b_c#@6n;GC#7 zuABkw9gKuGE6<|JTZzb4{aGr!z-^Y}PHIQ{*UWcVL3i`g#f@4*)i01u%7L46jrX_k zr;($hPjpO7Od$|5g`z-wnNo~LzQ7#PeMd)gf1AMKYjDG~&Rg5-zdHJ5^|wa9iJ^P4 zrLuf>vbKIrm>-n6CNEg=D3D&FVC3Kayic^{(Hzkn@G8!OKq=K|g?QKcUX%D;-X8X4K3zlwDV_D0+++C>`?*sY9egk;+@; z%Ud_;31W;$&`*Z0ZNjzp?;D6&GLi5)$v3FzwfD83Z%O*W##vw%EloBd~) zj23Bg^wxKAWSfZemC+D`ycrp77cwWUp~&aYG>_;^`qwz$cvbKUuqkjQkUS5q!5!-Y z;!oL~Z~x??r3FNL4$j^YUBeT__gR#(=e%ix`NKNwk;d(V=+1)HAi-YJ_*Bjqug`+8 zTGr5ZT?02oMpEb?L?{4c7o-C6eg^7ziRs;mW2C;mW(lKs&4QvBn3zI$CW^v=0>-4u zOvJJWEBDRO<~&FcdOu09AlJR9|1o3#)M)S}Jim4u<)V*-k)H#ZC5D<$0wutSGT<(C zrN`%hxO?A_?dnmsH--fwU;{?-?gj z&YynmP>Wt&ivWZ-P$$?KwSkxUEfnVS6e~}=*Zu&sKy{!O@YO^(Y6dde9)}u2p)?iH zP`5W+&=jGV?nJd3v(d|+XFZA59WDkbJZR@jex*VBVrPrkWyklvF$rFlMcyQdPdL& zdxA;Q4LKc+mF;R*IC*LIijVSA=un)GOGRzmbapcxNfH&ggAZrIThbY#X!tuJ?rvPb zzv*ew_o>|kXTFx&%6g?G?WiE-&4Mrh>cE8!2&@b#JFNscjT|ol+oSVyHp05-hju5s zKj2*@%}>Ys9^=bMcVG|^DY)Obk{><=%0YSGFeN-*t08gFn6Cy!He96_N_&Ap;l_gl zDYTv7LsLo2l?p6WFo`63=Mwx9Gl$ir1u15?gBXX(8$^-A3SkI-NkJB6&k7)(CBZF9 zU#hf-M^Q=WB*uNGqdRg>nYeu|L3@>W4+o^Yq>v;TMn1G3#A1LLk(^`qGlt1&^AYRV zbo7@Fsns$pz4G|BuJhEfT6lg2+7SKulD~pWGi+#D&bl@YfSCiC-sA&mFMlB`vP5MBSRVw=`;K zu2P*$oOYg9#`JKdyZYJ%8K!#N+7JH;`1-Xx(fJBFJr}7o)r_Zaqmkv`Pjo7sweQX6~pJqdygFp zzwkh|xnH}-D)m=c-$D^Ohn0`JMHZi@BdNsmt0WSW2(~^<`AHWhd z>L9%iJW>ovEY#dDNb1M|MG5(ooQfgD33Q<{;hGrqo9Tckx<&%Ma@7K1^28lu?gQYu z{4@ypE>E?%6jC?E!u_iVqrZJJYPtsda`gA=HNk{5TYTF0uciFMT)!>Q zRH;Pf9eJJYho4ccx)N)-{rpj}-Qa_;7=%OYnO@pYU8&(Tg;pzB#fc*f2?t6amcx~n z$QSrL3BZ?*jLczTEDFa^qDLvfmj+AZtK)0ZfqHi*qfd4|nxpZ5S zAXmu{vIP7CK?%B-7_1CPni!pNA^61M>Bv09tPwG*n9EOzr|@y@4Was8f>UcEliPFu<|4j-`xPDxq`GsLKYRN{ zs*6j?8Rxm?oq@pVfCrBB$r>@XR{wS5jgpL3e}CYG!>#8WmjSV|lG-`7=GXvTNVj4@bNnW|*cGkNL>Vdj@Cu zan{=WG?ceLM=MU_iTe+EVgRr&0&M4W6@(L}l4@%67ez!GC-7x6byH$=KF43j3R%NL z#mDd=z`i5EM4^NF#rXv&&~P$kx*nLY}0q<_(s{; zM;Hff@FHyR?$^&z*RFlmFYnfG@A0qp7q`H0}RklbD3QUieugt*!%mm2ayjshy^#Z5d0b`F9)N zJ~7A!3w7cItApsQ^YF4`_p<900qK%q?b0*#HLV{Y&MDePCkm$hi-f87mO+o1+%X~Q z2J+lU_KOsQ1S{a3BZ?X?00mEn%$;1mX4k>}THr@X7qo0ES(a3FrO{)#R6gzr%LQSP4u$a`oSU^JGHwS#i`h~<^q0=2G(BXhcu4ht`iho3_Uip5D+eosUVdw5;L9;Mcc<<+` zejWP0mp!sdfH*;vCs9mO(l*6%fl;Yg%`Cw7PD!bgcO>1idkK)7$d z>C=uH`HleCyjp`oqNQkk!4CC1c9`Dk+_(SUHiO_-WBuXMZ4WqNv7+4x>Q(-@(qQIR zIH&L@)GBx1#8CIiuIBK+KBbs=xtrDK>{veOF{vTQf)fjD0#rF~ztWZ?bZ5Ed+jVBV4E zAhH)DO0crn3H5eHhh-)}@-&NYB*o=*+W+JeuA1j5r8k-)Ra4dvHKfhmyRaYGEj5gv zyyz?+ zCu3w|!kPZfCC~R$%YFIs4Swr2wEltrOV|}~Ja;+^>2>J5kO+u_O?l%&&VR+PCINKQ&ZFVm{v95-2#yD20(1H-RC?H*9ej*T;&rrGJ zB(2bAz3JWKNMLKv)@sDF1>R~0R#q97JPwuE7_lYcRhl{*%|Kf}t`G9Qf3a3IBN+zfiHa+QE*~v+!k1UE7HNS6JN@fY?pv?NRvr_CGaSp* z0fSCkHv3aBm%Sg3L7<#c72aXp^SW%glRXzfmm?G7>6Cx+LSvFU#L5BgDXo~q>?=#5 z=9ib;@TwUrthn#}EcXN1)`Q5^}E7z}i zFxuMLuQO4uZom6h$DIpVEK*KOK5Eo|f({{Cn6XYdKgi3jB@?VeP5e1Ob!~0(;e)vs z)X?ZMphRn$v;xuf0Of~&EGARXbAr$Ge-T<%2yg)Wze2QnNNrta$1L7zr-#7Gq!Sn) z1@sEAZ%mVHyy`2cqkOc^^68s4t}p@bv!n&Qiu%l4ta{phFNh2|Y(H0*zDg8>-)0Bs z%^5dT$iu2tF3QTwtxNx`j>6u})v~g&ZG5Km%|5(kxN)1=t%GMb0^@}N+d&cSi?0=a zY!7jn^kFE(Y@TfqSyy~Aq!c~o$uh`7?MwaGXV*30M*vf~-kmurZFB;EK$-^|EI2c> zQ)*i+;?%dbWoggqq(hmRnM;a_!mko#+M7R*E@?!3RStxHemI=|QE^i8dwW#hOlP8rk3i~*-}1*$L$gF%()wl$Z15(H6U52}ux$kR&52#tZ2ty=FSUYx&a7%O)G6EgeBCpL8`;zBxm>(C`_1;|V)W3q>*B#8w%~g|} zmUNtPcl=_MG&RjV=1?CjQNxd5(!;_gro8@9BWZeO@^BTF)wA?4`*9=N!Rc|*2ypT{ z4=Y8rHDqH=2OoYS)M1}2%&mC6<7iD7u#MYF>j}V<&adC%@xd(CHsyC8U^80a9n2}|0f8> zL!DIU^)nl=a(;jp(_ictL*8UezK<|RaR?a)7i$&AO%aDWYZbHSTZ!Crd3*4yNKW`` z!??ao!?cA)8%H5iilP(Jm6*`lUSOEZI3g9V1b6Z31G5y9-s*Igfb+F{G~1?5=@V)9 zXMdoJ|7Him+#H~xK`^HW7i&d5*y!1U@T=pJ#plZmOHuF-8NmjqxSFqtD1dKNHQw9Z z*s0vNb0rDn7;RU4Z+2C`|RyUc|@a!jczg3<_1?ejcRGi#**$8qa9VWJQXeVn zA8S1H4_*hS6!yOZ{Qbp5s?1O^L3|sJc3{SE_zNAZI7cYA9>ifv&Ha&E5471i0`~nE zj^t#NL%aLe7teTNcD8{3b{HLYbHje!7i_#D0uIOh2s*6Ze#nU65bv^fWG3goR#e`+ zHF(+{=YA+8sav8mW}9m6*wJ2elF^$jw-)gA1Z4=UT`%AYp(9_?HHQpmu7O$ zf0(u~JijcH@S{S6YiqE-c;|og0n6XnhpRKAfjze!rk7}~gw;fh+1TfRk>O}z$P<$J zk0A#KFas>)aNMZ1NeejA)xUnm5TA+1p>Dm1H2nb@@d+%V?_bu`M1d!Mfs%oUq(JYG zt=ySO-_QbbZ^z~6n_z{P9#!nPa@{W%P zG$HKC@WSy)%ep#Jfp&UsH)?Ja%_#ZpT>_3Bu^TH-GMacl2 z7t||X6~k^f(takp0ikJCJ02f>)|R~*(O3LaGTgm6EnJFi+oCpAZ*?xm#}>|>{l}li zSh|uK7%+k=5`orJjaj!}@J#@Iclr!QPsRb4nfukQP^kNifyV50-!f&%Va2r!@JdDM zpO_5h^YedxzizZm>mG4*I!tSb1xpTWirFu>F)@qb0Ep%_M=`gR>HqbR$1*kuDK);w zAsuQ*ciS0>c*qo3@eVGag7SVU^1jS$4_B0Eyp9M|(VL0sRg)Mlp|M97E zwd|;#v`L<(Xz%<9LJf_GiKk0Qs(xb9g9W9onrLGPUa@$#Jy@ho6)>n19&1PK# zfa)K$PyS{m6DRwBc`QXGT!vD(Ua|D=P)cI5fr zlM)kOd1q;i-4VivJ_bBX3~)Up8tngB`!I5>to$N`dWz|T#W~Hoq2~1ND!hNCsBqvl z!j~B8<<8MNl{iBEU7~e8_%@|Jm-BBR@(&*uq(SrV4Pox>5!lW!nSSTjx`*O!K>v;R z!-k+fteVF}g#**|;@a^r#mU^9k%EFkT~~L0yD}#Sgo=u4x@~_G(LfJhVMnA|8J^wO zcxI=uBqs}DXV0D-nn~lHh*;1FT(p`w#%9E=%5EM5N0wm}HygU<$V$84Hq`;dN!N8L zaqNdUULTYy62vx2dVEleaS|%19zd{A2N&w=j+2Yi5&k%#n*Ewb^L{3gjKte-9joy>&G}_OV z^;)JF%0U%Np7wsk-W(X1nYdhETNW)gu{bbUZ0!j$s*QMy>#d}^i~yfMl^iQzcVFLC zWxKWmaDFQXN+C^HDed6zMG?~EmvX5_z+v_sE|>5`G{PVa>#$p3Se_^&!dL!evUplu<0=$L7t`M z@QE1Hr@;76dKI(FV@u@-(wY3~W7lWiXi28?sDQ8eIY18RwDp!o6BkC_`n?Zwg`OOs zHl^|H>4>vrV=M!-l1oXaGBX;@why=#x#=HrNu(D)1)6RTk?dN85SB_zr1?JJ=^9C0 z&De)wQ!bS=UXAeyY{sLrb?LP-=yyQ%pS-)%UY7!^DwDhY^Qi)LtDERhFXFAV9jg&L zVQi2$!2NB7StmU_Nx=#whd1nJJ=wC(c@%^4C8mN@T=aP;NcdcvZ$pwK^aj~Zno!xFylC4*Js3Q*FV?GCs48+W!!hZ; zLblMq(a}LcgR$S6J#+2uVTFS)v)dt37r7hgUo|u|CfSL;*}yD1|DvWD3vxSLh_y!~!e!a*pwbE{f(x8L1lg6Dv>H&*>IxedWkgwwp zOnMS&Hg=CoOX9O+a7L4XicTwmk9i6qZT!zxabfV;ikHu_>0S$V?SR?8uf^P>if*^7 zneLkT>o4CKZf*HJN7$#JKa_80BrSMoXo#jaiqrVVu>DB9yvEms-R}XT;y|Uc7-4~v z1KxXo;?iS1E@o(i=-Aj() zOs+EK&AZs7tvM{EtMym%CGDH&6Q||pjMR#EZca~9^Y#?>Bq%`&qQHFE!+=~RKe30_o--Z{NT*c9^pO=#o)I%zN4Rp-##r7 zRF}9ceLgxpZ84#VHNakK3Ky7wwP<_3LR(oqNA9RKZe9kC;QT{!M%{u|dt`qn4=g#9 z@nzieC@Q3W%l{z8ut$YH>tAO-oI{?lmd;LYrS|z!pI22yc)2?st6zb*`SRt9g3y&U zUoEQ!j^sXySBfx`G%Y9~J?BoDr6F+U2T`F@kMUnq1TU7Lh*}@slmq?xc$ZJ!;4px1 zVbRF#QUo9`WJbC~VlE7>e|5o>6V|G5+j1^hx)P#`C~646X4?rs-DVdz(N-=y;iD{j z4{_TA#3s?9?8f(WkLj(GrzYJGr{*`;bDLgjj~G;0Xx&{jw5LylP9V@R%Qfe1Vd0n* zp0$0O-P1j@=S+&6$;Mq-{nyq95(kk9p-HPjW&nLmX%q9FIC5&K=rXcFTKtQhdz)(C zbDtW{tAX8(my3zq;r-6!mQC!7AGA2;34nZhk*jg)!ZT4y}+$j9wu zVL>uUz&pe1wPum!!`Y1EP3`afrJ7&NCVs_r^oi&kibLULtRfsd432PHUnvhDA8)J1 z`I@!drL!y&9qfkBL;_lMd*PgS4PM)F@i%O9x$pJ1hq<>zi+N*-Nr+#MICYn~ovDAf zJUyD-+jE!^h6HP%4L1qpY$a3!P3~m9wx2F5g^TKABBcl(Zv3cpKCNaYlP%JQiOD4s z(1#|S=1F+)9Mqi%g<*Vtp~+Zo@;-&CK^yD&VWXyPutS7IQ(u4b@d-aeIeDW~U+@_% zZRHNY7;Ygou%8;jnZ;x$nH}2UKuzvOWOvj0rRpP;VK}Ka2`UyKzx(_I7@hSiNhlgiEN$1|W*Bhha=hr0O>|6wcDn*Izd3SK%c>VLfwbX!EC+xsgWoG8mdEJz z3F`-_^)p;1GQ?`b_tlzCrUx6$)Ht|m^H<;L@xir8UCB&-mG$nP=<#d1eHl=&o&5hF<%n&mXT3zMh3{=x=qdoLO@& z&8SwASr51eU_g*Q^o+-9%k?@mm+_AYS*hfKAC%wd(wn7cK+w(DzSD$Juh8Gzs#i(PbycFK&rbR4)@HO7ztUcnS@{fIh&}7XCResJG`$n`84R5Nbqi_uW zn^f+Pn+|r5R1z(h$ux)Z_{b!ez{!Vlm3~KK$G+dEJ8PxwKiB&u;M`e$7lW^3L0xVT z*S4=+(yik_uLwJrAGY%W#@YLIEyErqC8ddp3H)SaVg^|LY;0IDZIbhLEG(MdlC?O% zwX_$I@veNVei!=QB>XG7H`p1OrIm*%8a*?3P1S`CA=6G&#mOt4)}G?*@BG-~6h4Nq zlGW#oq~q38BoU@vk?UM4WGzkc;r)+2!PmkfKd3lq%AZ&V(nOIU;t@7uTLd;MZiJjC zH+gqeW3)K4JZN&{o2f}VVk{c}{CyX*U1Ok(lw$(I2%Jp2A_si)#DEN1G(X z-C)Viug$Hg9=^K?A9bdPY?IBX0d&Z78#4Ur^KfWLYgNr&Vjqj_5Lfj8GwerX_liUX*hiM;BXjp zmB}lIKN!?V=Cy5X@5^TYhnNxF=3NaHojKD5Nu^udzc8fv$78ZnbtF1XM`3K8iM2oD zn_?a=K`e4VG^Kv)AGu;7JG_pN?qB1&5~=c##ExbQ1x$2S!oOkx#L-Ifqu^%=Oi1CY zAZ9&@kKu2qfnaew-CcL$^*%Y;m$r`D2QoOkGT&n-%grs9Li~=FC`5KY74d$qw{#jJ zRvW-mzIeKW9lTcNA}5;?YH2d$KXm+FXxisWqip1)thbWYKqVnRSz_VZwrkKadwap^_lIqb$8_8$Kg&pEJqOB z6M=<7TIKb*Y>F_ftuRwx1rJIMY|u$wZREG3vOF!ID1Zl~ByyBodQ%ZG*!uQ!IQ}K? z8St=cZ0;*DNz^jDCZO^mKM=8grT6xPv@9f@ky6lMR#KT`=Hb4avK zW;MiRJXT%Hv|Gw4ZA(yOj6YSih+NU*FqqJS@;FwHuU<=Rmq$Kx<7r>vZ68XomY z+8&E!!Ze>@tiF#{t6SQO0>NU48SBsxMl9ow0wNP~Ws17**vM^cB$tn&>U>_dU$jua zJOXwlGp>W&f3e>Wbs()Xufvf|os1XS>HbW~t>TnY^Vl^IE@RA17gpTdgU zOQMGTm^HI8b}w>h5l))Ux++W1ICG~Dk;boW^mojcT}FNf7}-R)5_{*ePF#pB zEEwA34QP(ZAQkZmlW54GJs!+Ispum^i%;{zQID+M-f}U;4u}n9{8S0G&f(3^&lR3{ zCks{N_?j+@cj~&LlN$m?TANL?cN;U|Iub`-<}B`vh)92O1k8vbEf2cViK|{OYZmV@ zhVvk+Pzy=+=>-CyiTR>(soQMoasD@N%n-ceu8SAdYx$aWxpzAyqhG1~9? z%gH{MLZ8s=eM?j)M&EtqM!nJMd|e-{@>^#Hr941z)~BQ7--*qU^UTbQwg1Ivy+rdzAD{eVQWElZnzHd@)BO7z|O}; z)#SNy+@*tTf(yY)7zK$~h-6E8I>q}^sCN0t2ZpPNvPby+TU$-)TX6;OUMe%n~DyX^_VaPcC33TP)s<8a1L1&%+CV(IoV#JAEh6f zM6XhGmr4CHEe)6qX9)l;+kwKBe~RF2LgR%@j;J}Qm(E{PYnM(^q4J$`F}N7O zW@WGwUx_NV&JJgvMHr*uJ?yf)QkQq(VNrOuQfXAM*&q+GX@rp@s%367!9P7?mMmgS zt&o5wU37(a)==UfM!o4a{tTJAm!>dtn6Lo=yG|8wq7lE}A}Z0V#ndWmJul%K>x1~{;ki%J@-g*6 z^_M43gc7fOG(v?CVMx9jHklN@jPd0JC!Dt!EaI7iJ7?%cy)*Xx>vs_^z-*>idw^P` zZEg^H(Uw$%Xk`&&nU>zM@%m z4-^p$R?Ci&=5%{3!l7>TWlg6Q-{@hNuh9_0iR|+tds_ByzBp7xLL`XIW|s^9*O}n+ zjbdM2`ah?!_6U3itXFAZrwAl*A-=Cf50MtHkDikOJv2y588( zAgWRlNYfFUM!1aB54!&y#W;lilPIEGBV_cSEdI^2f}F<$(SW$#yhV%pH*t(Es{zRW z4u9sc$^Wn7ANv2NfB?GxKYRQC-WlEhjqU!a?u`siq+`)4;(tOQMOjsuGHH{b{{xw^ B;luy{ delta 10240 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 Date: Thu, 30 Jun 2022 18:14:23 -0400 Subject: [PATCH 4/5] Adjusts stirling heat transfer --- code/modules/power/stirling.dm | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/code/modules/power/stirling.dm b/code/modules/power/stirling.dm index 80d4176fdd6..3b15b4ec420 100644 --- a/code/modules/power/stirling.dm +++ b/code/modules/power/stirling.dm @@ -89,18 +89,19 @@ var/work_coefficient = working_volume.get_total_moles()*R_IDEAL_GAS_EQUATION*log(1.5) var/work_done = work_coefficient*abs(air1.temperature - air2.temperature)*cycle_frequency - var/reversed = air1.temperature < air2.temperature ? 1 : -1 + // Direction of heat flow, 1 for air1 -> air 2, -1 for air2 -> air 1 + var/heat_dir = air1.temperature > air2.temperature ? 1 : -1 - var/air1_dq = reversed*work_coefficient*air1.temperature*cycle_frequency - var/air2_dq = -reversed*work_coefficient*air2.temperature*cycle_frequency + var/air1_dq = -heat_dir*work_coefficient*air1.temperature*cycle_frequency + var/air2_dq = heat_dir*work_coefficient*air2.temperature*cycle_frequency var/power_generated = 0.75*work_done - // Excessive power is transferred as heat to the opposite side, reducing efficiency. + // Excessive power is transferred as heat to the cooler side, reducing efficiency. if(power_generated > max_power) - if(reversed) - air1_dq += 0.75*(max_power - power_generated) - else + if(heat_dir == 1) air2_dq += 0.75*(max_power - power_generated) + else + air1_dq += 0.75*(max_power - power_generated) power_generated = max_power if(prob(5)) spark_at(src, cardinal_only = TRUE) @@ -155,17 +156,18 @@ . = ..() /obj/machinery/atmospherics/binary/stirling/attack_hand(mob/user) - if(!(stat & BROKEN) && !active) - if(!inserted_cylinder) - to_chat(user, SPAN_WARNING("You must insert a stirling piston cylinder into \the [src] before you can start it!")) - return - to_chat(user, "You start trying to manually rev up \the [src].") - if(do_after(user, 2 SECONDS, src) && !active && inserted_cylinder && !(stat & BROKEN)) - visible_message("[user] pulls on the starting cord of \the [src], revving it up!", "You pull on the starting cord of \the [src], revving it up!") - playsound(src.loc, 'sound/machines/engine.ogg', 35, 1) - active = TRUE + + if((stat & BROKEN) || active || panel_open) + return ..() + + if(!inserted_cylinder) + to_chat(user, SPAN_WARNING("You must insert a stirling piston cylinder into \the [src] before you can start it!")) return - . = ..() + to_chat(user, "You start trying to manually rev up \the [src].") + if(do_after(user, 2 SECONDS, src) && !active && inserted_cylinder && !(stat & BROKEN)) + visible_message("[user] pulls on the starting cord of \the [src], revving it up!", "You pull on the starting cord of \the [src], revving it up!") + playsound(src.loc, 'sound/machines/engine.ogg', 35, 1) + active = TRUE /obj/machinery/atmospherics/binary/stirling/on_update_icon() cut_overlays() From 1bf95b8c8379d8ea3e84f609134599096c36af67 Mon Sep 17 00:00:00 2001 From: NataKilar Date: Mon, 1 Aug 2022 21:26:57 -0400 Subject: [PATCH 5/5] Updates math for stirling engine. --- code/modules/power/stirling.dm | 90 +++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/code/modules/power/stirling.dm b/code/modules/power/stirling.dm index 3b15b4ec420..c5d848ae494 100644 --- a/code/modules/power/stirling.dm +++ b/code/modules/power/stirling.dm @@ -1,4 +1,4 @@ -#define HEAT_TRANSFER 5000 +#define HEAT_TRANSFER 300 #define MAX_FREQUENCY 60 #define MIN_DELTA_T 5 @@ -21,7 +21,7 @@ var/cycle_frequency = MAX_FREQUENCY/2 var/active = FALSE - var/max_power = 100000 + var/max_power = 150000 var/genlev = 0 var/last_genlev @@ -39,14 +39,19 @@ /obj/machinery/atmospherics/binary/stirling/Process() ..() - // Some temperature equilibrium between the gas lines. - var/air1_eq = air1.get_thermal_energy_change(air2.temperature) - var/air2_eq = air2.get_thermal_energy_change(air1.temperature) + var/line1_heatcap = air1.heat_capacity() + var/line2_heatcap = air2.heat_capacity() + + var/delta_t = air1.temperature - air2.temperature - var/heat_transfer = clamp(HEAT_TRANSFER*(air1.temperature - air2.temperature), min(air1_eq, air2_eq), max(air1_eq, air2_eq)) - if(heat_transfer) - air1.add_thermal_energy(-heat_transfer) - air2.add_thermal_energy(heat_transfer) + // Absolute value of the heat transfer required to bring both lines in equilibrium. + var/line_equilibrium_heat = ((line1_heatcap*line2_heatcap)/(line1_heatcap + line2_heatcap))*abs(delta_t) + + // Some passive equilibrium between the lines. + var/passive_heat_transfer = min(HEAT_TRANSFER*abs(delta_t), line_equilibrium_heat) + + air1.add_thermal_energy(-sign(delta_t)*passive_heat_transfer) + air2.add_thermal_energy(sign(delta_t)*passive_heat_transfer) if(!istype(inserted_cylinder)) return @@ -55,14 +60,12 @@ var/datum/gas_mixture/working_volume = inserted_cylinder.air_contents - if((stat & BROKEN) || !air1.total_moles || !air2.total_moles || !working_volume?.total_moles) + if((stat & BROKEN) || !air1.total_moles || !air2.total_moles || !working_volume?.total_moles || !working_volume?.return_pressure()) stop_engine() return // A rough approximation of a Stirling cycle with perfect regeneration e.g. Carnot efficiency. var/working_heatcap = working_volume.heat_capacity() - var/line1_heatcap = air1.heat_capacity() - var/line2_heatcap = air2.heat_capacity() // Bring the internal tank in thermal equilibrium with the hottest line. The temperature/pressure // is kept at the maximum constantly. @@ -77,38 +80,44 @@ working_volume.add_thermal_energy(equil_transfer) + // Redefine in case there was a change from passive heat transfer. + delta_t = air1.temperature - air2.temperature + line_equilibrium_heat = ((line1_heatcap*line2_heatcap)/(line1_heatcap + line2_heatcap))*abs(delta_t) + // The cycle requires a minimum temperature to overcome friction. - if(abs(air1.temperature - air2.temperature) < MIN_DELTA_T) + if(abs(delta_t) < MIN_DELTA_T) if(skipped_cycle) // Allow some leeway since networks can take some time to update. stop_engine() skipped_cycle = TRUE return skipped_cycle = FALSE - // Positive/negative Work from volume expansion/compression. Maximum volume is 1.5 tank volume. The volume of the tank is not actually adjusted. + // Positive/negative work from volume expansion/compression. Maximum volume is 1.5 tank volume. The volume of the tank is not actually adjusted. var/work_coefficient = working_volume.get_total_moles()*R_IDEAL_GAS_EQUATION*log(1.5) - var/work_done = work_coefficient*abs(air1.temperature - air2.temperature)*cycle_frequency // Direction of heat flow, 1 for air1 -> air 2, -1 for air2 -> air 1 - var/heat_dir = air1.temperature > air2.temperature ? 1 : -1 + var/heat_dir = sign(delta_t) - var/air1_dq = -heat_dir*work_coefficient*air1.temperature*cycle_frequency - var/air2_dq = heat_dir*work_coefficient*air2.temperature*cycle_frequency + // We multiply by the cycle frequency to get reasonable values for power generation. + // Energy is still conserved, but the efficiency of the engine is slightly overestimated. + + // The hot line and cold line will not receive or give more than the heat required to reach equilibrium. + var/air1_dq = -heat_dir*min(work_coefficient*air1.temperature*cycle_frequency, line_equilibrium_heat) + var/air2_dq = heat_dir*min(work_coefficient*air2.temperature*cycle_frequency, line_equilibrium_heat) + + var/work_done = -(air1_dq + air2_dq) var/power_generated = 0.75*work_done // Excessive power is transferred as heat to the cooler side, reducing efficiency. if(power_generated > max_power) if(heat_dir == 1) - air2_dq += 0.75*(max_power - power_generated) + air2_dq += 0.85*(max_power - power_generated) else - air1_dq += 0.75*(max_power - power_generated) + air1_dq += 0.85*(max_power - power_generated) power_generated = max_power if(prob(5)) spark_at(src, cardinal_only = TRUE) - air1.add_thermal_energy(air1_dq) - air2.add_thermal_energy(air2_dq) - generate_power(power_generated) last_gen = power_generated genlev = max(0, min(round(4*power_generated / max_power), 4)) @@ -122,8 +131,8 @@ . = ..() if(distance > 1) return - - to_chat(user, "\The [src] is generating [round(last_gen/1000, 0.1)] kW") + if(active) + to_chat(user, "\The [src] is generating [round(last_gen/1000, 0.1)] kW") if(!inserted_cylinder) to_chat(user, "There is no piston cylinder inserted into \the [src].") @@ -138,24 +147,25 @@ update_icon() return TRUE - if(IS_CROWBAR(W) && inserted_cylinder) - inserted_cylinder.dropInto(get_turf(src)) - to_chat(user, SPAN_NOTICE("You remove \the [inserted_cylinder] from \the [src].")) - inserted_cylinder = null - stop_engine() - return TRUE + if(!panel_open) + if(IS_CROWBAR(W) && inserted_cylinder) + inserted_cylinder.dropInto(get_turf(src)) + to_chat(user, SPAN_NOTICE("You remove \the [inserted_cylinder] from \the [src].")) + inserted_cylinder = null + stop_engine() + return TRUE - if(panel_open && IS_WRENCH(W)) - var/target_frequency = input(user, "Enter the cycle frequency you would like \the [src] to operate at ([MAX_FREQUENCY/4] - [MAX_FREQUENCY] Hz)", "Stirling Frequency", cycle_frequency) as num | null - if(!CanPhysicallyInteract(user) || !target_frequency) - return - cycle_frequency = round(clamp(target_frequency, MAX_FREQUENCY/4, MAX_FREQUENCY)) - to_chat(usr, SPAN_NOTICE("You adjust \the [src] to operate at a frequency of [cycle_frequency] Hz.")) - return TRUE + if(IS_WRENCH(W)) + var/target_frequency = input(user, "Enter the cycle frequency you would like \the [src] to operate at ([MAX_FREQUENCY/4] - [MAX_FREQUENCY] Hz)", "Stirling Frequency", cycle_frequency) as num | null + if(!CanPhysicallyInteract(user) || !target_frequency) + return + cycle_frequency = round(clamp(target_frequency, MAX_FREQUENCY/4, MAX_FREQUENCY)) + to_chat(usr, SPAN_NOTICE("You adjust \the [src] to operate at a frequency of [cycle_frequency] Hz.")) + return TRUE . = ..() -/obj/machinery/atmospherics/binary/stirling/attack_hand(mob/user) +/obj/machinery/atmospherics/binary/stirling/physical_attack_hand(mob/user) if((stat & BROKEN) || active || panel_open) return ..() @@ -214,7 +224,7 @@ /obj/item/tank/stirling/Initialize() . = ..() - desc += "It's rated for temperatures up to [failure_temp] C." + desc += " It's rated for temperatures up to [failure_temp] C." /obj/item/tank/stirling/empty starting_pressure = list()