From db5dee6db320a1e8f3b91f022dc5829ef0637028 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Sun, 6 Aug 2023 01:21:05 -0700 Subject: [PATCH] Add radiators (#18728) * Add radiators * Limit heat transfer to fluid heat capacity * Adjust datafield names * Fix material arbitrage * This code has been debugged, and so there are no more bugs. Debugging code is therefore unnecessary * Adjust radiator layer subfloor visibility * Cache CVars * No default Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Like and unsubscribe * Fix CVar caching --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- .../Components/HeatExchangerComponent.cs | 36 +++++++ .../EntitySystems/AtmosphereSystem.Gases.cs | 10 ++ .../EntitySystems/HeatExchangerSystem.cs | 97 ++++++++++++++++++ Content.Shared/CCVar/CCVars.cs | 6 ++ .../Structures/Piping/Atmospherics/binary.yml | 42 ++++++++ .../Graphs/utilities/atmos_binary.yml | 24 +++++ .../Recipes/Construction/utilities.yml | 16 +++ .../Atmospherics/heatexchanger.rsi/heBend.png | Bin 0 -> 1432 bytes .../heatexchanger.rsi/heStraight.png | Bin 0 -> 929 bytes .../Atmospherics/heatexchanger.rsi/meta.json | 19 ++++ 10 files changed, 250 insertions(+) create mode 100644 Content.Server/Atmos/Components/HeatExchangerComponent.cs create mode 100644 Content.Server/Atmos/EntitySystems/HeatExchangerSystem.cs create mode 100644 Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/heBend.png create mode 100644 Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/heStraight.png create mode 100644 Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/meta.json diff --git a/Content.Server/Atmos/Components/HeatExchangerComponent.cs b/Content.Server/Atmos/Components/HeatExchangerComponent.cs new file mode 100644 index 0000000000..10819387cf --- /dev/null +++ b/Content.Server/Atmos/Components/HeatExchangerComponent.cs @@ -0,0 +1,36 @@ +namespace Content.Server.Atmos.Components; + +[RegisterComponent] +public sealed class HeatExchangerComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + [DataField("inlet")] + public string InletName { get; set; } = "inlet"; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("outlet")] + public string OutletName { get; set; } = "outlet"; + + /// + /// Pipe conductivity (mols/kPa/sec). + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("conductivity")] + public float G { get; set; } = 1f; + + /// + /// Thermal convection coefficient (J/degK/sec). + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("convectionCoefficient")] + public float K { get; set; } = 8000f; + + /// + /// Thermal radiation coefficient. Number of "effective" tiles this + /// radiator radiates compared to superconductivity tile losses. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("radiationCoefficient")] + public float alpha { get; set; } = 400f; +} + diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index f08262ef0c..0726606ae0 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -78,6 +78,16 @@ namespace Content.Server.Atmos.EntitySystems return mixture.Temperature * cachedHeatCapacity; } + /// + /// Add 'dQ' Joules of energy into 'mixture'. + /// + public void AddHeat(GasMixture mixture, float dQ) + { + var c = GetHeatCapacity(mixture); + float dT = dQ / c; + mixture.Temperature += dT; + } + /// /// Merges the gas mixture into the gas mixture. /// The gas mixture is not modified by this method. diff --git a/Content.Server/Atmos/EntitySystems/HeatExchangerSystem.cs b/Content.Server/Atmos/EntitySystems/HeatExchangerSystem.cs new file mode 100644 index 0000000000..6ef617b0cd --- /dev/null +++ b/Content.Server/Atmos/EntitySystems/HeatExchangerSystem.cs @@ -0,0 +1,97 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.Piping.Components; +using Content.Server.Atmos.Piping.Unary.Components; +using Content.Server.Atmos; +using Content.Server.Atmos.Components; +using Content.Server.NodeContainer.EntitySystems; +using Content.Server.NodeContainer.Nodes; +using Content.Server.NodeContainer; +using Content.Shared.Atmos.Piping; +using Content.Shared.Atmos; +using Content.Shared.CCVar; +using Content.Shared.Interaction; +using JetBrains.Annotations; +using Robust.Shared.Configuration; +using Robust.Shared.Timing; + +namespace Content.Server.Atmos.EntitySystems; + +public sealed class HeatExchangerSystem : EntitySystem +{ + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; + + float tileLoss; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAtmosUpdate); + + // Getting CVars is expensive, don't do it every tick + _cfg.OnValueChanged(CCVars.SuperconductionTileLoss, CacheTileLoss, true); + } + + public override void Shutdown() + { + base.Shutdown(); + _cfg.UnsubValueChanged(CCVars.SuperconductionTileLoss, CacheTileLoss); + } + + private void CacheTileLoss(float val) + { + tileLoss = val; + } + + private void OnAtmosUpdate(EntityUid uid, HeatExchangerComponent comp, AtmosDeviceUpdateEvent args) + { + if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !_nodeContainer.TryGetNode(nodeContainer, comp.InletName, out PipeNode? inlet) + || !_nodeContainer.TryGetNode(nodeContainer, comp.OutletName, out PipeNode? outlet)) + { + return; + } + + // Positive dN flows from inlet to outlet + var dt = 1/_atmosphereSystem.AtmosTickRate; + var dP = inlet.Air.Pressure - outlet.Air.Pressure; + var dN = comp.G*dP*dt; + + GasMixture xfer; + if (dN > 0) + xfer = inlet.Air.Remove(dN); + else + xfer = outlet.Air.Remove(-dN); + + var radTemp = Atmospherics.TCMB; + + // Convection + var environment = _atmosphereSystem.GetContainingMixture(uid, true, true); + if (environment != null) + { + radTemp = environment.Temperature; + + // Positive dT is from pipe to surroundings + var dT = xfer.Temperature - environment.Temperature; + var dE = comp.K * dT * dt; + var envLim = Math.Abs(_atmosphereSystem.GetHeatCapacity(environment) * dT * dt); + var xferLim = Math.Abs(_atmosphereSystem.GetHeatCapacity(xfer) * dT * dt); + var dEactual = Math.Sign(dE) * Math.Min(Math.Abs(dE), Math.Min(envLim, xferLim)); + _atmosphereSystem.AddHeat(xfer, -dEactual); + _atmosphereSystem.AddHeat(environment, dEactual); + } + + // Radiation + float dTR = xfer.Temperature - radTemp; + float a0 = tileLoss / MathF.Pow(Atmospherics.T20C, 4); + float dER = comp.alpha * a0 * MathF.Pow(dTR, 4) * dt; + _atmosphereSystem.AddHeat(xfer, -dER); + + if (dN > 0) + _atmosphereSystem.Merge(outlet.Air, xfer); + else + _atmosphereSystem.Merge(inlet.Air, xfer); + + } +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 3c4162106d..493bf14c1a 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -937,6 +937,12 @@ namespace Content.Shared.CCVar public static readonly CVarDef Superconduction = CVarDef.Create("atmos.superconduction", false, CVar.SERVERONLY); + /// + /// Heat loss per tile due to radiation at 20 degC, in W. + /// + public static readonly CVarDef SuperconductionTileLoss = + CVarDef.Create("atmos.superconduction_tile_loss", 30f, CVar.SERVERONLY); + /// /// Whether excited groups will be processed and created. /// diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index 758f4dbc00..13116f19ef 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -379,3 +379,45 @@ acts: ["Destruction"] - type: Machine board: GasRecyclerMachineCircuitboard + +- type: entity + parent: GasBinaryBase + id: HeatExchanger + name: radiator + description: Transfers heat between the pipe and its surroundings. + placement: + mode: SnapgridCenter + components: + - type: Rotatable + - type: Transform + noRot: false + - type: Sprite + sprite: Structures/Piping/Atmospherics/heatexchanger.rsi + layers: + - sprite: Structures/Piping/Atmospherics/pipe.rsi + state: pipeStraight + map: [ "enum.PipeVisualLayers.Pipe" ] + - state: heStraight + map: [ "enum.SubfloorLayers.FirstLayer" ] + - type: SubFloorHide + visibleLayers: + - enum.SubfloorLayers.FirstLayer + - type: Appearance + - type: PipeColorVisuals + - type: AtmosDevice + - type: HeatExchanger + - type: NodeContainer + nodes: + inlet: + !type:PipeNode + nodeGroupID: Pipe + pipeDirection: North + outlet: + !type:PipeNode + nodeGroupID: Pipe + pipeDirection: South + - type: Construction + graph: GasBinary + node: radiator + - type: StaticPrice + price: 50 diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_binary.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_binary.yml index 26db6533bf..eee5c6fbc4 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_binary.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_binary.yml @@ -46,6 +46,12 @@ amount: 2 doAfter: 1 + - to: radiator + steps: + - material: Steel + amount: 8 + doAfter: 1 + - node: pressurepump entity: GasPressurePump edges: @@ -159,3 +165,21 @@ doAfter: 1 - tool: Welding doAfter: 1 + + - node: radiator + entity: HeatExchanger + edges: + - to: start + conditions: + - !type:EntityAnchored + anchored: false + completed: + - !type:SpawnPrototype + prototype: SheetSteel1 + amount: 8 + - !type:DeleteEntity + steps: + - tool: Screwing + doAfter: 1 + - tool: Welding + doAfter: 1 diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index 6eba83b82f..40fe9b6883 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -663,6 +663,22 @@ conditions: - !type:TileNotBlocked {} +- type: construction + id: HeatExchanger + name: radiator + description: Transfers heat between the pipe and its surroundings. + graph: GasBinary + startNode: start + targetNode: radiator + category: construction-category-utilities + placementMode: SnapgridCenter + canBuildInImpassable: false + icon: + sprite: Structures/Piping/Atmospherics/heatexchanger.rsi + state: heStraight + conditions: + - !type:TileNotBlocked {} + # ATMOS TRINARY - type: construction id: GasFilter diff --git a/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/heBend.png b/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/heBend.png new file mode 100644 index 0000000000000000000000000000000000000000..180be218cbf92a5d591874c70db29321be6c1d50 GIT binary patch literal 1432 zcmV;J1!ww+P)?`EUT74e%CGK=_aY5>! z>>*5|gb3m#NtreBln6*IN@lHn<5k~0>*CSmt+R2RXGF43tSf){&mS|`djq9hUPm4&o*4%G_>Ra zTs<9=ks}a@O(bMejksA8kcDuc &7F0i*de(a&4EKAx-99e{fQIoo zckd&EpabVj8-qlq&OZMr0eG+mK&P%t1z?rOTeTlQe_0YYl4G7q1CDERpt0{XoBaLr z+rYRS4vK{fj1ys3E1!}gGj#A>f^Zt2K`-O+<8WgzFeZlyW(RjZW3qUhqZDx9V{ptJ z4yc1gqDUC_C=(|dU-1SQYbr1rtbIx<1Pd^sYQ9ZsjyFwP(iJai8T z_?7=t-$$LAT>4p{@#sA=-34(9^8$U(_v(J(&m^#Q{3j}pL9^#O-xWK!S4 z{=Y}kb*coettTi7c(50MlAhlxfE92FSZaTsu)CE{lj&?_5h!bY2otnqg9Pu?B=hpx zfQP;rXdVPkbCe}6GHJmRjR#v)oQYx!fM3Q6Z0tSwCgU2bx`2{2z1jt8_vUiBJ6E3& z5q#A?DTL(!|gfx&W%Z3q%l@#-*T})yFZULol~Dcr$Bd6omCL0ME17 z9q>IG3sU>@07!U`ivt6W1vO?`EUT74e%CGK=_aY5>! z>>*5|gb3m#NtreBln6*IN@lHn<5k~0>*CSmt+R2RXGF43tSf){&mS|`djq9hUPm4&o*4%G_>Ra zT@HRH*fX1M(+Yb4;%9d((BDndjL|(?oIBl#>|0IRej& z7tg#T-{ykJc@nwrMWsM`mxNYrP~qFN-~vXd^4pXY48xF=QlA+59>-Cv=r#FSZGd-} zAIIXfq`_};;8e9ZgjKcZ@6wsq;3pg4yBZJO3rkCj-J_Mz0jVO_jD%UCUb!<{B@02jLl!|Qu) zZN7cE<4TtRXVj