312 lines
12 KiB
C#
312 lines
12 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 required = tileData.ExplosionTolerance[_typeIndex];
|
|
if (required > _maxIntensity)
|
|
return; // blocker is never destroyed.
|
|
|
|
var clearIteration = iteration + (int) MathF.Ceiling(required / _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)
|
|
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;
|
|
}
|
|
}
|