Portable scrubbers (#9417)
This commit is contained in:
@@ -150,18 +150,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
if (portNode.NodeGroup is PipeNet {NodeCount: > 1} net)
|
||||
{
|
||||
var buffer = new GasMixture(net.Air.Volume + canister.Air.Volume);
|
||||
|
||||
_atmosphereSystem.Merge(buffer, net.Air);
|
||||
_atmosphereSystem.Merge(buffer, canister.Air);
|
||||
|
||||
net.Air.Clear();
|
||||
_atmosphereSystem.Merge(net.Air, buffer);
|
||||
net.Air.Multiply(net.Air.Volume / buffer.Volume);
|
||||
|
||||
canister.Air.Clear();
|
||||
_atmosphereSystem.Merge(canister.Air, buffer);
|
||||
canister.Air.Multiply(canister.Air.Volume / buffer.Volume);
|
||||
MixContainerWithPipeNet(canister.Air, net.Air);
|
||||
}
|
||||
|
||||
ContainerManagerComponent? containerManager = null;
|
||||
@@ -275,5 +264,25 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
|
||||
appearance.SetData(GasCanisterVisuals.TankInserted, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mix air from a gas container into a pipe net.
|
||||
/// Useful for anything that uses connector ports.
|
||||
/// </summary>
|
||||
public void MixContainerWithPipeNet(GasMixture containerAir, GasMixture pipeNetAir)
|
||||
{
|
||||
var buffer = new GasMixture(pipeNetAir.Volume + containerAir.Volume);
|
||||
|
||||
_atmosphereSystem.Merge(buffer, pipeNetAir);
|
||||
_atmosphereSystem.Merge(buffer, containerAir);
|
||||
|
||||
pipeNetAir.Clear();
|
||||
_atmosphereSystem.Merge(pipeNetAir, buffer);
|
||||
pipeNetAir.Multiply(pipeNetAir.Volume / buffer.Volume);
|
||||
|
||||
containerAir.Clear();
|
||||
_atmosphereSystem.Merge(containerAir, buffer);
|
||||
containerAir.Multiply(containerAir.Volume / buffer.Volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private bool FindGasPortIn(EntityUid? gridId, EntityCoordinates coordinates, [NotNullWhen(true)] out GasPortComponent? port)
|
||||
public bool FindGasPortIn(EntityUid? gridId, EntityCoordinates coordinates, [NotNullWhen(true)] out GasPortComponent? port)
|
||||
{
|
||||
port = null;
|
||||
|
||||
|
||||
@@ -86,33 +86,42 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
AtmosDeviceEnabledEvent args) => UpdateState(uid, component);
|
||||
|
||||
private void Scrub(float timeDelta, GasVentScrubberComponent scrubber, GasMixture? tile, PipeNode outlet)
|
||||
{
|
||||
Scrub(timeDelta, scrubber.TransferRate, scrubber.PumpDirection, scrubber.FilterGases, tile, outlet.Air);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if we were able to scrub, false if we were not.
|
||||
/// </summary>
|
||||
public bool Scrub(float timeDelta, float transferRate, ScrubberPumpDirection mode, HashSet<Gas> filterGases, GasMixture? tile, GasMixture destination)
|
||||
{
|
||||
// Cannot scrub if tile is null or air-blocked.
|
||||
if (tile == null
|
||||
|| outlet.Air.Pressure >= 50 * Atmospherics.OneAtmosphere) // Cannot scrub if pressure too high.
|
||||
|| destination.Pressure >= 50 * Atmospherics.OneAtmosphere) // Cannot scrub if pressure too high.
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Take a gas sample.
|
||||
var ratio = MathF.Min(1f, timeDelta * scrubber.TransferRate / tile.Volume);
|
||||
var ratio = MathF.Min(1f, timeDelta * transferRate / tile.Volume);
|
||||
var removed = tile.RemoveRatio(ratio);
|
||||
|
||||
// Nothing left to remove from the tile.
|
||||
if (MathHelper.CloseToPercent(removed.TotalMoles, 0f))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (scrubber.PumpDirection == ScrubberPumpDirection.Scrubbing)
|
||||
if (mode == ScrubberPumpDirection.Scrubbing)
|
||||
{
|
||||
_atmosphereSystem.ScrubInto(removed, outlet.Air, scrubber.FilterGases);
|
||||
_atmosphereSystem.ScrubInto(removed, destination, filterGases);
|
||||
|
||||
// Remix the gases.
|
||||
_atmosphereSystem.Merge(tile, removed);
|
||||
}
|
||||
else if (scrubber.PumpDirection == ScrubberPumpDirection.Siphoning)
|
||||
else if (mode == ScrubberPumpDirection.Siphoning)
|
||||
{
|
||||
_atmosphereSystem.Merge(outlet.Air, removed);
|
||||
_atmosphereSystem.Merge(destination, removed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnAtmosAlarm(EntityUid uid, GasVentScrubberComponent component, AtmosMonitorAlarmEvent args)
|
||||
|
||||
48
Content.Server/Atmos/Portable/PortableScrubberComponent.cs
Normal file
48
Content.Server/Atmos/Portable/PortableScrubberComponent.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Server.Atmos.Portable
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class PortableScrubberComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The air inside this machine.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("gasMixture")]
|
||||
public GasMixture Air { get; } = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("port")]
|
||||
public string PortName { get; set; } = "port";
|
||||
|
||||
/// <summary>
|
||||
/// Which gases this machine will scrub out.
|
||||
/// Unlike fixed scrubbers controlled by an air alarm,
|
||||
/// this can't be changed in game.
|
||||
/// </summary>
|
||||
[DataField("filterGases")]
|
||||
public HashSet<Gas> FilterGases = new()
|
||||
{
|
||||
Gas.CarbonDioxide,
|
||||
Gas.Plasma,
|
||||
Gas.Tritium,
|
||||
Gas.WaterVapor,
|
||||
Gas.Miasma
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Can this scrubber hold more gas?
|
||||
/// </summary>
|
||||
public bool Full => Air.Pressure >= MaxPressure;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum internal pressure before it refuses to take more.
|
||||
/// </summary>
|
||||
[DataField("maxPressure")]
|
||||
public float MaxPressure = 3000f;
|
||||
[DataField("transferRate")]
|
||||
public float TransferRate = 1000f;
|
||||
public bool Enabled = true;
|
||||
}
|
||||
}
|
||||
162
Content.Server/Atmos/Portable/PortableScrubberSystem.cs
Normal file
162
Content.Server/Atmos/Portable/PortableScrubberSystem.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Atmos.Visuals;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.Audio;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
|
||||
|
||||
|
||||
namespace Content.Server.Atmos.Portable
|
||||
{
|
||||
public sealed class PortableScrubberSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly GasVentScrubberSystem _scrubberSystem = default!;
|
||||
[Dependency] private readonly GasCanisterSystem _canisterSystem = default!;
|
||||
[Dependency] private readonly GasPortableSystem _gasPortableSystem = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PortableScrubberComponent, AtmosDeviceUpdateEvent>(OnDeviceUpdated);
|
||||
SubscribeLocalEvent<PortableScrubberComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||
SubscribeLocalEvent<PortableScrubberComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<PortableScrubberComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<PortableScrubberComponent, DestructionEventArgs>(OnDestroyed);
|
||||
}
|
||||
|
||||
private void OnDeviceUpdated(EntityUid uid, PortableScrubberComponent component, AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out AtmosDeviceComponent? device))
|
||||
return;
|
||||
|
||||
var timeDelta = (float) (_gameTiming.CurTime - device.LastProcess).TotalSeconds;
|
||||
|
||||
if (!component.Enabled)
|
||||
return;
|
||||
|
||||
/// If we are on top of a connector port, empty into it.
|
||||
if (TryComp<NodeContainerComponent>(uid, out var nodeContainer)
|
||||
&& nodeContainer.TryGetNode(component.PortName, out PortablePipeNode? portableNode)
|
||||
&& portableNode.ConnectionsEnabled)
|
||||
{
|
||||
_atmosphereSystem.React(component.Air, portableNode);
|
||||
if (portableNode.NodeGroup is PipeNet {NodeCount: > 1} net)
|
||||
_canisterSystem.MixContainerWithPipeNet(component.Air, net.Air);
|
||||
}
|
||||
|
||||
if (component.Full)
|
||||
{
|
||||
UpdateAppearance(uid, true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (xform.GridUid == null)
|
||||
return;
|
||||
|
||||
var position = _transformSystem.GetGridOrMapTilePosition(uid, xform);
|
||||
|
||||
var environment = _atmosphereSystem.GetTileMixture(xform.GridUid, xform.MapUid, position, true);
|
||||
|
||||
var running = Scrub(timeDelta, component, environment);
|
||||
|
||||
UpdateAppearance(uid, false, running);
|
||||
/// We scrub once to see if we can and set the animation
|
||||
if (!running)
|
||||
return;
|
||||
/// widenet
|
||||
foreach (var adjacent in _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true))
|
||||
{
|
||||
Scrub(timeDelta, component, environment);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If there is a port under us, let us connect with adjacent atmos pipes.
|
||||
/// </summary>
|
||||
private void OnAnchorChanged(EntityUid uid, PortableScrubberComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
|
||||
if (!nodeContainer.TryGetNode(component.PortName, out PipeNode? portableNode))
|
||||
return;
|
||||
|
||||
portableNode.ConnectionsEnabled = (args.Anchored && _gasPortableSystem.FindGasPortIn(Transform(uid).GridUid, Transform(uid).Coordinates, out _));
|
||||
|
||||
UpdateDrainingAppearance(uid, portableNode.ConnectionsEnabled);
|
||||
}
|
||||
private void OnPowerChanged(EntityUid uid, PortableScrubberComponent component, PowerChangedEvent args)
|
||||
{
|
||||
UpdateAppearance(uid, component.Full, args.Powered);
|
||||
component.Enabled = args.Powered;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Examining tells you how full it is as a %.
|
||||
/// </summary>
|
||||
private void OnExamined(EntityUid uid, PortableScrubberComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
var percentage = Math.Round(((component.Air.Pressure) / component.MaxPressure) * 100);
|
||||
args.PushMarkup(Loc.GetString("portable-scrubber-fill-level", ("percent", percentage)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When this is destroyed, we dump out all the gas inside.
|
||||
/// </summary>
|
||||
private void OnDestroyed(EntityUid uid, PortableScrubberComponent component, DestructionEventArgs args)
|
||||
{
|
||||
var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
|
||||
|
||||
if (environment != null)
|
||||
_atmosphereSystem.Merge(environment, component.Air);
|
||||
|
||||
_adminLogger.Add(LogType.CanisterPurged, LogImpact.Medium, $"Portable scrubber {ToPrettyString(uid):canister} purged its contents of {component.Air:gas} into the environment.");
|
||||
component.Air.Clear();
|
||||
}
|
||||
|
||||
private bool Scrub(float timeDelta, PortableScrubberComponent scrubber, GasMixture? tile)
|
||||
{
|
||||
return _scrubberSystem.Scrub(timeDelta, scrubber.TransferRate, ScrubberPumpDirection.Scrubbing, scrubber.FilterGases, tile, scrubber.Air);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, bool isFull, bool isRunning)
|
||||
{
|
||||
if (!TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
return;
|
||||
|
||||
_ambientSound.SetAmbience(uid, isRunning);
|
||||
|
||||
appearance.SetData(PortableScrubberVisuals.IsFull, isFull);
|
||||
appearance.SetData(PortableScrubberVisuals.IsRunning, isRunning);
|
||||
}
|
||||
|
||||
private void UpdateDrainingAppearance(EntityUid uid, bool isDraining)
|
||||
{
|
||||
if (!TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
return;
|
||||
|
||||
appearance.SetData(PortableScrubberVisuals.IsDraining, isDraining);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user