]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
AirtightSystem Tests (#42190)
authorArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Tue, 13 Jan 2026 23:06:59 +0000 (15:06 -0800)
committerGitHub <noreply@github.com>
Tue, 13 Jan 2026 23:06:59 +0000 (15:06 -0800)
Content.IntegrationTests/Tests/Atmos/AirtightTest.cs [new file with mode: 0644]
Content.Server/Atmos/Components/AirtightComponent.cs
Content.Server/Atmos/EntitySystems/AirtightSystem.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs

diff --git a/Content.IntegrationTests/Tests/Atmos/AirtightTest.cs b/Content.IntegrationTests/Tests/Atmos/AirtightTest.cs
new file mode 100644 (file)
index 0000000..63e4d9f
--- /dev/null
@@ -0,0 +1,585 @@
+using System.Numerics;
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Maths;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.IntegrationTests.Tests.Atmos;
+
+/// <summary>
+/// Mega-testclass for testing <see cref="AirtightSystem"/> and <see cref="AirtightComponent"/>.
+/// </summary>
+[TestOf(typeof(AirtightSystem))]
+[TestOf(typeof(AtmosphereSystem))]
+public sealed class AirtightTest : AtmosTest
+{
+    // Load the same DeltaPressure test because it's quite a useful testmap for testing airtightness.
+    protected override ResPath? TestMapPath => new("Maps/Test/Atmospherics/DeltaPressure/deltapressuretest.yml");
+
+    private readonly EntProtoId _wallProto = new("WallSolid");
+
+    private EntityUid _targetWall = EntityUid.Invalid;
+    private EntityUid _targetRotationEnt = EntityUid.Invalid;
+
+    #region Prototypes
+
+    [TestPrototypes]
+    private const string Prototypes = @"
+- type: entity
+  id: AirtightDirectionalRotationTest
+  parent: WindowDirectional
+  components:
+  - type: Airtight
+    airBlockedDirection: North
+    fixAirBlockedDirectionInitialize: true
+    noAirWhenFullyAirBlocked: false
+";
+
+    #endregion
+
+    #region Component and Helper Assertions
+
+    /*
+     Tests for asserting that proper ComponentInit and other events properly work.
+     */
+
+    [Test]
+    public async Task Component_InitDataCorrect()
+    {
+        // Ensure grid/atmos is initialized.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        await Server.WaitPost(delegate
+        {
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, Vector2.Zero);
+            _targetWall = SEntMan.SpawnAtPosition(_wallProto, coords);
+        });
+
+        SEntMan.TryGetComponent<AirtightComponent>(_targetWall, out var airtightComp);
+        Assert.That(airtightComp, Is.Not.Null, "Expected spawned wall entity to have AirtightComponent.");
+
+        // The data on the component itself should reflect full blockage.
+        // It should also hold the proper last position.
+        using (Assert.EnterMultipleScope())
+        {
+            Assert.That(airtightComp.AirBlockedDirection, Is.EqualTo(AtmosDirection.All));
+            Assert.That(airtightComp.LastPosition, Is.EqualTo((RelevantAtmos.Owner, Vector2i.Zero)));
+        }
+    }
+
+    [Test]
+    [TestCase(AtmosDirection.North)]
+    [TestCase(AtmosDirection.South)]
+    [TestCase(AtmosDirection.East)]
+    [TestCase(AtmosDirection.West)]
+    public async Task MultiTile_Component_InitDataCorrect(AtmosDirection direction)
+    {
+        // Ensure grid/atmos is initialized.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        var offsetVec = Vector2i.Zero.Offset(direction);
+        await Server.WaitPost(delegate
+        {
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, offsetVec);
+            _targetWall = SEntMan.SpawnAtPosition(_wallProto, coords);
+        });
+
+        SEntMan.TryGetComponent<AirtightComponent>(_targetWall, out var airtightComp);
+        Assert.That(airtightComp, Is.Not.Null, "Expected spawned wall entity to have AirtightComponent.");
+
+        // The data on the component itself should reflect full blockage.
+        // It should also hold the proper last position.
+        using (Assert.EnterMultipleScope())
+        {
+            Assert.That(airtightComp.AirBlockedDirection, Is.EqualTo(AtmosDirection.All));
+            Assert.That(airtightComp.LastPosition, Is.EqualTo((RelevantAtmos.Owner, offsetVec)));
+        }
+    }
+
+    #endregion
+
+    #region Single Tile Assertion
+
+    /*
+     Tests for asserting single tile airtightness state on both reconstructed and cached data.
+     These tests just spawn a wall in the center and make sure that both reconstructed and cached
+     airtight data reflect the expected states both immediately after the action and after an atmos tick.
+     */
+
+    /// <summary>
+    /// Tests that the reconstructed airtight map reflects properly when an airtight entity is spawned.
+    /// </summary>
+    [Test]
+    public async Task Spawn_ReconstructedUpdatesImmediately()
+    {
+        // Ensure grid/atmos is initialized.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        // Before an entity is spawned, the tile in question should be completely unblocked.
+        // This should be reflected in a reconstruction.
+        using (Assert.EnterMultipleScope())
+        {
+            Assert.That(
+                SAtmos.IsTileAirBlocked(ProcessEnt.Owner, Vector2i.Zero, mapGridComp: ProcessEnt.Comp3),
+                Is.False,
+                "Expected no airtightness for reconstructed AirtightData before spawning an airtight entity.");
+        }
+
+        // We cannot use the Spawn InteractionTest helper because it runs ticks,
+        // which invalidate testing for cached data (ticks would update the cache).
+        await Server.WaitPost(delegate
+        {
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, Vector2.Zero);
+            _targetWall = SEntMan.SpawnAtPosition(_wallProto, coords);
+        });
+
+        // Now, immediately after spawn, the reconstructed data should reflect airtightness.
+        Assert.That(
+            SAtmos.IsTileAirBlocked(ProcessEnt.Owner, Vector2i.Zero, mapGridComp: ProcessEnt.Comp3),
+            Is.True,
+            "Expected airtightness for reconstructed AirtightData immediately after spawn.");
+    }
+
+    /// <summary>
+    /// Tests that the AirtightData cache updates properly when an airtight entity is spawned.
+    /// </summary>
+    [Test]
+    public async Task Spawn_CacheUpdatesOnAtmosTick()
+    {
+        // Ensure grid/atmos is initialized.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        // Space should be blank before spawn.
+        using (Assert.EnterMultipleScope())
+        {
+            Assert.That(
+                SAtmos.IsTileAirBlockedCached(RelevantAtmos, Vector2i.Zero),
+                Is.False,
+                "Expected cached AirtightData to be unblocked before spawning an airtight entity.");
+
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.All),
+                "Expected tile to be completely unblocked before spawning an airtight entity.");
+
+            Assert.That(tile.AirtightData.BlockedDirections,
+                Is.EqualTo(AtmosDirection.Invalid),
+                "Expected AirtightData to reflect non-airtight state before spawning an airtight entity.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                Assert.That(curTile, Is.Not.Null, $"Center tile does not hold expected reference to adjacent tile in direction {direction}.");
+            }
+        }
+
+        await Server.WaitPost(delegate
+        {
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, Vector2.Zero);
+            _targetWall = SEntMan.SpawnAtPosition(_wallProto, coords);
+        });
+
+        // Now, immediately after spawn, the reconstructed data should reflect airtightness,
+        // but the cached data should still be stale.
+        // This goes the same for the references, which haven't been updated, as well as the AirtightData.
+        using (Assert.EnterMultipleScope())
+        {
+            Assert.That(
+                SAtmos.IsTileAirBlockedCached(RelevantAtmos, Vector2i.Zero),
+                Is.False,
+                "Expected cached AirtightData to remain stale immediately after spawn before atmos tick.");
+
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.All),
+                "Expected tile to still show non-airtight state before an atmos tick.");
+
+            Assert.That(tile.AirtightData.BlockedDirections,
+                Is.EqualTo(AtmosDirection.Invalid),
+                "Expected AirtightData to reflect non-airtight state after spawn before an atmos tick.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                Assert.That(curTile, Is.Not.Null, $"Center tile does not hold expected reference to adjacent tile in direction {direction}.");
+            }
+        }
+
+        // Tick to update cache.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        using (Assert.EnterMultipleScope())
+        {
+            Assert.That(
+                SAtmos.IsTileAirBlocked(ProcessEnt.Owner, Vector2i.Zero, mapGridComp: ProcessEnt.Comp3),
+                Is.True,
+                "Expected airtightness for reconstructed AirtightData after atmos tick.");
+
+            Assert.That(
+                SAtmos.IsTileAirBlockedCached(RelevantAtmos, Vector2i.Zero),
+                Is.True,
+                "Expected cached AirtightData to reflect airtightness after atmos tick.");
+
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.Invalid),
+                "Expected tile to reflect airtight state after atmos tick.");
+
+            Assert.That(tile.AirtightData.BlockedDirections,
+                Is.EqualTo(AtmosDirection.All),
+                "Expected AirtightData to reflect airtight state after spawn before an atmos tick.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                Assert.That(curTile, Is.Null, $"Center tile holds unexpected reference to adjacent tile in direction {direction}.");
+            }
+        }
+    }
+
+    /// <summary>
+    /// Tests that an airtight reconstruction reflects properly after an entity is deleted.
+    /// </summary>
+    [Test]
+    public async Task Delete_ReconstructedUpdatesImmediately()
+    {
+        // Ensure grid/atmos is initialized.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        await Server.WaitPost(delegate
+        {
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, Vector2.Zero);
+            _targetWall = SEntMan.SpawnAtPosition(_wallProto, coords);
+        });
+
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        Assert.That(
+            SAtmos.IsTileAirBlocked(ProcessEnt.Owner, Vector2i.Zero, mapGridComp: ProcessEnt.Comp3),
+            Is.True,
+            "Expected airtightness for reconstructed AirtightData before deletion.");
+
+        await Server.WaitPost(delegate
+        {
+            SEntMan.DeleteEntity(_targetWall);
+        });
+
+        Assert.That(
+            SAtmos.IsTileAirBlocked(ProcessEnt.Owner, Vector2i.Zero, mapGridComp: ProcessEnt.Comp3),
+            Is.False,
+            "Expected no airtightness for reconstructed AirtightData immediately after deletion.");
+
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        Assert.That(
+            SAtmos.IsTileAirBlocked(ProcessEnt.Owner, Vector2i.Zero, mapGridComp: ProcessEnt.Comp3),
+            Is.False,
+            "Expected no airtightness for reconstructed AirtightData after atmos tick.");
+    }
+
+    /// <summary>
+    /// Tests that the cached airtight map reflects properly when an entity is deleted
+    /// </summary>
+    [Test]
+    public async Task Delete_CacheUpdatesOnAtmosTick()
+    {
+        // Ensure grid/atmos is initialized.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        await Server.WaitPost(delegate
+        {
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, Vector2.Zero);
+            _targetWall = SEntMan.SpawnAtPosition(_wallProto, coords);
+        });
+
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        await Server.WaitPost(delegate
+        {
+            SEntMan.DeleteEntity(_targetWall);
+        });
+
+        using (Assert.EnterMultipleScope())
+        {
+            Assert.That(
+                SAtmos.IsTileAirBlockedCached(RelevantAtmos, Vector2i.Zero),
+                Is.True,
+                "Expected cached AirtightData to remain stale immediately after deletion before atmos tick.");
+
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.Invalid),
+                "Expected tile to still show airtight state before atmos tick after deletion.");
+
+            Assert.That(tile.AirtightData.BlockedDirections,
+                Is.EqualTo(AtmosDirection.All),
+                "Expected AirtightData to reflect non-airtight state before after deletion before an atmos tick.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                Assert.That(curTile, Is.Null, $"Center tile holds unexpected reference to adjacent tile in direction {direction}.");
+            }
+        }
+
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        using (Assert.EnterMultipleScope())
+        {
+            Assert.That(
+                SAtmos.IsTileAirBlockedCached(RelevantAtmos, Vector2i.Zero),
+                Is.False,
+                "Expected cached AirtightData to reflect deletion after atmos tick.");
+
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.All),
+                "Expected tile to reflect non-airtight state after atmos tick.");
+
+            Assert.That(tile.AirtightData.BlockedDirections,
+                Is.EqualTo(AtmosDirection.Invalid),
+                "Expected AirtightData to reflect non-airtight state after atmos tick.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                Assert.That(curTile, Is.Not.Null, $"Center tile does not hold expected reference to adjacent tile in direction {direction}.");
+            }
+        }
+    }
+
+    #endregion
+
+    #region Multi-Tile Assertion
+
+    /*
+     Tests for asserting multi-tile airtightness state on cached data.
+     These tests spawn multiple entities and check that the center unblocked entity
+     properly reflects partial airtightness states.
+
+     Note that reconstruction won't save you in the case where you're surrounded by airtight entities,
+     as those don't show up in the reconstruction. Thus, only cached data tests are done here.
+     */
+
+    /// <summary>
+    /// Tests that the cached airtight map reflects properly when airtight entities are spawned
+    /// along the cardinal directions.
+    /// </summary>
+    /// <param name="atmosDirection">The direction to spawn the airtight entity in.</param>
+    [Test]
+    [TestCase(AtmosDirection.North)]
+    [TestCase(AtmosDirection.South)]
+    [TestCase(AtmosDirection.East)]
+    [TestCase(AtmosDirection.West)]
+    public async Task MultiTile_Spawn_CacheUpdatesOnAtmosTick(AtmosDirection atmosDirection)
+    {
+        // Ensure grid/atmos is initialized.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        // Tile should be completely unblocked.
+        using (Assert.EnterMultipleScope())
+        {
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.All),
+                "Expected tile to be completely unblocked before spawning an airtight entity.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                Assert.That(curTile, Is.Not.Null, $"Center tile does not hold expected reference to adjacent tile in direction {direction}.");
+            }
+        }
+
+        await Server.WaitPost(delegate
+        {
+            var offsetVec = Vector2i.Zero.Offset(atmosDirection);
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, offsetVec);
+            _targetWall = SEntMan.SpawnAtPosition(_wallProto, coords);
+        });
+
+        using (Assert.EnterMultipleScope())
+        {
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.All),
+                "Expected tile to still show non-airtight state before an atmos tick.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                Assert.That(curTile, Is.Not.Null, $"Center tile does not hold expected reference to adjacent tile in direction {direction}.");
+            }
+        }
+
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        using (Assert.EnterMultipleScope())
+        {
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.All & ~atmosDirection),
+                "Expected tile to reflect airtight state after atmos tick.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                if (direction == atmosDirection)
+                {
+                    Assert.That(curTile, Is.Null, $"Center tile holds unexpected reference to adjacent tile in direction {direction}.");
+                }
+                else
+                {
+                    Assert.That(curTile, Is.Not.Null, $"Center tile does not hold expected reference to adjacent tile in direction {direction}.");
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// Tests that the cached airtight map reflects properly when an airtight entity is deleted
+    /// along a cardinal direction.
+    /// </summary>
+    /// <param name="atmosDirection">The direction the airtight entity is spawned and then deleted in.</param>
+    [Test]
+    [TestCase(AtmosDirection.North)]
+    [TestCase(AtmosDirection.South)]
+    [TestCase(AtmosDirection.East)]
+    [TestCase(AtmosDirection.West)]
+    public async Task MultiTile_Delete_CacheUpdatesOnAtmosTick(AtmosDirection atmosDirection)
+    {
+        // Ensure grid/atmos is initialized.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        await Server.WaitPost(delegate
+        {
+            var offsetVec = Vector2i.Zero.Offset(atmosDirection);
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, offsetVec);
+            _targetWall = SEntMan.SpawnAtPosition(_wallProto, coords);
+        });
+
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        await Server.WaitPost(delegate
+        {
+            SEntMan.DeleteEntity(_targetWall);
+        });
+
+        using (Assert.EnterMultipleScope())
+        {
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.All & ~atmosDirection),
+                "Expected tile to remain stale immediately after deletion before an atmos tick.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                if (direction == atmosDirection)
+                {
+                    Assert.That(curTile, Is.Null, $"Center tile holds unexpected reference to adjacent tile in direction {direction}.");
+                }
+                else
+                {
+                    Assert.That(curTile, Is.Not.Null, $"Center tile does not hold expected reference to adjacent tile in direction {direction}.");
+                }
+            }
+        }
+
+        // Tick to update cache after deletion.
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        using (Assert.EnterMultipleScope())
+        {
+            var tile = RelevantAtmos.Comp.Tiles[Vector2i.Zero];
+            Assert.That(tile.AdjacentBits,
+                Is.EqualTo(AtmosDirection.All),
+                "Expected tile to reflect non-airtight state after deletion after atmos tick.");
+
+            for (var i = 0; i < Atmospherics.Directions; i++)
+            {
+                var direction = (AtmosDirection)(1 << i);
+                var curTile = tile.AdjacentTiles[i];
+                Assert.That(curTile, Is.Not.Null, $"Center tile does not hold expected reference to adjacent tile in direction {direction}.");
+            }
+        }
+    }
+
+    #endregion
+
+    #region Rotation Assertion
+
+    /// <summary>
+    /// Asserts that an airtight entity with a directional air blocked direction
+    /// properly reflects rotation on spawn.
+    /// </summary>
+    /// <param name="degrees">The degrees to rotate the entity on spawn.</param>
+    /// <param name="expected">The expected blocked direction after rotation.</param>
+    /// <remarks>Yeah, so here I learned that RT handles rotation directions
+    /// as positive == counterclockwise.</remarks>
+    [Test]
+    [TestCase(0f, AtmosDirection.North)]
+    [TestCase(90f, AtmosDirection.West)]
+    [TestCase(180f, AtmosDirection.South)]
+    [TestCase(270f, AtmosDirection.East)]
+    [TestCase(-90f, AtmosDirection.East)]
+    [TestCase(-180f, AtmosDirection.South)]
+    [TestCase(-270f, AtmosDirection.West)]
+    public async Task Rotation_AirBlockedDirectionsOnSpawn(float degrees, AtmosDirection expected)
+    {
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        var rotation = Angle.FromDegrees(degrees);
+
+        await Server.WaitPost(delegate
+        {
+            var coords = new EntityCoordinates(RelevantAtmos.Owner, Vector2.Zero);
+            _targetRotationEnt = SEntMan.SpawnAtPosition("AirtightDirectionalRotationTest", coords);
+
+            Transform.SetLocalRotation(_targetRotationEnt, rotation);
+        });
+
+        SAtmos.RunProcessingFull(ProcessEnt, MapData.Grid.Owner, SAtmos.AtmosTickRate);
+
+        await Server.WaitAssertion(delegate
+        {
+            using (Assert.EnterMultipleScope())
+            {
+                SEntMan.TryGetComponent<AirtightComponent>(_targetRotationEnt, out var airtight);
+                Assert.That(airtight, Is.Not.Null);
+
+                var initial = (AtmosDirection)airtight.InitialAirBlockedDirection;
+                Assert.That(initial,
+                    Is.EqualTo(AtmosDirection.North),
+                    "Directional airtight entity should block North on spawn.");
+
+                Assert.That(airtight.AirBlockedDirection,
+                    Is.EqualTo(expected),
+                    $"Expected AirBlockedDirection to be {expected} after rotating by {degrees} degrees on spawn.");
+
+                // i dont trust you airtightsystem
+                if (degrees is 90f or 270f)
+                {
+                    Assert.That(expected,
+                        Is.Not.EqualTo(initial),
+                        "Rotated directions should differ for 90/270 degrees.");
+                }
+            }
+        });
+    }
+
+    #endregion
+}
index 69488a71307ee7a37558cfdd1159f76c01fddecc..29c1f18af3a9040909898e08f1e1f57fbca5a9b0 100644 (file)
@@ -40,14 +40,23 @@ namespace Content.Server.Atmos.Components
         // depressurizing a room. However it can also effectively be used as a means of generating gasses for free
         // TODO ATMOS Mass conservation. Make it actually push/pull air from adjacent tiles instead of destroying & creating,
 
