1
0

AtmosphereSystem.GridAtmosphere.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. using Content.Server.Atmos.Components;
  2. using Content.Server.Atmos.Reactions;
  3. using Content.Shared.Atmos;
  4. using Content.Shared.Atmos.Components;
  5. using Content.Shared.Atmos.Reactions;
  6. using Robust.Shared.Map;
  7. using Robust.Shared.Map.Components;
  8. using Robust.Shared.Utility;
  9. namespace Content.Server.Atmos.EntitySystems;
  10. public sealed partial class AtmosphereSystem
  11. {
  12. private void InitializeGridAtmosphere()
  13. {
  14. SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit);
  15. SubscribeLocalEvent<GridAtmosphereComponent, ComponentStartup>(OnGridAtmosphereStartup);
  16. SubscribeLocalEvent<GridAtmosphereComponent, ComponentRemove>(OnAtmosphereRemove);
  17. SubscribeLocalEvent<GridAtmosphereComponent, GridSplitEvent>(OnGridSplit);
  18. #region Atmos API Subscriptions
  19. SubscribeLocalEvent<GridAtmosphereComponent, IsSimulatedGridMethodEvent>(GridIsSimulated);
  20. SubscribeLocalEvent<GridAtmosphereComponent, GetAllMixturesMethodEvent>(GridGetAllMixtures);
  21. SubscribeLocalEvent<GridAtmosphereComponent, ReactTileMethodEvent>(GridReactTile);
  22. SubscribeLocalEvent<GridAtmosphereComponent, HotspotExtinguishMethodEvent>(GridHotspotExtinguish);
  23. SubscribeLocalEvent<GridAtmosphereComponent, IsHotspotActiveMethodEvent>(GridIsHotspotActive);
  24. #endregion
  25. }
  26. private void OnAtmosphereRemove(EntityUid uid, GridAtmosphereComponent component, ComponentRemove args)
  27. {
  28. for (var i = 0; i < _currentRunAtmosphere.Count; i++)
  29. {
  30. if (_currentRunAtmosphere[i].Owner != uid)
  31. continue;
  32. _currentRunAtmosphere.RemoveAt(i);
  33. if (_currentRunAtmosphereIndex > i)
  34. _currentRunAtmosphereIndex--;
  35. }
  36. }
  37. private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args)
  38. {
  39. base.Initialize();
  40. EnsureComp<GasTileOverlayComponent>(uid);
  41. foreach (var tile in component.Tiles.Values)
  42. {
  43. tile.GridIndex = uid;
  44. }
  45. }
  46. private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args)
  47. {
  48. if (!TryComp(uid, out MapGridComponent? mapGrid))
  49. return;
  50. InvalidateAllTiles((uid, mapGrid, component));
  51. }
  52. private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args)
  53. {
  54. foreach (var newGrid in args.NewGrids)
  55. {
  56. // Make extra sure this is a valid grid.
  57. if (!TryComp(newGrid, out MapGridComponent? mapGrid))
  58. continue;
  59. // If the new split grid has an atmosphere already somehow, use that. Otherwise, add a new one.
  60. if (!TryComp(newGrid, out GridAtmosphereComponent? newGridAtmos))
  61. newGridAtmos = AddComp<GridAtmosphereComponent>(newGrid);
  62. // We assume the tiles on the new grid have the same coordinates as they did on the old grid...
  63. var enumerator = _mapSystem.GetAllTilesEnumerator(newGrid, mapGrid);
  64. while (enumerator.MoveNext(out var tile))
  65. {
  66. var indices = tile.Value.GridIndices;
  67. // This split event happens *before* the spaced tiles have been invalidated, therefore we can still
  68. // access their gas data. On the next atmos update tick, these tiles will be spaced. Poof!
  69. if (!originalGridAtmos.Tiles.TryGetValue(indices, out var tileAtmosphere))
  70. continue;
  71. // The new grid atmosphere has been initialized, meaning it has all the needed TileAtmospheres...
  72. if (!newGridAtmos.Tiles.TryGetValue(indices, out var newTileAtmosphere))
  73. // Let's be honest, this is really not gonna happen, but just in case...!
  74. continue;
  75. // Copy a bunch of data over... Not great, maybe put this in TileAtmosphere?
  76. newTileAtmosphere.Air = tileAtmosphere.Air?.Clone();
  77. newTileAtmosphere.Hotspot = tileAtmosphere.Hotspot;
  78. newTileAtmosphere.HeatCapacity = tileAtmosphere.HeatCapacity;
  79. newTileAtmosphere.Temperature = tileAtmosphere.Temperature;
  80. newTileAtmosphere.PressureDifference = tileAtmosphere.PressureDifference;
  81. newTileAtmosphere.PressureDirection = tileAtmosphere.PressureDirection;
  82. // TODO ATMOS: Somehow force GasTileOverlaySystem to perform an update *right now, right here.*
  83. // The reason why is that right now, gas will flicker until the next GasTileOverlay update.
  84. // That looks bad, of course. We want to avoid that! Anyway that's a bit more complicated so out of scope.
  85. // Invalidate the tile, it's redundant but redundancy is good! Also HashSet so really, no duplicates.
  86. originalGridAtmos.InvalidatedCoords.Add(indices);
  87. newGridAtmos.InvalidatedCoords.Add(indices);
  88. }
  89. }
  90. }
  91. private void GridIsSimulated(EntityUid uid, GridAtmosphereComponent component, ref IsSimulatedGridMethodEvent args)
  92. {
  93. if (args.Handled)
  94. return;
  95. args.Simulated = component.Simulated;
  96. args.Handled = true;
  97. }
  98. private void GridGetAllMixtures(EntityUid uid, GridAtmosphereComponent component,
  99. ref GetAllMixturesMethodEvent args)
  100. {
  101. if (args.Handled)
  102. return;
  103. IEnumerable<GasMixture> EnumerateMixtures(EntityUid gridUid, GridAtmosphereComponent grid, bool invalidate)
  104. {
  105. foreach (var (indices, tile) in grid.Tiles)
  106. {
  107. if (tile.Air == null)
  108. continue;
  109. if (invalidate)
  110. {
  111. //var ev = new InvalidateTileMethodEvent(gridUid, indices);
  112. //GridInvalidateTile(gridUid, grid, ref ev);
  113. AddActiveTile(grid, tile);
  114. }
  115. yield return tile.Air;
  116. }
  117. }
  118. // Return the enumeration over all the tiles in the atmosphere.
  119. args.Mixtures = EnumerateMixtures(uid, component, args.Excite);
  120. args.Handled = true;
  121. }
  122. private void GridReactTile(EntityUid uid, GridAtmosphereComponent component, ref ReactTileMethodEvent args)
  123. {
  124. if (args.Handled)
  125. return;
  126. if (!component.Tiles.TryGetValue(args.Tile, out var tile))
  127. return;
  128. args.Result = tile.Air is { } air ? React(air, tile) : ReactionResult.NoReaction;
  129. args.Handled = true;
  130. }
  131. /// <summary>
  132. /// Update array of adjacent tiles and the adjacency flags.
  133. /// </summary>
  134. private void UpdateAdjacentTiles(
  135. Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
  136. TileAtmosphere tile,
  137. bool activate = false)
  138. {
  139. var uid = ent.Owner;
  140. var atmos = ent.Comp1;
  141. var blockedDirs = tile.AirtightData.BlockedDirections;
  142. if (activate)
  143. AddActiveTile(atmos, tile);
  144. tile.AdjacentBits = AtmosDirection.Invalid;
  145. for (var i = 0; i < Atmospherics.Directions; i++)
  146. {
  147. var direction = (AtmosDirection)(1 << i);
  148. var adjacentIndices = tile.GridIndices.Offset(direction);
  149. TileAtmosphere? adjacent;
  150. if (!tile.NoGridTile)
  151. {
  152. adjacent = GetOrNewTile(uid, atmos, adjacentIndices);
  153. }
  154. else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent))
  155. {
  156. tile.AdjacentBits &= ~direction;
  157. tile.AdjacentTiles[i] = null;
  158. continue;
  159. }
  160. var adjBlockDirs = adjacent.AirtightData.BlockedDirections;
  161. if (activate)
  162. AddActiveTile(atmos, adjacent);
  163. var oppositeIndex = i.ToOppositeIndex();
  164. var oppositeDirection = (AtmosDirection)(1 << oppositeIndex);
  165. if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
  166. {
  167. // Adjacency is blocked by some airtight entity.
  168. tile.AdjacentBits &= ~direction;
  169. adjacent.AdjacentBits &= ~oppositeDirection;
  170. tile.AdjacentTiles[i] = null;
  171. adjacent.AdjacentTiles[oppositeIndex] = null;
  172. }
  173. else
  174. {
  175. // No airtight entity in the way.
  176. tile.AdjacentBits |= direction;
  177. adjacent.AdjacentBits |= oppositeDirection;
  178. tile.AdjacentTiles[i] = adjacent;
  179. adjacent.AdjacentTiles[oppositeIndex] = tile;
  180. }
  181. DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^
  182. adjacent.AdjacentBits.IsFlagSet(oppositeDirection)));
  183. if (!adjacent.AdjacentBits.IsFlagSet(adjacent.MonstermosInfo.CurrentTransferDirection))
  184. adjacent.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
  185. }
  186. if (!tile.AdjacentBits.IsFlagSet(tile.MonstermosInfo.CurrentTransferDirection))
  187. tile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
  188. }
  189. private (GasMixture Air, bool IsSpace) GetDefaultMapAtmosphere(MapAtmosphereComponent? map)
  190. {
  191. if (map == null)
  192. return (GasMixture.SpaceGas, true);
  193. var air = map.Mixture;
  194. DebugTools.Assert(air.Immutable);
  195. return (air, map.Space);
  196. }
  197. private void GridHotspotExtinguish(EntityUid uid, GridAtmosphereComponent component,
  198. ref HotspotExtinguishMethodEvent args)
  199. {
  200. if (args.Handled)
  201. return;
  202. if (!component.Tiles.TryGetValue(args.Tile, out var tile))
  203. return;
  204. tile.Hotspot = new Hotspot();
  205. args.Handled = true;
  206. //var ev = new InvalidateTileMethodEvent(uid, args.Tile);
  207. //GridInvalidateTile(uid, component, ref ev);
  208. AddActiveTile(component, tile);
  209. }
  210. private void GridIsHotspotActive(EntityUid uid, GridAtmosphereComponent component,
  211. ref IsHotspotActiveMethodEvent args)
  212. {
  213. if (args.Handled)
  214. return;
  215. if (!component.Tiles.TryGetValue(args.Tile, out var tile))
  216. return;
  217. args.Result = tile.Hotspot.Valid;
  218. args.Handled = true;
  219. }
  220. private void GridFixTileVacuum(TileAtmosphere tile)
  221. {
  222. DebugTools.AssertNotNull(tile.Air);
  223. DebugTools.Assert(tile.Air?.Immutable == false);
  224. tile.AirArchived = null;
  225. tile.ArchivedCycle = 0;
  226. var count = 0;
  227. foreach (var adj in tile.AdjacentTiles)
  228. {
  229. if (adj?.Air != null)
  230. count++;
  231. }
  232. if (count == 0)
  233. return;
  234. var ratio = 1f / count;
  235. var totalTemperature = 0f;
  236. foreach (var adj in tile.AdjacentTiles)
  237. {
  238. if (adj?.Air == null)
  239. continue;
  240. totalTemperature += adj.Temperature;
  241. // TODO ATMOS. Why is this removing and then re-adding air to the neighbouring tiles?
  242. // Is it some rounding issue to do with Atmospherics.GasMinMoles? because otherwise this is just unnecessary.
  243. // if we get rid of this, then this could also just add moles and then multiply by ratio at the end, rather
  244. // than having to iterate over adjacent tiles twice.
  245. // Remove a bit of gas from the adjacent ratio...
  246. var mix = adj.Air.RemoveRatio(ratio);
  247. // And merge it to the new tile air.
  248. Merge(tile.Air, mix);
  249. // Return removed gas to its original mixture.
  250. Merge(adj.Air, mix);
  251. }
  252. // New temperature is the arithmetic mean of the sum of the adjacent temperatures...
  253. tile.Air.Temperature = totalTemperature / count;
  254. }
  255. /// <summary>
  256. /// Repopulates all tiles on a grid atmosphere.
  257. /// </summary>
  258. public void InvalidateAllTiles(Entity<MapGridComponent?, GridAtmosphereComponent?> entity)
  259. {
  260. var (uid, grid, atmos) = entity;
  261. if (!Resolve(uid, ref grid, ref atmos))
  262. return;
  263. foreach (var indices in atmos.Tiles.Keys)
  264. {
  265. atmos.InvalidatedCoords.Add(indices);
  266. }
  267. var enumerator = _map.GetAllTilesEnumerator(uid, grid);
  268. while (enumerator.MoveNext(out var tile))
  269. {
  270. atmos.InvalidatedCoords.Add(tile.Value.GridIndices);
  271. }
  272. }
  273. public TileRef GetTileRef(TileAtmosphere tile)
  274. {
  275. if (!TryComp(tile.GridIndex, out MapGridComponent? grid))
  276. return default;
  277. _map.TryGetTileRef(tile.GridIndex, grid, tile.GridIndices, out var tileRef);
  278. return tileRef;
  279. }
  280. }