CatStation/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasMixerSystem.cs
psykana ad60a5a5a5
Predict gas filter and mixer BUIs (#43712)
* move

* NetworkedComponent

* anchor

* split

* move

* mixer

* fix

* builds

* I forgor

* moar

* filename typo

* fix admin log

* ftl

* metadata

* IOC readonly 2

* yet another IOC readonly

* new features

* test I fix you

* review 1

* review 2

* docs
2026-07-19 23:50:39 +00:00

151 lines
6.1 KiB
C#

using Content.Server.Atmos.EntitySystems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping.Components;
using Content.Shared.Atmos.Piping.Trinary.Components;
using Content.Shared.Atmos.Piping.Trinary.EntitySystems;
using Content.Shared.Audio;
using JetBrains.Annotations;
namespace Content.Server.Atmos.Piping.Trinary.EntitySystems;
[UsedImplicitly]
public sealed partial class GasMixerSystem : SharedGasMixerSystem
{
[Dependency] private SharedUserInterfaceSystem _ui = default!;
[Dependency] private AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private NodeContainerSystem _nodeContainer = default!;
[SubscribeLocalEvent]
private void OnInit(Entity<GasMixerComponent> ent, ref ComponentInit args)
{
UpdateAppearance(ent);
}
[SubscribeLocalEvent]
private void OnMixerUpdated(Entity<GasMixerComponent> ent, ref AtmosDeviceUpdateEvent args)
{
// TODO ATMOS: Cache total moles since it's expensive.
if (!ent.Comp.Enabled
|| !_nodeContainer.TryGetNodes(ent.Owner, ent.Comp.InletOne, ent.Comp.InletTwo, ent.Comp.Outlet, out PipeNode? inletOne, out PipeNode? inletTwo, out PipeNode? outlet))
{
_ambientSoundSystem.SetAmbience(ent.Owner, false);
return;
}
var outputStartingPressure = outlet.Air.Pressure;
if (outputStartingPressure >= ent.Comp.TargetPressure)
return; // Target reached, no need to mix.
var generalTransfer = (ent.Comp.TargetPressure - outputStartingPressure) * outlet.Air.Volume / Atmospherics.R;
var transferMolesOne = inletOne.Air.Temperature > 0 ? ent.Comp.InletOneConcentration * generalTransfer / inletOne.Air.Temperature : 0f;
var transferMolesTwo = inletTwo.Air.Temperature > 0 ? ent.Comp.InletTwoConcentration * generalTransfer / inletTwo.Air.Temperature : 0f;
if (ent.Comp.InletTwoConcentration <= 0f)
{
if (inletOne.Air.Temperature <= 0f)
return;
transferMolesOne = MathF.Min(transferMolesOne, inletOne.Air.TotalMoles);
transferMolesTwo = 0f;
}
else if (ent.Comp.InletOneConcentration <= 0)
{
if (inletTwo.Air.Temperature <= 0f)
return;
transferMolesOne = 0f;
transferMolesTwo = MathF.Min(transferMolesTwo, inletTwo.Air.TotalMoles);
}
else
{
if (inletOne.Air.Temperature <= 0f || inletTwo.Air.Temperature <= 0f)
return;
if (transferMolesOne <= 0 || transferMolesTwo <= 0)
{
_ambientSoundSystem.SetAmbience(ent.Owner, false);
return;
}
if (inletOne.Air.TotalMoles < transferMolesOne || inletTwo.Air.TotalMoles < transferMolesTwo)
{
var ratio = MathF.Min(inletOne.Air.TotalMoles / transferMolesOne, inletTwo.Air.TotalMoles / transferMolesTwo);
transferMolesOne *= ratio;
transferMolesTwo *= ratio;
}
}
// Actually transfer the gas now.
var transferred = false;
if (transferMolesOne > 0f)
{
transferred = true;
var removed = inletOne.Air.Remove(transferMolesOne);
_atmosphereSystem.Merge(outlet.Air, removed);
}
if (transferMolesTwo > 0f)
{
transferred = true;
var removed = inletTwo.Air.Remove(transferMolesTwo);
_atmosphereSystem.Merge(outlet.Air, removed);
}
if (transferred)
_ambientSoundSystem.SetAmbience(ent.Owner, true);
}
[SubscribeLocalEvent]
private void OnMixerLeaveAtmosphere(Entity<GasMixerComponent> ent, ref AtmosDeviceDisabledEvent args)
{
ent.Comp.Enabled = false;
DirtyField(ent.Owner, ent.Comp, nameof(GasMixerComponent.Enabled));
UpdateAppearance(ent);
_ambientSoundSystem.SetAmbience(ent.Owner, false);
_ui.CloseUi(ent.Owner, GasMixerUiKey.Key);
}
/// <summary>
/// Returns the gas mixture for the gas analyzer
/// </summary>
[SubscribeLocalEvent]
private void OnMixerAnalyzed(Entity<GasMixerComponent> ent, ref GasAnalyzerScanEvent args)
{
args.GasMixtures ??= new List<(string, GasMixture?)>();
// multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system
if (_nodeContainer.TryGetNode(ent.Owner, ent.Comp.InletOne, out PipeNode? inletOne) && inletOne.Air.Volume != 0f)
{
var inletOneAirLocal = inletOne.Air.Clone();
inletOneAirLocal.Multiply(inletOne.Volume / inletOne.Air.Volume);
inletOneAirLocal.Volume = inletOne.Volume;
args.GasMixtures.Add(($"{inletOne.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletOneAirLocal));
}
if (_nodeContainer.TryGetNode(ent.Owner, ent.Comp.InletTwo, out PipeNode? inletTwo) && inletTwo.Air.Volume != 0f)
{
var inletTwoAirLocal = inletTwo.Air.Clone();
inletTwoAirLocal.Multiply(inletTwo.Volume / inletTwo.Air.Volume);
inletTwoAirLocal.Volume = inletTwo.Volume;
args.GasMixtures.Add(($"{inletTwo.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletTwoAirLocal));
}
if (_nodeContainer.TryGetNode(ent.Owner, ent.Comp.Outlet, out PipeNode? outlet) && outlet.Air.Volume != 0f)
{
var outletAirLocal = outlet.Air.Clone();
outletAirLocal.Multiply(outlet.Volume / outlet.Air.Volume);
outletAirLocal.Volume = outlet.Volume;
args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-outlet"), outletAirLocal));
}
args.DeviceFlipped = inletOne != null && inletTwo != null && inletOne.CurrentPipeDirection.ToDirection() == inletTwo.CurrentPipeDirection.ToDirection().GetClockwise90Degrees();
}
}