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.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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<GasRecyclerComponent, AtmosDeviceEnabledEvent>(OnEnabled);
|
||||
SubscribeLocalEvent<GasRecyclerComponent, AtmosDeviceUpdateEvent>(OnUpdate);
|
||||
SubscribeLocalEvent<GasRecyclerComponent, AtmosDeviceDisabledEvent>(OnDisabled);
|
||||
SubscribeLocalEvent<GasRecyclerComponent, ExaminedEvent>(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<TransformComponent>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
3
Resources/Locale/en-US/atmos/gas-recycler-system.ftl
Normal file
3
Resources/Locale/en-US/atmos/gas-recycler-system.ftl
Normal file
@@ -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].
|
||||
@@ -270,6 +270,7 @@
|
||||
unlockedRecipes:
|
||||
- ThermomachineFreezerMachineCircuitBoard
|
||||
- PortableScrubberMachineCircuitBoard
|
||||
- GasRecyclerMachineCircuitboard
|
||||
|
||||
# Avionics Circuitry Technology Tree
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -278,6 +278,7 @@
|
||||
- WallmountGeneratorAPUElectronics
|
||||
- WallmountSubstationElectronics
|
||||
- EmitterCircuitboard
|
||||
- GasRecyclerMachineCircuitboard
|
||||
- type: Machine
|
||||
board: CircuitImprinterMachineCircuitboard
|
||||
- type: Lathe
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
BIN
Resources/Textures/Structures/Machines/gasrecycler.rsi/icon.png
Normal file
BIN
Resources/Textures/Structures/Machines/gasrecycler.rsi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 525 B |
@@ -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
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 843 B |
BIN
Resources/Textures/Structures/Machines/gasrecycler.rsi/unlit.png
Normal file
BIN
Resources/Textures/Structures/Machines/gasrecycler.rsi/unlit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 B |
Reference in New Issue
Block a user