mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-15 03:50:54 +01:00
AirtightSystem Tests (#42190)
This commit is contained in:
585
Content.IntegrationTests/Tests/Atmos/AirtightTest.cs
Normal file
585
Content.IntegrationTests/Tests/Atmos/AirtightTest.cs
Normal file
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user