mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-02-14 19:29:57 +01:00
Fix atmos devices not correctly reffing the changed atmos (#41585)
This commit is contained in:
130
Content.IntegrationTests/Tests/Atmos/AtmosMonitoringTest.cs
Normal file
130
Content.IntegrationTests/Tests/Atmos/AtmosMonitoringTest.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Atmos.Monitor.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Atmos;
|
||||
|
||||
/// <summary>
|
||||
/// Test for determining that an AtmosMonitoringComponent/System correctly references
|
||||
/// the GasMixture of the tile it is on if the tile's GasMixture ever changes.
|
||||
/// </summary>
|
||||
[TestOf(typeof(Atmospherics))]
|
||||
public sealed class AtmosMonitoringTest : AtmosTest
|
||||
{
|
||||
// We can just reuse the dP test, I just want a grid.
|
||||
protected override ResPath? TestMapPath => new("Maps/Test/Atmospherics/DeltaPressure/deltapressuretest.yml");
|
||||
|
||||
private readonly EntProtoId _airSensorProto = new("AirSensor");
|
||||
private readonly EntProtoId _wallProto = new("WallSolid");
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the monitor properly nulls out its reference to the tile mixture
|
||||
/// when a wall is placed on top of it, and restores the reference when the wall is removed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task NullOutTileAtmosphereGasMixture()
|
||||
{
|
||||
// run an atmos update to initialize everything For Real surely
|
||||
SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
|
||||
|
||||
var gridNetEnt = SEntMan.GetNetEntity(RelevantAtmos.Owner);
|
||||
TargetCoords = new NetCoordinates(gridNetEnt, Vector2.Zero);
|
||||
var netEnt = await Spawn(_airSensorProto);
|
||||
var airSensorUid = SEntMan.GetEntity(netEnt);
|
||||
Transform.TryGetGridTilePosition(airSensorUid, out var vec);
|
||||
|
||||
// run another one to ensure that the ref to the GasMixture was picked up
|
||||
SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
|
||||
|
||||
// should be in the middle
|
||||
Assert.That(vec,
|
||||
Is.EqualTo(Vector2i.Zero),
|
||||
"Air sensor not in expected position on grid (0, 0)");
|
||||
|
||||
var atmosMonitor = SEntMan.GetComponent<AtmosMonitorComponent>(airSensorUid);
|
||||
var tileMixture = SAtmos.GetTileMixture(airSensorUid);
|
||||
|
||||
Assert.That(tileMixture,
|
||||
Is.SameAs(atmosMonitor.TileGas),
|
||||
"Atmos monitor's TileGas does not match actual tile mixture after spawn.");
|
||||
|
||||
// ok now spawn a wall or something on top of it
|
||||
var wall = await Spawn(_wallProto);
|
||||
var wallUid = SEntMan.GetEntity(wall);
|
||||
|
||||
// ensure that atmospherics registers the change - the gas mixture should no longer exist
|
||||
SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
|
||||
|
||||
// the monitor's ref to the gas should be null now
|
||||
Assert.That(atmosMonitor.TileGas,
|
||||
Is.Null,
|
||||
"Atmos monitor's TileGas is not null after wall placed on top. Possible dead reference.");
|
||||
// the actual mixture on the tile should be null now too
|
||||
var nullTileMixture = SAtmos.GetTileMixture(airSensorUid);
|
||||
Assert.That(nullTileMixture, Is.Null, "Tile mixture is not null after wall placed on top.");
|
||||
|
||||
// ok now delete the wall
|
||||
await Delete(wallUid);
|
||||
|
||||
// ensure that atmospherics registers the change - the gas mixture should be back
|
||||
SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
|
||||
|
||||
// gas mixture should now exist again
|
||||
var newTileMixture = SAtmos.GetTileMixture(airSensorUid);
|
||||
Assert.That(newTileMixture, Is.Not.Null, "Tile mixture is null after wall removed.");
|
||||
// monitor's ref to the gas should be back too
|
||||
Assert.That(atmosMonitor.TileGas,
|
||||
Is.SameAs(newTileMixture),
|
||||
"Atmos monitor's TileGas does not match actual tile mixture after wall removed.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the monitor properly updates its reference to the tile mixture
|
||||
/// when the FixGridAtmos command is called.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task FixGridAtmosReplaceMixtureOnTileChange()
|
||||
{
|
||||
// run an atmos update to initialize everything For Real surely
|
||||
SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
|
||||
|
||||
var gridNetEnt = SEntMan.GetNetEntity(RelevantAtmos.Owner);
|
||||
TargetCoords = new NetCoordinates(gridNetEnt, Vector2.Zero);
|
||||
var netEnt = await Spawn(_airSensorProto);
|
||||
var airSensorUid = SEntMan.GetEntity(netEnt);
|
||||
Transform.TryGetGridTilePosition(airSensorUid, out var vec);
|
||||
|
||||
// run another one to ensure that the ref to the GasMixture was picked up
|
||||
SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
|
||||
|
||||
// should be in the middle
|
||||
Assert.That(vec,
|
||||
Is.EqualTo(Vector2i.Zero),
|
||||
"Air sensor not in expected position on grid (0, 0)");
|
||||
|
||||
var atmosMonitor = SEntMan.GetComponent<AtmosMonitorComponent>(airSensorUid);
|
||||
var tileMixture = SAtmos.GetTileMixture(airSensorUid);
|
||||
|
||||
Assert.That(tileMixture,
|
||||
Is.SameAs(atmosMonitor.TileGas),
|
||||
"Atmos monitor's TileGas does not match actual tile mixture after spawn.");
|
||||
|
||||
SAtmos.RebuildGridAtmosphere((ProcessEnt.Owner, ProcessEnt.Comp1, ProcessEnt.Comp3));
|
||||
|
||||
// EXTREMELY IMPORTANT: The reference to the tile mixture on the tile should be completely different.
|
||||
var newTileMixture = SAtmos.GetTileMixture(airSensorUid);
|
||||
Assert.That(newTileMixture,
|
||||
Is.Not.SameAs(tileMixture),
|
||||
"Tile mixture is the same instance after fixgridatmos was ran. It should be a new instance.");
|
||||
|
||||
// The monitor's ref to the tile mixture should have updated too.
|
||||
Assert.That(atmosMonitor.TileGas,
|
||||
Is.SameAs(newTileMixture),
|
||||
"Atmos monitor's TileGas does not match actual tile mixture after fixgridatmos was ran.");
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,9 @@ public sealed partial class AtmosphereSystem
|
||||
// Fix Grid Atmos command.
|
||||
_consoleHost.RegisterCommand("fixgridatmos",
|
||||
"Makes every tile on a grid have a roundstart gas mix.",
|
||||
"fixgridatmos <grid Ids>", FixGridAtmosCommand, FixGridAtmosCommandCompletions);
|
||||
"fixgridatmos <grid Ids>",
|
||||
FixGridAtmosCommand,
|
||||
FixGridAtmosCommandCompletions);
|
||||
}
|
||||
|
||||
private void ShutdownCommands()
|
||||
@@ -36,42 +38,6 @@ public sealed partial class AtmosphereSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var mixtures = new GasMixture[9];
|
||||
for (var i = 0; i < mixtures.Length; i++)
|
||||
mixtures[i] = new GasMixture(Atmospherics.CellVolume) { Temperature = Atmospherics.T20C };
|
||||
|
||||
// 0: Air
|
||||
mixtures[0].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
|
||||
mixtures[0].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
|
||||
|
||||
// 1: Vaccum
|
||||
|
||||
// 2: Oxygen (GM)
|
||||
mixtures[2].AdjustMoles(Gas.Oxygen, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 3: Nitrogen (GM)
|
||||
mixtures[3].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 4: Plasma (GM)
|
||||
mixtures[4].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 5: Instant Plasmafire (r)
|
||||
mixtures[5].AdjustMoles(Gas.Oxygen, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].Temperature = 5000f;
|
||||
|
||||
// 6: (Walk-In) Freezer
|
||||
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesFreezer);
|
||||
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesFreezer);
|
||||
mixtures[6].Temperature = Atmospherics.FreezerTemp; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
|
||||
|
||||
// 7: Nitrogen (101kpa) for vox rooms
|
||||
mixtures[7].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellStandard);
|
||||
|
||||
// 8: Air (GM)
|
||||
mixtures[8].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesGasMiner);
|
||||
mixtures[8].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesGasMiner);
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (!NetEntity.TryParse(arg, out var netEntity) || !TryGetEntity(netEntity, out var euid))
|
||||
@@ -92,34 +58,82 @@ public sealed partial class AtmosphereSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
// Force Invalidate & update air on all tiles
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> grid =
|
||||
new(euid.Value, gridAtmosphere, Comp<GasTileOverlayComponent>(euid.Value), gridComp, Transform(euid.Value));
|
||||
|
||||
RebuildGridTiles(grid);
|
||||
|
||||
var query = GetEntityQuery<AtmosFixMarkerComponent>();
|
||||
foreach (var (indices, tile) in gridAtmosphere.Tiles.ToArray())
|
||||
{
|
||||
if (tile.Air is not {Immutable: false} air)
|
||||
continue;
|
||||
|
||||
air.Clear();
|
||||
var mixtureId = 0;
|
||||
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(grid, grid, indices);
|
||||
while (enumerator.MoveNext(out var entUid))
|
||||
{
|
||||
if (query.TryComp(entUid, out var marker))
|
||||
mixtureId = marker.Mode;
|
||||
}
|
||||
|
||||
var mixture = mixtures[mixtureId];
|
||||
Merge(air, mixture);
|
||||
air.Temperature = mixture.Temperature;
|
||||
}
|
||||
RebuildGridAtmosphere((euid.Value, gridAtmosphere, gridComp));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds all <see cref="TileAtmosphere"/>s on a grid to have roundstart gas mixes.
|
||||
/// </summary>
|
||||
/// <remarks>Please be responsible with this method. Used only by tests and fixgridatmos.</remarks>
|
||||
public void RebuildGridAtmosphere(Entity<GridAtmosphereComponent, MapGridComponent> ent)
|
||||
{
|
||||
var mixtures = new GasMixture[9];
|
||||
for (var i = 0; i < mixtures.Length; i++)
|
||||
{
|
||||
mixtures[i] = new GasMixture(Atmospherics.CellVolume) { Temperature = Atmospherics.T20C };
|
||||
}
|
||||
|
||||
// 0: Air
|
||||
mixtures[0].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesStandard);
|
||||
mixtures[0].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesStandard);
|
||||
|
||||
// 1: Vaccum
|
||||
|
||||
// 2: Oxygen (GM)
|
||||
mixtures[2].AdjustMoles(Gas.Oxygen, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 3: Nitrogen (GM)
|
||||
mixtures[3].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 4: Plasma (GM)
|
||||
mixtures[4].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
|
||||
// 5: Instant Plasmafire (r)
|
||||
mixtures[5].AdjustMoles(Gas.Oxygen, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
|
||||
mixtures[5].Temperature = 5000f;
|
||||
|
||||
// 6: (Walk-In) Freezer
|
||||
mixtures[6].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesFreezer);
|
||||
mixtures[6].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesFreezer);
|
||||
mixtures[6].Temperature = Atmospherics.FreezerTemp; // Little colder than an actual freezer but gives a grace period to get e.g. themomachines set up, should keep warm for a few door openings
|
||||
|
||||
// 7: Nitrogen (101kpa) for vox rooms
|
||||
mixtures[7].AdjustMoles(Gas.Nitrogen, Atmospherics.MolesCellStandard);
|
||||
|
||||
// 8: Air (GM)
|
||||
mixtures[8].AdjustMoles(Gas.Oxygen, Atmospherics.OxygenMolesGasMiner);
|
||||
mixtures[8].AdjustMoles(Gas.Nitrogen, Atmospherics.NitrogenMolesGasMiner);
|
||||
|
||||
|
||||
// Force Invalidate & update air on all tiles
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> grid =
|
||||
new(ent.Owner, ent.Comp1, Comp<GasTileOverlayComponent>(ent), ent.Comp2, Transform(ent));
|
||||
|
||||
RebuildGridTiles(grid);
|
||||
|
||||
var query = GetEntityQuery<AtmosFixMarkerComponent>();
|
||||
foreach (var (indices, tile) in ent.Comp1.Tiles.ToArray())
|
||||
{
|
||||
if (tile.Air is not {Immutable: false} air)
|
||||
continue;
|
||||
|
||||
air.Clear();
|
||||
var mixtureId = 0;
|
||||
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(grid, grid, indices);
|
||||
while (enumerator.MoveNext(out var entUid))
|
||||
{
|
||||
if (query.TryComp(entUid, out var marker))
|
||||
mixtureId = marker.Mode;
|
||||
}
|
||||
|
||||
var mixture = mixtures[mixtureId];
|
||||
Merge(air, mixture);
|
||||
air.Temperature = mixture.Temperature;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears & re-creates all references to <see cref="TileAtmosphere"/>s stored on a grid.
|
||||
/// </summary>
|
||||
|
||||
@@ -265,6 +265,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
tile.ArchivedCycle = 0;
|
||||
tile.LastShare = 0f;
|
||||
tile.Hotspot = new Hotspot();
|
||||
NotifyDeviceTileChanged((ent.Owner, ent.Comp1, ent.Comp3), tile.GridIndices);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -275,6 +276,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
if (data.FixVacuum)
|
||||
GridFixTileVacuum(tile);
|
||||
|
||||
// Since we assigned the tile a new GasMixture we need to tell any devices
|
||||
// on this tile that the reference has changed.
|
||||
NotifyDeviceTileChanged((ent.Owner, ent.Comp1, ent.Comp3), tile.GridIndices);
|
||||
}
|
||||
|
||||
private void QueueRunTiles(
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Maps;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Shared.Atmos.Piping.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
@@ -176,4 +174,21 @@ public partial class AtmosphereSystem
|
||||
|
||||
_tile.PryTile(tileRef);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies all subscribing entities on a particular tile that the tile has changed.
|
||||
/// Atmos devices may store references to tiles, so this is used to properly resync devices
|
||||
/// after a significant atmos change on that tile, for example a tile getting a new <see cref="GasMixture"/>.
|
||||
/// </summary>
|
||||
/// <param name="ent">The grid atmosphere entity.</param>
|
||||
/// <param name="tile">The tile to check for devices on.</param>
|
||||
private void NotifyDeviceTileChanged(Entity<GridAtmosphereComponent, MapGridComponent> ent, Vector2i tile)
|
||||
{
|
||||
var inTile = _mapSystem.GetAnchoredEntities(ent.Owner, ent.Comp2, tile);
|
||||
var ev = new AtmosDeviceTileChangedEvent();
|
||||
foreach (var uid in inTile)
|
||||
{
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
InitializeGridAtmosphere();
|
||||
InitializeMap();
|
||||
|
||||
_mapAtmosQuery = GetEntityQuery<MapAtmosphereComponent>();
|
||||
_atmosQuery = GetEntityQuery<GridAtmosphereComponent>();
|
||||
_mapAtmosQuery = GetEntityQuery<MapAtmosphereComponent>();
|
||||
_airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
_firelockQuery = GetEntityQuery<FirelockComponent>();
|
||||
|
||||
|
||||
@@ -57,6 +57,13 @@ public sealed class AtmosMonitorSystem : EntitySystem
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceDisabledEvent>(OnAtmosDeviceLeaveAtmosphere);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceEnabledEvent>(OnAtmosDeviceEnterAtmosphere);
|
||||
SubscribeLocalEvent<AtmosMonitorComponent, AtmosDeviceTileChangedEvent>(OnAtmosDeviceTileChangedEvent);
|
||||
}
|
||||
|
||||
private void OnAtmosDeviceTileChangedEvent(Entity<AtmosMonitorComponent> ent, ref AtmosDeviceTileChangedEvent args)
|
||||
{
|
||||
if (!ent.Comp.MonitorsPipeNet)
|
||||
ent.Comp.TileGas = _atmosphereSystem.GetContainingMixture(ent.Owner, true);
|
||||
}
|
||||
|
||||
private void OnAtmosDeviceLeaveAtmosphere(EntityUid uid, AtmosMonitorComponent atmosMonitor, ref AtmosDeviceDisabledEvent args)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Shared.Atmos.Piping.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed on entities when the tile that they reside in has had their
|
||||
/// associated TileAtmosphere changed significantly, i.e. a tile/<see cref="GasMixture"/> being added, removed,
|
||||
/// or replaced. Important when atmos devices need to update any stored references to their tile's atmosphere.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct AtmosDeviceTileChangedEvent;
|
||||
Reference in New Issue
Block a user