Files
tbd-station-14/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs
2022-06-12 11:54:41 +10:00

308 lines
11 KiB
C#

using Content.Shared.Atmos;
using Robust.Shared.Map;
using static Content.Server.Explosion.EntitySystems.ExplosionSystem;
namespace Content.Server.Explosion.EntitySystems;
/// <summary>
/// See <see cref="ExplosionTileFlood"/>. Each instance of this class corresponds to a seperate grid.
/// </summary>
public sealed class ExplosionGridTileFlood : ExplosionTileFlood
{
public IMapGrid Grid;
private bool _needToTransform = false;
private Matrix3 _matrix = Matrix3.Identity;
private Vector2 _offset;
// Tiles which neighbor an exploding tile, but have not yet had the explosion spread to them due to an
// airtight entity on the exploding tile that prevents the explosion from spreading in that direction. These
// will be added as a neighbor after some delay, once the explosion on that tile is sufficiently strong to
// destroy the airtight entity.
private Dictionary<int, List<(Vector2i, AtmosDirection)>> _delayedNeighbors = new();
private Dictionary<Vector2i, TileData> _airtightMap;
private float _maxIntensity;
private float _intensityStepSize;
private int _typeIndex;
private UniqueVector2iSet _spaceTiles = new();
private UniqueVector2iSet _processedSpaceTiles = new();
public HashSet<Vector2i> SpaceJump = new();
private Dictionary<Vector2i, NeighborFlag> _edgeTiles;
public ExplosionGridTileFlood(
IMapGrid grid,
Dictionary<Vector2i, TileData> airtightMap,
float maxIntensity,
float intensityStepSize,
int typeIndex,
Dictionary<Vector2i, NeighborFlag> edgeTiles,
EntityUid? referenceGrid,
Matrix3 spaceMatrix,
Angle spaceAngle)
{
Grid = grid;
_airtightMap = airtightMap;
_maxIntensity = maxIntensity;
_intensityStepSize = intensityStepSize;
_typeIndex = typeIndex;
_edgeTiles = edgeTiles;
// initialise SpaceTiles
foreach (var (tile, spaceNeighbors) in _edgeTiles)
{
for (var i = 0; i < NeighbourVectors.Length; i++)
{
var dir = (NeighborFlag) (1 << i);
if ((spaceNeighbors & dir) != NeighborFlag.Invalid)
_spaceTiles.Add(tile + NeighbourVectors[i]);
}
}
if (referenceGrid == Grid.GridEntityId)
return;
_needToTransform = true;
var transform = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(Grid.GridEntityId);
var size = (float) Grid.TileSize;
_matrix.R0C2 = size / 2;
_matrix.R1C2 = size / 2;
_matrix *= transform.WorldMatrix * Matrix3.Invert(spaceMatrix);
var relativeAngle = transform.WorldRotation - spaceAngle;
_offset = relativeAngle.RotateVec((size / 4, size / 4));
}
public override void InitTile(Vector2i initialTile)
{
TileLists[0] = new() { initialTile };
if (_airtightMap.ContainsKey(initialTile))
EnteredBlockedTiles.Add(initialTile);
else
ProcessedTiles.Add(initialTile);
}
public int AddNewTiles(int iteration, HashSet<Vector2i>? gridJump)
{
SpaceJump = new();
NewTiles = new();
NewBlockedTiles = new();
// Mark tiles as entered if any were just freed due to airtight/explosion blockers being destroyed.
if (FreedTileLists.TryGetValue(iteration, out var freed))
{
HashSet<Vector2i> toRemove = new();
foreach (var tile in freed)
{
if (!EnteredBlockedTiles.Add(tile))
toRemove.Add(tile);
}
freed.ExceptWith(toRemove);
NewFreedTiles = freed;
}
else
{
NewFreedTiles = new();
FreedTileLists[iteration] = NewFreedTiles;
}
// Add adjacent tiles
if (TileLists.TryGetValue(iteration - 2, out var adjacent))
AddNewAdjacentTiles(iteration, adjacent, false);
if (FreedTileLists.TryGetValue(iteration - 2, out var delayedAdjacent))
AddNewAdjacentTiles(iteration, delayedAdjacent, true);
// Add diagonal tiles
if (TileLists.TryGetValue(iteration - 3, out var diagonal))
AddNewDiagonalTiles(iteration, diagonal, false);
if (FreedTileLists.TryGetValue(iteration - 3, out var delayedDiagonal))
AddNewDiagonalTiles(iteration, delayedDiagonal, true);
// Add delayed tiles
AddDelayedNeighbors(iteration);
// Tiles from Spaaaace
if (gridJump != null)
{
foreach (var tile in gridJump)
{
ProcessNewTile(iteration, tile, AtmosDirection.Invalid);
}
}
// Store new tiles
if (NewTiles.Count != 0)
TileLists[iteration] = NewTiles;
if (NewBlockedTiles.Count != 0)
BlockedTileLists[iteration] = NewBlockedTiles;
return NewTiles.Count + NewBlockedTiles.Count;
}
protected override void ProcessNewTile(int iteration, Vector2i tile, AtmosDirection entryDirections)
{
// Is there an airtight blocker on this tile?
if (!_airtightMap.TryGetValue(tile, out var tileData))
{
// No blocker. Ezy. Though maybe this a space tile?
if (_spaceTiles.Contains(tile))
JumpToSpace(tile);
else if (ProcessedTiles.Add(tile))
NewTiles.Add(tile);
return;
}
// If the explosion is entering this new tile from an unblocked direction, we add it directly. Note that because
// for space -> grid jumps, we don't have a direction from which the explosion came, we will only assume it is
// unblocked if all space-facing directions are unblocked. Though this could eventually be done properly.
bool blocked;
var blockedDirections = tileData.BlockedDirections;
if (entryDirections == AtmosDirection.Invalid) // is coming from space?
{
blocked = AnyNeighborBlocked(_edgeTiles[tile], blockedDirections); // at least one space direction is blocked.
}
else
blocked = (blockedDirections & entryDirections) == entryDirections;// **ALL** entry directions are blocked
if (blocked)
{
// was this tile already entered from some other direction?
if (EnteredBlockedTiles.Contains(tile))
return;
// Did the explosion already attempt to enter this tile from some other direction?
if (!UnenteredBlockedTiles.Add(tile))
return;
NewBlockedTiles.Add(tile);
// At what explosion iteration would this blocker be destroyed?
var clearIteration = iteration + (int) MathF.Ceiling(tileData.ExplosionTolerance[_typeIndex] / _intensityStepSize);
if (FreedTileLists.TryGetValue(clearIteration, out var list))
list.Add(tile);
else
FreedTileLists[clearIteration] = new() { tile };
return;
}
// was this tile already entered from some other direction?
if (!EnteredBlockedTiles.Add(tile))
return;
// Did the explosion already attempt to enter this tile from some other direction?
if (UnenteredBlockedTiles.Contains(tile))
{
NewFreedTiles.Add(tile);
return;
}
// This is a completely new tile, and we just so happened to enter it from an unblocked direction.
NewTiles.Add(tile);
}
private void JumpToSpace(Vector2i tile)
{
// Did we already jump/process this tile?
if (!_processedSpaceTiles.Add(tile))
return;
if (!_needToTransform)
{
SpaceJump.Add(tile);
return;
}
var center = _matrix.Transform(tile);
SpaceJump.Add(new((int) MathF.Floor(center.X + _offset.X), (int) MathF.Floor(center.Y + _offset.Y)));
SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.Y), (int) MathF.Floor(center.Y + _offset.X)));
SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.X), (int) MathF.Floor(center.Y - _offset.Y)));
SpaceJump.Add(new((int) MathF.Floor(center.X + _offset.Y), (int) MathF.Floor(center.Y - _offset.X)));
}
private void AddDelayedNeighbors(int iteration)
{
if (!_delayedNeighbors.TryGetValue(iteration, out var delayed))
return;
foreach (var (tile, direction) in delayed)
{
ProcessNewTile(iteration, tile, direction);
}
_delayedNeighbors.Remove(iteration);
}
// Gets the tiles that are directly adjacent to other tiles. If a currently exploding tile has an airtight entity
// that blocks the explosion from propagating in some direction, those tiles are added to a list of delayed tiles
// that will be added to the explosion in some future iteration.
private void AddNewAdjacentTiles(int iteration, IEnumerable<Vector2i> tiles, bool ignoreTileBlockers = false)
{
foreach (var tile in tiles)
{
var blockedDirections = AtmosDirection.Invalid;
float sealIntegrity = 0;
// Note that if (grid, tile) is not a valid key, then airtight.BlockedDirections will default to 0 (no blocked directions)
if (_airtightMap.TryGetValue(tile, out var tileData))
{
blockedDirections = tileData.BlockedDirections;
sealIntegrity = tileData.ExplosionTolerance[_typeIndex];
}
// First, yield any neighboring tiles that are not blocked by airtight entities on this tile
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (ignoreTileBlockers || !blockedDirections.IsFlagSet(direction))
{
ProcessNewTile(iteration, tile.Offset(direction), direction.GetOpposite());
}
}
// If there are no blocked directions, we are done with this tile.
if (ignoreTileBlockers || blockedDirections == AtmosDirection.Invalid)
continue;
// This tile has one or more airtight entities anchored to it blocking the explosion from traveling in
// some directions. First, check whether this blocker can even be destroyed by this explosion?
if (sealIntegrity > _maxIntensity || float.IsNaN(sealIntegrity))
continue;
// At what explosion iteration would this blocker be destroyed?
var clearIteration = iteration + (int) MathF.Ceiling(sealIntegrity / _intensityStepSize);
// Get the delayed neighbours list
if (!_delayedNeighbors.TryGetValue(clearIteration, out var list))
{
list = new();
_delayedNeighbors[clearIteration] = list;
}
// Check which directions are blocked, and add them to the list.
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (blockedDirections.IsFlagSet(direction))
{
list.Add((tile.Offset(direction), direction.GetOpposite()));
}
}
}
}
protected override AtmosDirection GetUnblockedDirectionOrAll(Vector2i tile)
{
return ~_airtightMap.GetValueOrDefault(tile).BlockedDirections;
}
}