using Content.Server.Atmos.Components; using Content.Server.Atmos.Piping.Components; using Content.Shared.Atmos; using Robust.Shared.Map; using System.Diagnostics.CodeAnalysis; namespace Content.Server.Atmos.EntitySystems; /// /// Handles gas filtering and intake for and . /// public sealed class AirFilterSystem : EntitySystem { [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnIntakeUpdate); SubscribeLocalEvent(OnFilterUpdate); } private void OnIntakeUpdate(EntityUid uid, AirIntakeComponent intake, ref AtmosDeviceUpdateEvent args) { if (!GetAir(uid, out var air)) return; // if the volume is filled there is nothing to do if (air.Pressure >= intake.Pressure) return; var environment = _atmosphere.GetContainingMixture(uid, args.Grid, args.Map, true, true); // nothing to intake from if (environment == null) return; // absolute maximum pressure change var pressureDelta = args.dt * intake.TargetPressureChange; pressureDelta = MathF.Min(pressureDelta, intake.Pressure - air.Pressure); if (pressureDelta <= 0) return; // how many moles to transfer to change internal pressure by pressureDelta // ignores temperature difference because lazy var transferMoles = pressureDelta * air.Volume / (environment.Temperature * Atmospherics.R); _atmosphere.Merge(air, environment.Remove(transferMoles)); } private void OnFilterUpdate(EntityUid uid, AirFilterComponent filter, ref AtmosDeviceUpdateEvent args) { if (!GetAir(uid, out var air)) return; var ratio = MathF.Min(1f, args.dt * filter.TransferRate * _atmosphere.PumpSpeedup()); var removed = air.RemoveRatio(ratio); // nothing left to remove from the volume if (MathHelper.CloseToPercent(removed.TotalMoles, 0f)) return; // when oxygen gets too low start removing overflow gases (nitrogen) to maintain oxygen ratio var oxygen = air.GetMoles(filter.Oxygen) / air.TotalMoles; var gases = oxygen >= filter.TargetOxygen ? filter.Gases : filter.OverflowGases; GasMixture? destination = null; if (args.Grid is {} grid) { var position = _transform.GetGridTilePositionOrDefault(uid); destination = _atmosphere.GetTileMixture(grid, args.Map, position, true); } if (destination != null) { _atmosphere.ScrubInto(removed, destination, gases); } else { // filtering into space/planet so just discard them foreach (var gas in gases) { removed.SetMoles(gas, 0f); } } _atmosphere.Merge(air, removed); } /// /// Uses to get an internal volume of air on an entity. /// Used for both filter and intake. /// public bool GetAir(EntityUid uid, [NotNullWhen(true)] out GasMixture? air) { air = null; var ev = new GetFilterAirEvent(); RaiseLocalEvent(uid, ref ev); air = ev.Air; return air != null; } } /// /// Get a reference to an entity's air volume to filter. /// Do not create a new mixture as this will be modified when filtering and intaking air. /// [ByRefEvent] public record struct GetFilterAirEvent(GasMixture? Air = null);