1
0

ExplosionGridTileFlood.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. using System.Numerics;
  2. using Content.Shared.Atmos;
  3. using Robust.Shared.Map;
  4. using Robust.Shared.Map.Components;
  5. using static Content.Server.Explosion.EntitySystems.ExplosionSystem;
  6. namespace Content.Server.Explosion.EntitySystems;
  7. /// <summary>
  8. /// See <see cref="ExplosionTileFlood"/>. Each instance of this class corresponds to a seperate grid.
  9. /// </summary>
  10. public sealed class ExplosionGridTileFlood : ExplosionTileFlood
  11. {
  12. public MapGridComponent Grid;
  13. private bool _needToTransform = false;
  14. private Matrix3x2 _matrix = Matrix3x2.Identity;
  15. private Vector2 _offset;
  16. // Tiles which neighbor an exploding tile, but have not yet had the explosion spread to them due to an
  17. // airtight entity on the exploding tile that prevents the explosion from spreading in that direction. These
  18. // will be added as a neighbor after some delay, once the explosion on that tile is sufficiently strong to
  19. // destroy the airtight entity.
  20. private Dictionary<int, List<(Vector2i, AtmosDirection)>> _delayedNeighbors = new();
  21. private Dictionary<Vector2i, TileData> _airtightMap;
  22. private float _maxIntensity;
  23. private float _intensityStepSize;
  24. private int _typeIndex;
  25. private UniqueVector2iSet _spaceTiles = new();
  26. private UniqueVector2iSet _processedSpaceTiles = new();
  27. public HashSet<Vector2i> SpaceJump = new();
  28. private Dictionary<Vector2i, NeighborFlag> _edgeTiles;
  29. public ExplosionGridTileFlood(
  30. MapGridComponent grid,
  31. Dictionary<Vector2i, TileData> airtightMap,
  32. float maxIntensity,
  33. float intensityStepSize,
  34. int typeIndex,
  35. Dictionary<Vector2i, NeighborFlag> edgeTiles,
  36. EntityUid? referenceGrid,
  37. Matrix3x2 spaceMatrix,
  38. Angle spaceAngle)
  39. {
  40. Grid = grid;
  41. _airtightMap = airtightMap;
  42. _maxIntensity = maxIntensity;
  43. _intensityStepSize = intensityStepSize;
  44. _typeIndex = typeIndex;
  45. _edgeTiles = edgeTiles;
  46. // initialise SpaceTiles
  47. foreach (var (tile, spaceNeighbors) in _edgeTiles)
  48. {
  49. for (var i = 0; i < NeighbourVectors.Length; i++)
  50. {
  51. var dir = (NeighborFlag) (1 << i);
  52. if ((spaceNeighbors & dir) != NeighborFlag.Invalid)
  53. _spaceTiles.Add(tile + NeighbourVectors[i]);
  54. }
  55. }
  56. if (referenceGrid == Grid.Owner)
  57. return;
  58. _needToTransform = true;
  59. var entityManager = IoCManager.Resolve<IEntityManager>();
  60. var transformSystem = entityManager.System<SharedTransformSystem>();
  61. var transform = entityManager.GetComponent<TransformComponent>(Grid.Owner);
  62. var size = (float)Grid.TileSize;
  63. _matrix.M31 = size / 2;
  64. _matrix.M32 = size / 2;
  65. Matrix3x2.Invert(spaceMatrix, out var invSpace);
  66. var (_, relativeAngle, worldMatrix) = transformSystem.GetWorldPositionRotationMatrix(transform);
  67. relativeAngle -= spaceAngle;
  68. _matrix *= worldMatrix * invSpace;
  69. _offset = relativeAngle.RotateVec(new Vector2(size / 4, size / 4));
  70. }
  71. public override void InitTile(Vector2i initialTile)
  72. {
  73. TileLists[0] = new() { initialTile };
  74. if (_airtightMap.ContainsKey(initialTile))
  75. EnteredBlockedTiles.Add(initialTile);
  76. else
  77. ProcessedTiles.Add(initialTile);
  78. }
  79. public int AddNewTiles(int iteration, HashSet<Vector2i>? gridJump)
  80. {
  81. SpaceJump = new();
  82. NewTiles = new();
  83. NewBlockedTiles = new();
  84. // Mark tiles as entered if any were just freed due to airtight/explosion blockers being destroyed.
  85. if (FreedTileLists.TryGetValue(iteration, out var freed))
  86. {
  87. HashSet<Vector2i> toRemove = new();
  88. foreach (var tile in freed)
  89. {
  90. if (!EnteredBlockedTiles.Add(tile))
  91. toRemove.Add(tile);
  92. }
  93. freed.ExceptWith(toRemove);
  94. NewFreedTiles = freed;
  95. }
  96. else
  97. {
  98. NewFreedTiles = new();
  99. FreedTileLists[iteration] = NewFreedTiles;
  100. }
  101. // Add adjacent tiles
  102. if (TileLists.TryGetValue(iteration - 2, out var adjacent))
  103. AddNewAdjacentTiles(iteration, adjacent, false);
  104. if (FreedTileLists.TryGetValue(iteration - 2, out var delayedAdjacent))
  105. AddNewAdjacentTiles(iteration, delayedAdjacent, true);
  106. // Add diagonal tiles
  107. if (TileLists.TryGetValue(iteration - 3, out var diagonal))
  108. AddNewDiagonalTiles(iteration, diagonal, false);
  109. if (FreedTileLists.TryGetValue(iteration - 3, out var delayedDiagonal))
  110. AddNewDiagonalTiles(iteration, delayedDiagonal, true);
  111. // Add delayed tiles
  112. AddDelayedNeighbors(iteration);
  113. // Tiles from Spaaaace
  114. if (gridJump != null)
  115. {
  116. foreach (var tile in gridJump)
  117. {
  118. ProcessNewTile(iteration, tile, AtmosDirection.Invalid);
  119. }
  120. }
  121. // Store new tiles
  122. if (NewTiles.Count != 0)
  123. TileLists[iteration] = NewTiles;
  124. if (NewBlockedTiles.Count != 0)
  125. BlockedTileLists[iteration] = NewBlockedTiles;
  126. return NewTiles.Count + NewBlockedTiles.Count;
  127. }
  128. protected override void ProcessNewTile(int iteration, Vector2i tile, AtmosDirection entryDirections)
  129. {
  130. // Is there an airtight blocker on this tile?
  131. if (!_airtightMap.TryGetValue(tile, out var tileData))
  132. {
  133. // No blocker. Ezy. Though maybe this a space tile?
  134. if (_spaceTiles.Contains(tile))
  135. JumpToSpace(tile);
  136. else if (ProcessedTiles.Add(tile))
  137. NewTiles.Add(tile);
  138. return;
  139. }
  140. // If the explosion is entering this new tile from an unblocked direction, we add it directly. Note that because
  141. // for space -> grid jumps, we don't have a direction from which the explosion came, we will only assume it is
  142. // unblocked if all space-facing directions are unblocked. Though this could eventually be done properly.
  143. bool blocked;
  144. var blockedDirections = tileData.BlockedDirections;
  145. if (entryDirections == AtmosDirection.Invalid) // is coming from space?
  146. {
  147. blocked = AnyNeighborBlocked(_edgeTiles[tile], blockedDirections); // at least one space direction is blocked.
  148. }
  149. else
  150. blocked = (blockedDirections & entryDirections) == entryDirections;// **ALL** entry directions are blocked
  151. if (blocked)
  152. {
  153. // was this tile already entered from some other direction?
  154. if (EnteredBlockedTiles.Contains(tile))
  155. return;
  156. // Did the explosion already attempt to enter this tile from some other direction?
  157. if (!UnenteredBlockedTiles.Add(tile))
  158. return;
  159. NewBlockedTiles.Add(tile);
  160. // At what explosion iteration would this blocker be destroyed?
  161. var required = tileData.ExplosionTolerance[_typeIndex];
  162. if (required > _maxIntensity)
  163. return; // blocker is never destroyed.
  164. var clearIteration = iteration + (int) MathF.Ceiling(required / _intensityStepSize);
  165. if (FreedTileLists.TryGetValue(clearIteration, out var list))
  166. list.Add(tile);
  167. else
  168. FreedTileLists[clearIteration] = new() { tile };
  169. return;
  170. }
  171. // was this tile already entered from some other direction?
  172. if (!EnteredBlockedTiles.Add(tile))
  173. return;
  174. // Did the explosion already attempt to enter this tile from some other direction?
  175. if (UnenteredBlockedTiles.Contains(tile))
  176. {
  177. NewFreedTiles.Add(tile);
  178. return;
  179. }
  180. // This is a completely new tile, and we just so happened to enter it from an unblocked direction.
  181. NewTiles.Add(tile);
  182. }
  183. private void JumpToSpace(Vector2i tile)
  184. {
  185. // Did we already jump/process this tile?
  186. if (!_processedSpaceTiles.Add(tile))
  187. return;
  188. if (!_needToTransform)
  189. {
  190. SpaceJump.Add(tile);
  191. return;
  192. }
  193. var center = Vector2.Transform(tile, _matrix);
  194. SpaceJump.Add(new((int) MathF.Floor(center.X + _offset.X), (int) MathF.Floor(center.Y + _offset.Y)));
  195. SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.Y), (int) MathF.Floor(center.Y + _offset.X)));
  196. SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.X), (int) MathF.Floor(center.Y - _offset.Y)));
  197. SpaceJump.Add(new((int) MathF.Floor(center.X + _offset.Y), (int) MathF.Floor(center.Y - _offset.X)));
  198. }
  199. private void AddDelayedNeighbors(int iteration)
  200. {
  201. if (!_delayedNeighbors.TryGetValue(iteration, out var delayed))
  202. return;
  203. foreach (var (tile, direction) in delayed)
  204. {
  205. ProcessNewTile(iteration, tile, direction);
  206. }
  207. _delayedNeighbors.Remove(iteration);
  208. }
  209. // Gets the tiles that are directly adjacent to other tiles. If a currently exploding tile has an airtight entity
  210. // that blocks the explosion from propagating in some direction, those tiles are added to a list of delayed tiles
  211. // that will be added to the explosion in some future iteration.
  212. private void AddNewAdjacentTiles(int iteration, IEnumerable<Vector2i> tiles, bool ignoreTileBlockers = false)
  213. {
  214. foreach (var tile in tiles)
  215. {
  216. var blockedDirections = AtmosDirection.Invalid;
  217. float sealIntegrity = 0;
  218. // Note that if (grid, tile) is not a valid key, then airtight.BlockedDirections will default to 0 (no blocked directions)
  219. if (_airtightMap.TryGetValue(tile, out var tileData))
  220. {
  221. blockedDirections = tileData.BlockedDirections;
  222. sealIntegrity = tileData.ExplosionTolerance[_typeIndex];
  223. }
  224. // First, yield any neighboring tiles that are not blocked by airtight entities on this tile
  225. for (var i = 0; i < Atmospherics.Directions; i++)
  226. {
  227. var direction = (AtmosDirection) (1 << i);
  228. if (ignoreTileBlockers || !blockedDirections.IsFlagSet(direction))
  229. {
  230. ProcessNewTile(iteration, tile.Offset(direction), i.ToOppositeDir());
  231. }
  232. }
  233. // If there are no blocked directions, we are done with this tile.
  234. if (ignoreTileBlockers || blockedDirections == AtmosDirection.Invalid)
  235. continue;
  236. // This tile has one or more airtight entities anchored to it blocking the explosion from traveling in
  237. // some directions. First, check whether this blocker can even be destroyed by this explosion?
  238. if (sealIntegrity > _maxIntensity)
  239. continue;
  240. // At what explosion iteration would this blocker be destroyed?
  241. var clearIteration = iteration + (int) MathF.Ceiling(sealIntegrity / _intensityStepSize);
  242. // Get the delayed neighbours list
  243. if (!_delayedNeighbors.TryGetValue(clearIteration, out var list))
  244. {
  245. list = new();
  246. _delayedNeighbors[clearIteration] = list;
  247. }
  248. // Check which directions are blocked, and add them to the list.
  249. for (var i = 0; i < Atmospherics.Directions; i++)
  250. {
  251. var direction = (AtmosDirection) (1 << i);
  252. if (blockedDirections.IsFlagSet(direction))
  253. {
  254. list.Add((tile.Offset(direction), i.ToOppositeDir()));
  255. }
  256. }
  257. }
  258. }
  259. protected override AtmosDirection GetUnblockedDirectionOrAll(Vector2i tile)
  260. {
  261. return ~_airtightMap.GetValueOrDefault(tile).BlockedDirections;
  262. }
  263. }