mech nitrogen filtering 2 (#19868)
* target oxygen logic * filter out nitrogen when low on oxygen * vvrw and datafield for everything * :trollface: * bruh does work * tagless chicken * move into atmos, make it not depend on mech * update mech prototype --------- Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
46
Content.Server/Atmos/Components/AirFilterComponent.cs
Normal file
46
Content.Server/Atmos/Components/AirFilterComponent.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Server.Atmos.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is basically a reverse scrubber but using <see cref="GetFilterAirEvent"/>.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(AirFilterSystem))]
|
||||||
|
public sealed partial class AirFilterComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gases that will be filtered out of internal air
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public HashSet<Gas> Gases = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gases that will be filtered out of internal air to maintain oxygen ratio.
|
||||||
|
/// When oxygen is below <see cref="TargetOxygen"/>, these gases will be filtered instead of <see cref="Gases"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public HashSet<Gas> OverflowGases = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum oxygen fraction before it will start removing <see cref="OverflowGases"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float TargetOxygen = 0.21f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gas to consider oxygen for <see cref="TargetOxygen"/> and <see cref="OverflowGases"/> logic.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For slime you might want to change this to be nitrogen, and overflowgases to remove oxygen.
|
||||||
|
/// However theres still no real danger since standard atmos is mostly nitrogen so nitrogen tends to 100% anyway.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public Gas Oxygen = Gas.Oxygen;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fraction of target volume to transfer every second.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float TransferRate = 0.1f;
|
||||||
|
}
|
||||||
29
Content.Server/Atmos/Components/AirIntakeComponent.cs
Normal file
29
Content.Server/Atmos/Components/AirIntakeComponent.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Server.Atmos.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is basically a siphon vent for <see cref="GetFilterAirEvent"/>.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(AirFilterSystem))]
|
||||||
|
public sealed partial class AirIntakeComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Target pressure change for a single atmos tick
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float TargetPressureChange = 5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How strong the intake pump is, it will be able to replenish air from lower pressure areas.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float PumpPower = 2f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pressure to intake gases up to, maintains pressure of the air volume.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float Pressure = Atmospherics.OneAtmosphere;
|
||||||
|
}
|
||||||
113
Content.Server/Atmos/EntitySystems/AirFilterSystem.cs
Normal file
113
Content.Server/Atmos/EntitySystems/AirFilterSystem.cs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
using Content.Server.Atmos;
|
||||||
|
using Content.Server.Atmos.Components;
|
||||||
|
using Content.Server.Atmos.Piping.Components;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Content.Server.Atmos.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles gas filtering and intake for <see cref="AirIntakeComponent"/> and <see cref="AirFilterComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AirFilterSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
|
[Dependency] private readonly IMapManager _map = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AirIntakeComponent, AtmosDeviceUpdateEvent>(OnIntakeUpdate);
|
||||||
|
SubscribeLocalEvent<AirFilterComponent, AtmosDeviceUpdateEvent>(OnFilterUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnIntakeUpdate(EntityUid uid, AirIntakeComponent intake, 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, 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, AtmosDeviceUpdateEvent args)
|
||||||
|
{
|
||||||
|
if (!GetAir(uid, out var air))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ratio = MathF.Min(1f, args.dt * filter.TransferRate);
|
||||||
|
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;
|
||||||
|
|
||||||
|
var coordinates = Transform(uid).MapPosition;
|
||||||
|
GasMixture? destination = null;
|
||||||
|
if (_map.TryFindGridAt(coordinates, out _, out var grid))
|
||||||
|
{
|
||||||
|
var tile = grid.GetTileRef(coordinates);
|
||||||
|
destination = _atmosphere.GetTileMixture(tile.GridUid, null, tile.GridIndices, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses <see cref="GetFilterAirEvent"/> to get an internal volume of air on an entity.
|
||||||
|
/// Used for both filter and intake.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[ByRefEvent]
|
||||||
|
public record struct GetFilterAirEvent(GasMixture? Air = null);
|
||||||
@@ -6,7 +6,8 @@ namespace Content.Server.Mech.Components;
|
|||||||
public sealed partial class MechAirComponent : Component
|
public sealed partial class MechAirComponent : Component
|
||||||
{
|
{
|
||||||
//TODO: this doesn't support a tank implant for mechs or anything like that
|
//TODO: this doesn't support a tank implant for mechs or anything like that
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
public GasMixture Air = new (GasMixVolume);
|
public GasMixture Air = new (GasMixVolume);
|
||||||
|
|
||||||
public const float GasMixVolume = 70f;
|
public const float GasMixVolume = 70f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using Content.Shared.Atmos;
|
|
||||||
|
|
||||||
namespace Content.Server.Mech.Components;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is basically a reverse scrubber for MechAir
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class MechAirFilterComponent : Component
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gases that will be filtered out of internal air
|
|
||||||
/// </summary>
|
|
||||||
[DataField("gases", required: true)]
|
|
||||||
public HashSet<Gas> Gases = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Target volume to transfer every second.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("transferRate")]
|
|
||||||
public float TransferRate = MechAirComponent.GasMixVolume * 0.1f;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using Content.Shared.Atmos;
|
|
||||||
|
|
||||||
namespace Content.Server.Mech.Components;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is basically a siphon vent for mech but not using pump vent component because MechAir bad
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class MechAirIntakeComponent : Component
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Target pressure change for a single atmos tick
|
|
||||||
/// </summary>
|
|
||||||
[DataField("targetPressureChange")]
|
|
||||||
public float TargetPressureChange = 5f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How strong the intake pump is, it will be able to replenish air from lower pressure areas.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("pumpPower")]
|
|
||||||
public float PumpPower = 2f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pressure to intake gases up to, maintains MechAir pressure.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("pressure")]
|
|
||||||
public float Pressure = Atmospherics.OneAtmosphere;
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
using Content.Server.Atmos;
|
|
||||||
using Content.Server.Atmos.Piping.Components;
|
|
||||||
using Content.Server.Mech.Components;
|
|
||||||
using Content.Shared.Atmos;
|
|
||||||
using Content.Shared.Mech.Components;
|
|
||||||
|
|
||||||
namespace Content.Server.Mech.Systems;
|
|
||||||
|
|
||||||
// TODO: this could be reused for gasmask or something if MechAir wasnt a thing
|
|
||||||
public sealed partial class MechSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
||||||
|
|
||||||
private void InitializeFiltering()
|
|
||||||
{
|
|
||||||
SubscribeLocalEvent<MechAirIntakeComponent, AtmosDeviceUpdateEvent>(OnIntakeUpdate);
|
|
||||||
SubscribeLocalEvent<MechAirFilterComponent, AtmosDeviceUpdateEvent>(OnFilterUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnIntakeUpdate(EntityUid uid, MechAirIntakeComponent intake, AtmosDeviceUpdateEvent args)
|
|
||||||
{
|
|
||||||
if (!TryComp<MechComponent>(uid, out var mech) || !mech.Airtight || !TryComp<MechAirComponent>(uid, out var mechAir))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// if the mech is filled there is nothing to do
|
|
||||||
if (mechAir.Air.Pressure >= intake.Pressure)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var environment = _atmosphere.GetContainingMixture(uid, 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 - mechAir.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 * mechAir.Air.Volume / (environment.Temperature * Atmospherics.R);
|
|
||||||
_atmosphere.Merge(mechAir.Air, environment.Remove(transferMoles));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFilterUpdate(EntityUid uid, MechAirFilterComponent filter, AtmosDeviceUpdateEvent args)
|
|
||||||
{
|
|
||||||
if (!TryComp<MechComponent>(uid, out var mech) || !mech.Airtight || !TryComp<MechAirComponent>(uid, out var mechAir))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var ratio = MathF.Min(1f, args.dt * filter.TransferRate / mechAir.Air.Volume);
|
|
||||||
var removed = mechAir.Air.RemoveRatio(ratio);
|
|
||||||
// nothing left to remove from the mech
|
|
||||||
if (MathHelper.CloseToPercent(removed.TotalMoles, 0f))
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
var coordinates = Transform(uid).MapPosition;
|
|
||||||
GasMixture? destination = null;
|
|
||||||
if (_map.TryFindGridAt(coordinates, out var gridId, out var grid))
|
|
||||||
{
|
|
||||||
var tile = _mapSystem.GetTileRef(gridId, grid, coordinates);
|
|
||||||
destination = _atmosphere.GetTileMixture(tile.GridUid, null, tile.GridIndices, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destination != null)
|
|
||||||
{
|
|
||||||
_atmosphere.ScrubInto(removed, destination, filter.Gases);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// filtering into space/planet so just discard them
|
|
||||||
foreach (var gas in filter.Gases)
|
|
||||||
{
|
|
||||||
removed.SetMoles(gas, 0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_atmosphere.Merge(mechAir.Air, removed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -47,8 +47,6 @@ public sealed partial class MechSystem : SharedMechSystem
|
|||||||
|
|
||||||
_sawmill = Logger.GetSawmill("mech");
|
_sawmill = Logger.GetSawmill("mech");
|
||||||
|
|
||||||
InitializeFiltering();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<MechComponent, InteractUsingEvent>(OnInteractUsing);
|
SubscribeLocalEvent<MechComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
SubscribeLocalEvent<MechComponent, EntInsertedIntoContainerMessage>(OnInsertBattery);
|
SubscribeLocalEvent<MechComponent, EntInsertedIntoContainerMessage>(OnInsertBattery);
|
||||||
SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
|
||||||
@@ -69,6 +67,8 @@ public sealed partial class MechSystem : SharedMechSystem
|
|||||||
SubscribeLocalEvent<MechPilotComponent, ExhaleLocationEvent>(OnExhale);
|
SubscribeLocalEvent<MechPilotComponent, ExhaleLocationEvent>(OnExhale);
|
||||||
SubscribeLocalEvent<MechPilotComponent, AtmosExposedGetAirEvent>(OnExpose);
|
SubscribeLocalEvent<MechPilotComponent, AtmosExposedGetAirEvent>(OnExpose);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<MechAirComponent, GetFilterAirEvent>(OnGetFilterAir);
|
||||||
|
|
||||||
#region Equipment UI message relays
|
#region Equipment UI message relays
|
||||||
SubscribeLocalEvent<MechComponent, MechGrabberEjectMessage>(ReceiveEquipmentUiMesssages);
|
SubscribeLocalEvent<MechComponent, MechGrabberEjectMessage>(ReceiveEquipmentUiMesssages);
|
||||||
SubscribeLocalEvent<MechComponent, MechSoundboardPlayMessage>(ReceiveEquipmentUiMesssages);
|
SubscribeLocalEvent<MechComponent, MechSoundboardPlayMessage>(ReceiveEquipmentUiMesssages);
|
||||||
@@ -423,5 +423,17 @@ public sealed partial class MechSystem : SharedMechSystem
|
|||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnGetFilterAir(EntityUid uid, MechAirComponent comp, ref GetFilterAirEvent args)
|
||||||
|
{
|
||||||
|
if (args.Air != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// only airtight mechs get internal air
|
||||||
|
if (!TryComp<MechComponent>(uid, out var mech) || !mech.Airtight)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Air = comp.Air;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,29 @@
|
|||||||
- type: MobMover
|
- type: MobMover
|
||||||
- type: Mech
|
- type: Mech
|
||||||
- type: MechAir
|
- type: MechAir
|
||||||
- type: MechAirFilter
|
- type: AirFilter
|
||||||
# everything except oxygen and nitrogen
|
# everything except oxygen and nitrogen
|
||||||
gases:
|
gases:
|
||||||
- 2
|
- CarbonDioxide
|
||||||
- 3
|
- Plasma
|
||||||
- 4
|
- Tritium
|
||||||
- 5
|
- WaterVapor
|
||||||
- 6
|
- Miasma
|
||||||
- 7
|
- NitrousOxide
|
||||||
- 8
|
- Frezon
|
||||||
#- 9 TODO: fusion
|
#- Helium3 TODO: fusion
|
||||||
- type: MechAirIntake
|
# remove everything except oxygen to maintain oxygen ratio
|
||||||
|
overflowGases:
|
||||||
|
- Nitrogen
|
||||||
|
- CarbonDioxide
|
||||||
|
- Plasma
|
||||||
|
- Tritium
|
||||||
|
- WaterVapor
|
||||||
|
- Miasma
|
||||||
|
- NitrousOxide
|
||||||
|
- Frezon
|
||||||
|
#- Helium3 TODO: fusion
|
||||||
|
- type: AirIntake
|
||||||
# for intake and filter to work
|
# for intake and filter to work
|
||||||
- type: AtmosDevice
|
- type: AtmosDevice
|
||||||
requireAnchored: false
|
requireAnchored: false
|
||||||
|
|||||||
Reference in New Issue
Block a user