-
-        // TODO ATMOS Do we need these two fields?
+        // TODO ATMOS slate for removal. Stuff doesn't use this.
         [DataField("rotateAirBlocked")]
         public bool RotateAirBlocked { get; set; } = true;
 
-        // TODO ATMOS remove this? What is this even for??
-        [DataField("fixAirBlockedDirectionInitialize")]
-        public bool FixAirBlockedDirectionInitialize { get; set; } = true;
+        /// <summary>
+        /// Whether to fix the <see cref="CurrentAirBlockedDirection"/> on initialization
+        /// to the entity's current rotation.
+        /// </summary>
+        /// <remarks>This is an optimization routine for initializing airtight components.
+        /// If this entity doesn't have unique airtight directions
+        /// (ex. not all directions are blocked but some are), we can skip
+        /// a lot of event/transform business during initialization.
+        /// This field marks whether this is skipped or not.</remarks>
+        /// <example>If your entity only blocks air in one direction,
+        /// and that can depend on rotation, this needs to be set to true.</example>
+        [DataField]
+        public bool FixAirBlockedDirectionInitialize = true;
 
         /// <summary>
         /// If true, then the tile that this entity is on will have no air at all if all directions are blocked.
index 431707c83503831933d37d38a833e5d0e52c02f0..78f61c80a477a869777df9bf6df7248758b58007 100644 (file)
@@ -25,7 +25,10 @@ namespace Content.Server.Atmos.EntitySystems
 
         private void OnAirtightInit(Entity<AirtightComponent> airtight, ref ComponentInit args)
         {
-            // TODO AIRTIGHT what FixAirBlockedDirectionInitialize even for?
+            // If this entity has unique airtight directions that are affected by rotation,
+            // we need to fix up the current airtight directions based on its rotation.
+            // Otherwise, we can skip all of that logic (stuff adds up when you're initing
+            // a morbillion walls).
             if (!airtight.Comp.FixAirBlockedDirectionInitialize)
             {
                 UpdatePosition(airtight);
index 5cce6075b705e93f10aa15f7f659e12758a024cf..e2e39abdda9ac3a8f96cbe88c6cee42646a630d4 100644 (file)
@@ -300,6 +300,8 @@ public partial class AtmosphereSystem
 
     /// <summary>
     /// Checks if a tile on a grid is air-blocked in the specified directions.
+    /// This only checks for if the current tile, and only the current tile, is blocking
+    /// air.
     /// </summary>
     /// <param name="gridUid">The grid to check.</param>
     /// <param name="tile">The tile on the grid to check.</param>
@@ -323,6 +325,8 @@ public partial class AtmosphereSystem
 
     /// <summary>
     /// Checks if a tile on a grid is air-blocked in the specified directions, using cached data.
+    /// This only checks for if the current tile, and only the current tile, is blocking
+    /// air.
     /// </summary>
     /// <param name="grid">The grid to check.</param>
     /// <param name="tile">The tile on the grid to check.</param>