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 0000000000..180be218cb Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/heBend.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/heStraight.png b/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/heStraight.png new file mode 100644 index 0000000000..dfdf1f405b Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/heStraight.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/meta.json b/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/meta.json new file mode 100644 index 0000000000..d5b5c3beda --- /dev/null +++ b/Resources/Textures/Structures/Piping/Atmospherics/heatexchanger.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version":1, + "size":{ + "x":32, + "y":32 + }, + "license":"CC-BY-SA-3.0", + "copyright":"Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da and modified by BasedUser", + "states":[ + { + "name":"heStraight", + "directions":4 + }, + { + "name":"heBend", + "directions":4 + } + ] +}