From 45349e3c0d40c95cddd7f75875b3225a871e5b4e Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Sun, 11 Sep 2022 16:02:01 -0700 Subject: [PATCH] Add gas recyclers (#9934) * Add RemoveVolume() RemoveVolume(vol) captures the common pattern of: air.RemoveRatio(vol / air.Volume) Change existing code to use this method where appropriate. * Add gas recyclers Gas recyclers catalyze the conversion of CO2 and N2O to O2 and N2. The gas recycler component takes waste gas from the input net and releases the result into the output net. To make things more fun, the input net must be pressurized to 3 MPa and heated to at least 300 C; otherwise, no reaction will occur. Game-mechanic wise, gas recyclers contain the catalyst for the conversion reaction, and therefore, requires no external power. However, the external pumps and heaters required to make the reaction happen still do. * Fix gas recyclers Fix negative sqrt, fix pressure check after remove. --- .../EntitySystems/AtmosphereSystem.Hotspot.cs | 2 +- Content.Server/Atmos/GasMixture.cs | 5 + .../Binary/Components/GasRecyclerComponent.cs | 20 +++ .../Binary/EntitySystems/GasRecyclerSystem.cs | 120 ++++++++++++++++++ .../EntitySystems/GasVolumePumpSystem.cs | 4 +- .../Trinary/EntitySystems/GasFilterSystem.cs | 6 +- .../PressureControlledValveSystem.cs | 6 +- .../Body/Systems/RespiratorSystem.cs | 3 +- .../en-US/atmos/gas-recycler-system.ftl | 3 + .../Catalog/Research/technologies.yml | 1 + .../Circuitboards/Machine/production.yml | 12 ++ .../Entities/Structures/Machines/lathe.yml | 1 + .../Structures/Piping/Atmospherics/binary.yml | 46 +++++++ .../Prototypes/Recipes/Lathes/electronics.yml | 9 ++ .../Machines/gasrecycler.rsi/icon.png | Bin 0 -> 525 bytes .../Machines/gasrecycler.rsi/meta.json | 30 +++++ .../Machines/gasrecycler.rsi/running.png | Bin 0 -> 843 bytes .../Machines/gasrecycler.rsi/unlit.png | Bin 0 -> 179 bytes 18 files changed, 256 insertions(+), 12 deletions(-) create mode 100644 Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs create mode 100644 Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs create mode 100644 Resources/Locale/en-US/atmos/gas-recycler-system.ftl create mode 100644 Resources/Textures/Structures/Machines/gasrecycler.rsi/icon.png create mode 100644 Resources/Textures/Structures/Machines/gasrecycler.rsi/meta.json create mode 100644 Resources/Textures/Structures/Machines/gasrecycler.rsi/running.png create mode 100644 Resources/Textures/Structures/Machines/gasrecycler.rsi/unlit.png diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs index 763391cdcc..9402d76cde 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs @@ -153,7 +153,7 @@ namespace Content.Server.Atmos.EntitySystems } else { - var affected = tile.Air.RemoveRatio(tile.Hotspot.Volume / tile.Air.Volume); + var affected = tile.Air.RemoveVolume(tile.Hotspot.Volume); affected.Temperature = tile.Hotspot.Temperature; React(affected, tile); tile.Hotspot.Temperature = affected.Temperature; diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Server/Atmos/GasMixture.cs index 4278520fa5..2b84daa697 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Server/Atmos/GasMixture.cs @@ -176,6 +176,11 @@ namespace Content.Server.Atmos return removed; } + public GasMixture RemoveVolume(float vol) + { + return RemoveRatio(vol / Volume); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CopyFromMutable(GasMixture sample) { diff --git a/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs b/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs new file mode 100644 index 0000000000..03d4d95ed8 --- /dev/null +++ b/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Atmos; + +namespace Content.Server.Atmos.Piping.Binary.Components +{ + [RegisterComponent] + public sealed class GasRecyclerComponent : Component + { + [ViewVariables(VVAccess.ReadOnly)] + [DataField("reacting")] + public Boolean Reacting { get; set; } = false; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("inlet")] + public string InletName { get; set; } = "inlet"; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("outlet")] + public string OutletName { get; set; } = "outlet"; + } +} diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs new file mode 100644 index 0000000000..13aeb5ece5 --- /dev/null +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs @@ -0,0 +1,120 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos.Piping; +using Content.Server.Atmos.Piping.Binary.Components; +using Content.Server.Atmos.Piping.Components; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.Nodes; +using Content.Shared.Atmos; +using Content.Shared.Audio; +using Content.Shared.Examine; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Piping.Binary.EntitySystems +{ + [UsedImplicitly] + public sealed class GasReyclerSystem : EntitySystem + { + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; + + private const float MinTemp = 300 + Atmospherics.T0C; // 300 C + private const float MinPressure = 30 * Atmospherics.OneAtmosphere; // 3 MPa + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnEnabled); + SubscribeLocalEvent(OnUpdate); + SubscribeLocalEvent(OnDisabled); + SubscribeLocalEvent(OnExamined); + } + + private void OnEnabled(EntityUid uid, GasRecyclerComponent comp, AtmosDeviceEnabledEvent args) + { + UpdateAppearance(uid, comp); + } + + private void OnExamined(EntityUid uid, GasRecyclerComponent comp, ExaminedEvent args) + { + if (!EntityManager.GetComponent(comp.Owner).Anchored || !args.IsInDetailsRange) // Not anchored? Out of range? No status. + return; + + if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !nodeContainer.TryGetNode(comp.InletName, out PipeNode? inlet) + || !nodeContainer.TryGetNode(comp.OutletName, out PipeNode? outlet)) + { + return; + } + + if (comp.Reacting) + { + args.PushMarkup(Loc.GetString("gas-recycler-reacting")); + } + else + { + if (inlet.Air.Pressure < MinPressure) + { + args.PushMarkup(Loc.GetString("gas-recycler-low-pressure")); + } + + if (inlet.Air.Temperature < MinTemp) + { + args.PushMarkup(Loc.GetString("gas-recycler-low-temperature")); + } + } + } + + private void OnUpdate(EntityUid uid, GasRecyclerComponent comp, AtmosDeviceUpdateEvent args) + { + if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) + || !nodeContainer.TryGetNode(comp.InletName, out PipeNode? inlet) + || !nodeContainer.TryGetNode(comp.OutletName, out PipeNode? outlet)) + { + _ambientSoundSystem.SetAmbience(comp.Owner, false); + return; + } + + // The gas recycler is a passive device, so it permits gas flow even if nothing is being reacted. + comp.Reacting = inlet.Air.Temperature >= MinTemp && inlet.Air.Pressure >= MinPressure; + var removed = inlet.Air.RemoveVolume(PassiveTransferVol(inlet.Air, outlet.Air)); + if (comp.Reacting) + { + var nCO2 = removed.GetMoles(Gas.CarbonDioxide); + removed.AdjustMoles(Gas.CarbonDioxide, -nCO2); + removed.AdjustMoles(Gas.Oxygen, nCO2); + var nN2O = removed.GetMoles(Gas.NitrousOxide); + removed.AdjustMoles(Gas.NitrousOxide, -nN2O); + removed.AdjustMoles(Gas.Nitrogen, nN2O); + } + + _atmosphereSystem.Merge(outlet.Air, removed); + UpdateAppearance(uid, comp); + _ambientSoundSystem.SetAmbience(comp.Owner, true); + } + + public float PassiveTransferVol(GasMixture inlet, GasMixture outlet) + { + if (inlet.Pressure < outlet.Pressure) + { + return 0; + } + float overPressConst = 300; // pressure difference (in atm) to get 200 L/sec transfer rate + float alpha = Atmospherics.MaxTransferRate / (float)Math.Sqrt(overPressConst*Atmospherics.OneAtmosphere); + return alpha * (float)Math.Sqrt(inlet.Pressure - outlet.Pressure); + } + + private void OnDisabled(EntityUid uid, GasRecyclerComponent comp, AtmosDeviceDisabledEvent args) + { + comp.Reacting = false; + UpdateAppearance(uid, comp); + } + + private void UpdateAppearance(EntityUid uid, GasRecyclerComponent? comp = null, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref comp, ref appearance, false)) + return; + + appearance.SetData(PumpVisuals.Enabled, comp.Reacting); + } + } +} diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs index cf41fc1187..e1a547512d 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs @@ -82,9 +82,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems return; // We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters. - var transferRatio = (float)(pump.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inlet.Air.Volume; - - var removed = inlet.Air.RemoveRatio(transferRatio); + var removed = inlet.Air.RemoveVolume((float)(pump.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds)); // Some of the gas from the mixture leaks when overclocked. if (pump.Overclocked) diff --git a/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs b/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs index 97887e36d9..f15e0e7290 100644 --- a/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs +++ b/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs @@ -62,15 +62,15 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems } // We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters. - var transferRatio = (float)(filter.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume; + var transferVol = (float)(filter.TransferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds); - if (transferRatio <= 0) + if (transferVol <= 0) { _ambientSoundSystem.SetAmbience(filter.Owner, false); return; } - var removed = inletNode.Air.RemoveRatio(transferRatio); + var removed = inletNode.Air.RemoveVolume(transferVol); if (filter.FilteredGas.HasValue) { diff --git a/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs b/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs index d9403ba3eb..c435dc5af0 100644 --- a/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs +++ b/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs @@ -66,15 +66,15 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems UpdateAppearance(uid, comp); // We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters. - var transferRatio = (float)(transferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds) / inletNode.Air.Volume; - if (transferRatio <= 0) + var transferVolume = (float)(transferRate * (_gameTiming.CurTime - device.LastProcess).TotalSeconds); + if (transferVolume <= 0) { _ambientSoundSystem.SetAmbience(comp.Owner, false); return; } _ambientSoundSystem.SetAmbience(comp.Owner, true); - var removed = inletNode.Air.RemoveRatio(transferRatio); + var removed = inletNode.Air.RemoveVolume(transferVolume); _atmosphereSystem.Merge(outletNode.Air, removed); } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index c647abf743..2b9c1c70fb 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -109,8 +109,7 @@ namespace Content.Server.Body.Systems return; } - var ratio = (Atmospherics.BreathVolume / ev.Gas.Volume); - var actualGas = ev.Gas.RemoveRatio(ratio); + var actualGas = ev.Gas.RemoveVolume(Atmospherics.BreathVolume); var lungRatio = 1.0f / organs.Count; var gas = organs.Count == 1 ? actualGas : actualGas.RemoveRatio(lungRatio); diff --git a/Resources/Locale/en-US/atmos/gas-recycler-system.ftl b/Resources/Locale/en-US/atmos/gas-recycler-system.ftl new file mode 100644 index 0000000000..cc527adf5c --- /dev/null +++ b/Resources/Locale/en-US/atmos/gas-recycler-system.ftl @@ -0,0 +1,3 @@ +gas-recycler-reacting = It is [color=green]converting[/color] waste gases. +gas-recycler-low-pressure = The input pressure is [color=darkred]too low[/color]. +gas-recycler-low-temperature = The input temperature is [color=darkred]too low[/color]. diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml index c0541eb546..135768adb6 100644 --- a/Resources/Prototypes/Catalog/Research/technologies.yml +++ b/Resources/Prototypes/Catalog/Research/technologies.yml @@ -270,6 +270,7 @@ unlockedRecipes: - ThermomachineFreezerMachineCircuitBoard - PortableScrubberMachineCircuitBoard + - GasRecyclerMachineCircuitboard # Avionics Circuitry Technology Tree diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 9436adc6f9..a07d1ad655 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -541,3 +541,15 @@ materialRequirements: Glass: 2 Cable: 2 + +- type: entity + id: GasRecyclerMachineCircuitboard + parent: BaseMachineCircuitboard + name: gas recycler board + description: A printed circuit board for a gas recycler + components: + - type: MachineBoard + prototype: GasRecycler + materialRequirements: + Steel: 10 + Plasma: 10 diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index c8d253a2aa..f1246a135f 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -278,6 +278,7 @@ - WallmountGeneratorAPUElectronics - WallmountSubstationElectronics - EmitterCircuitboard + - GasRecyclerMachineCircuitboard - type: Machine board: CircuitImprinterMachineCircuitboard - type: Lathe diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index cb6fa50c2f..32319b6b8d 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -257,3 +257,49 @@ !type:PipeNode nodeGroupID: Pipe pipeDirection: South + +- type: entity + parent: [ GasBinaryBase, BaseMachine, ConstructibleMachine ] + id: GasRecycler + name: gas recycler + description: Recycles carbon dioxide and nitrous oxide. Heater and compressor not included. + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: Structures/Machines/gasrecycler.rsi + netsync: false + layers: + - sprite: Structures/Piping/Atmospherics/pipe.rsi + state: pipeStraight + map: [ "enum.PipeVisualLayers.Pipe" ] + - state: running + - state: unlit + shader: unshaded + - type: GenericVisualizer + visuals: + enum.PumpVisuals.Enabled: + enabled: + True: { state: running } + False: { state: unlit } + - type: Appearance + - type: PipeColorVisuals + - type: GasRecycler + - type: AmbientSound + enabled: false + volume: -9 + range: 5 + sound: + path: /Audio/Ambience/Objects/gas_pump.ogg + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:ChangeConstructionNodeBehavior + node: machineFrame + - !type:DoActsBehavior + acts: ["Destruction"] + - type: Machine + board: GasRecyclerMachineCircuitboard diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index ca09f5662c..eff89e2f1e 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -404,3 +404,12 @@ materials: Steel: 100 Glass: 900 + +- type: latheRecipe + id: GasRecyclerMachineCircuitboard + icon: Objects/Misc/module.rsi/id_mod.png + result: GasRecyclerMachineCircuitboard + completetime: 4 + materials: + Steel: 100 + Glass: 900 \ No newline at end of file diff --git a/Resources/Textures/Structures/Machines/gasrecycler.rsi/icon.png b/Resources/Textures/Structures/Machines/gasrecycler.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3c040e2545859373a3b239e516611f42b71127e5 GIT binary patch literal 525 zcmV+o0`mQdP)Px$$4Nv%R9J=WRy|7tK@fdPT452Bn52;?$fXXp2}-~sg?~T{`0)=cEv?kfN)YlB zEK&%fXc-C7DuLJOA58|1&6>&rlFno2Gd zOGqR$0Dwd?gF>+sDy+*T4VY#NhLHmR7)B1J*^)M(!WC`-0Cb%Wx=sfGaCdtpEw0KX zJHWS&Z>bDaxTFETb$m-@pu#1$fNve&QW>alg$6|9A`4Zn&wypWYYXebGho{m9v;o# zGvK-|8jX80QRN^;&^!GywE_lYP$-tjaaEmw zfC1aKLX)HZ>*RW`)A;<-Ae&v578nKX_7i$<&%sBj==QR03#n9ES%l*K>`Y(^^7$3t zO2wnrsCq+0ZUJKquImcPVW4=7F*KWX@fI+~$YMH>;rR?Sn{_Rre~f}pTL-l=D{X1l P00000NkvXXu0mjfZd~KU literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/gasrecycler.rsi/meta.json b/Resources/Textures/Structures/Machines/gasrecycler.rsi/meta.json new file mode 100644 index 0000000000..40d9c0cb83 --- /dev/null +++ b/Resources/Textures/Structures/Machines/gasrecycler.rsi/meta.json @@ -0,0 +1,30 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by Peptide90 for SS14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "unlit" + }, + { + "name": "running", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Machines/gasrecycler.rsi/running.png b/Resources/Textures/Structures/Machines/gasrecycler.rsi/running.png new file mode 100644 index 0000000000000000000000000000000000000000..e1f0759da6cbfd6e333883b4f554653a5a2aa6d4 GIT binary patch literal 843 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQjEnx?oJHr&dI!FU|?qTba4!+ znDcgyZ?>?b%(3}i>kb9wB+c?(*|g;O15-!NiWiRd4H6%oN>_Y&;kma`i{mecT$O;! z%Hv)gwx%3WdsMQ6IJxT@DlW9#w3s}%_N33t|372R?#})HC+&2}x7II=9EwGe!Syd} zYOG2X@78}#^4&ha?)$$?>=UR71zrzyq_Pzui*c-((%;KY<)%s136wGVqlohcYpaS6aTfh z+TVR%m3`HU^Lc&@@cy3te1=4eBhw%6`^Eb4j5_TVm1ay&<)_PW!9tUx_j>SXB$Zv5!0-6vZ)jFDFBV^x-q_vw2q8znAFIZ|dMN zTFYSf|JO_XYd}Z5-oIa3y6<1^@x_eu?$)o({ru|b=bv|%`^I+z9X7v;HDlYYW5=h4 z-eY~3|8Drr@;neof=_y6A;Zn(HZ^7gE6>baAD89VNeu79Mez`^7am||Kn zdzS*oh1ar?`i)=KUyF~j$#|v1o%#Aj`Fo&WJYD@<);T3K0RTq*kt+ZI literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/gasrecycler.rsi/unlit.png b/Resources/Textures/Structures/Machines/gasrecycler.rsi/unlit.png new file mode 100644 index 0000000000000000000000000000000000000000..b1823f27705448f582e7125dcc50eb435f14a106 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}xt=bLArY;~ z2@452~?ESR}rj`2}l4f#;s8 zpCa0`B^