AtmosphereSystem.Monstermos.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. using System.Linq;
  2. using System.Numerics;
  3. using Content.Server.Atmos.Components;
  4. using Content.Server.Doors.Systems;
  5. using Content.Shared.Atmos;
  6. using Content.Shared.Atmos.Components;
  7. using Content.Shared.Database;
  8. using Robust.Shared.Map.Components;
  9. using Robust.Shared.Physics.Components;
  10. using Robust.Shared.Random;
  11. using Robust.Shared.Utility;
  12. namespace Content.Server.Atmos.EntitySystems
  13. {
  14. public sealed partial class AtmosphereSystem
  15. {
  16. [Dependency] private readonly FirelockSystem _firelockSystem = default!;
  17. private readonly TileAtmosphereComparer _monstermosComparer = new();
  18. private readonly TileAtmosphere?[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
  19. private readonly TileAtmosphere[] _equalizeGiverTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
  20. private readonly TileAtmosphere[] _equalizeTakerTiles = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
  21. private readonly TileAtmosphere[] _equalizeQueue = new TileAtmosphere[Atmospherics.MonstermosTileLimit];
  22. private readonly TileAtmosphere[] _depressurizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
  23. private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
  24. private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
  25. private void EqualizePressureInZone(
  26. Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
  27. TileAtmosphere tile,
  28. int cycleNum)
  29. {
  30. if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
  31. return; // Already done.
  32. tile.MonstermosInfo = new MonstermosInfo();
  33. var startingMoles = tile.Air.TotalMoles;
  34. var runAtmos = false;
  35. // We need to figure if this is necessary
  36. for (var i = 0; i < Atmospherics.Directions; i++)
  37. {
  38. var direction = (AtmosDirection) (1 << i);
  39. if (!tile.AdjacentBits.IsFlagSet(direction)) continue;
  40. var other = tile.AdjacentTiles[i];
  41. if (other?.Air == null) continue;
  42. var comparisonMoles = other.Air.TotalMoles;
  43. if (!(MathF.Abs(comparisonMoles - startingMoles) > Atmospherics.MinimumMolesDeltaToMove)) continue;
  44. runAtmos = true;
  45. break;
  46. }
  47. if (!runAtmos) // There's no need so we don't bother.
  48. {
  49. tile.MonstermosInfo.LastCycle = cycleNum;
  50. return;
  51. }
  52. var gridAtmosphere = ent.Comp1;
  53. var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
  54. var totalMoles = 0f;
  55. _equalizeTiles[0] = tile;
  56. tile.MonstermosInfo.LastQueueCycle = queueCycle;
  57. var tileCount = 1;
  58. for (var i = 0; i < tileCount; i++)
  59. {
  60. if (i > Atmospherics.MonstermosHardTileLimit) break;
  61. var exploring = _equalizeTiles[i]!;
  62. if (i < Atmospherics.MonstermosTileLimit)
  63. {
  64. // Tiles in the _equalizeTiles array cannot have null air.
  65. var tileMoles = exploring.Air!.TotalMoles;
  66. exploring.MonstermosInfo.MoleDelta = tileMoles;
  67. totalMoles += tileMoles;
  68. }
  69. for (var j = 0; j < Atmospherics.Directions; j++)
  70. {
  71. var direction = (AtmosDirection) (1 << j);
  72. if (!exploring.AdjacentBits.IsFlagSet(direction)) continue;
  73. var adj = exploring.AdjacentTiles[j];
  74. if (adj?.Air == null) continue;
  75. if(adj.MonstermosInfo.LastQueueCycle == queueCycle) continue;
  76. adj.MonstermosInfo = new MonstermosInfo {LastQueueCycle = queueCycle};
  77. if(tileCount < Atmospherics.MonstermosHardTileLimit)
  78. _equalizeTiles[tileCount++] = adj;
  79. if (adj.Space && MonstermosDepressurization)
  80. {
  81. // Looks like someone opened an airlock to space!
  82. ExplosivelyDepressurize(ent, tile, cycleNum);
  83. return;
  84. }
  85. }
  86. }
  87. if (tileCount > Atmospherics.MonstermosTileLimit)
  88. {
  89. for (var i = Atmospherics.MonstermosTileLimit; i < tileCount; i++)
  90. {
  91. //We unmark them. We shouldn't be pushing/pulling gases to/from them.
  92. var otherTile = _equalizeTiles[i];
  93. if (otherTile == null)
  94. continue;
  95. otherTile.MonstermosInfo.LastQueueCycle = 0;
  96. }
  97. tileCount = Atmospherics.MonstermosTileLimit;
  98. }
  99. var averageMoles = totalMoles / (tileCount);
  100. var giverTilesLength = 0;
  101. var takerTilesLength = 0;
  102. for (var i = 0; i < tileCount; i++)
  103. {
  104. var otherTile = _equalizeTiles[i]!;
  105. otherTile.MonstermosInfo.LastCycle = cycleNum;
  106. otherTile.MonstermosInfo.MoleDelta -= averageMoles;
  107. if (otherTile.MonstermosInfo.MoleDelta > 0)
  108. {
  109. _equalizeGiverTiles[giverTilesLength++] = otherTile;
  110. }
  111. else
  112. {
  113. _equalizeTakerTiles[takerTilesLength++] = otherTile;
  114. }
  115. }
  116. var logN = MathF.Log2(tileCount);
  117. // Optimization - try to spread gases using an O(n log n) algorithm that has a chance of not working first to avoid O(n^2)
  118. if (giverTilesLength > logN && takerTilesLength > logN)
  119. {
  120. // Even if it fails, it will speed up the next part.
  121. Array.Sort(_equalizeTiles, 0, tileCount, _monstermosComparer);
  122. for (var i = 0; i < tileCount; i++)
  123. {
  124. var otherTile = _equalizeTiles[i]!;
  125. otherTile.MonstermosInfo.FastDone = true;
  126. if (!(otherTile.MonstermosInfo.MoleDelta > 0)) continue;
  127. var eligibleDirections = AtmosDirection.Invalid;
  128. var eligibleDirectionCount = 0;
  129. for (var j = 0; j < Atmospherics.Directions; j++)
  130. {
  131. var direction = (AtmosDirection) (1 << j);
  132. if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
  133. var tile2 = otherTile.AdjacentTiles[j]!;
  134. DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
  135. // skip anything that isn't part of our current processing block.
  136. if (tile2.MonstermosInfo.FastDone || tile2.MonstermosInfo.LastQueueCycle != queueCycle)
  137. continue;
  138. eligibleDirections |= direction;
  139. eligibleDirectionCount++;
  140. }
  141. if (eligibleDirectionCount <= 0)
  142. continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this.
  143. var molesToMove = otherTile.MonstermosInfo.MoleDelta / eligibleDirectionCount;
  144. for (var j = 0; j < Atmospherics.Directions; j++)
  145. {
  146. var direction = (AtmosDirection) (1 << j);
  147. if (!eligibleDirections.IsFlagSet(direction)) continue;
  148. AdjustEqMovement(otherTile, direction, molesToMove);
  149. otherTile.MonstermosInfo.MoleDelta -= molesToMove;
  150. otherTile.AdjacentTiles[j]!.MonstermosInfo.MoleDelta += molesToMove;
  151. }
  152. }
  153. giverTilesLength = 0;
  154. takerTilesLength = 0;
  155. for (var i = 0; i < tileCount; i++)
  156. {
  157. var otherTile = _equalizeTiles[i]!;
  158. if (otherTile.MonstermosInfo.MoleDelta > 0)
  159. {
  160. _equalizeGiverTiles[giverTilesLength++] = otherTile;
  161. }
  162. else
  163. {
  164. _equalizeTakerTiles[takerTilesLength++] = otherTile;
  165. }
  166. }
  167. }
  168. // This is the part that can become O(n^2).
  169. if (giverTilesLength < takerTilesLength)
  170. {
  171. // as an optimization, we choose one of two methods based on which list is smaller. We really want to avoid O(n^2) if we can.
  172. for (var j = 0; j < giverTilesLength; j++)
  173. {
  174. var giver = _equalizeGiverTiles[j];
  175. giver.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
  176. giver.MonstermosInfo.CurrentTransferAmount = 0;
  177. var queueCycleSlow = ++gridAtmosphere.EqualizationQueueCycleControl;
  178. var queueLength = 0;
  179. _equalizeQueue[queueLength++] = giver;
  180. giver.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
  181. for (var i = 0; i < queueLength; i++)
  182. {
  183. if (giver.MonstermosInfo.MoleDelta <= 0)
  184. break; // We're done here now. Let's not do more work than needed.
  185. var otherTile = _equalizeQueue[i];
  186. for (var k = 0; k < Atmospherics.Directions; k++)
  187. {
  188. var direction = (AtmosDirection) (1 << k);
  189. if (!otherTile.AdjacentBits.IsFlagSet(direction))
  190. continue;
  191. if (giver.MonstermosInfo.MoleDelta <= 0)
  192. break; // We're done here now. Let's not do more work than needed.
  193. var otherTile2 = otherTile.AdjacentTiles[k];
  194. if (otherTile2 == null || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue;
  195. DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
  196. if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
  197. _equalizeQueue[queueLength++] = otherTile2;
  198. otherTile2.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
  199. otherTile2.MonstermosInfo.CurrentTransferDirection = k.ToOppositeDir();
  200. otherTile2.MonstermosInfo.CurrentTransferAmount = 0;
  201. if (otherTile2.MonstermosInfo.MoleDelta < 0)
  202. {
  203. // This tile needs gas. Let's give it to 'em.
  204. if (-otherTile2.MonstermosInfo.MoleDelta > giver.MonstermosInfo.MoleDelta)
  205. {
  206. // We don't have enough gas!
  207. otherTile2.MonstermosInfo.CurrentTransferAmount -= giver.MonstermosInfo.MoleDelta;
  208. otherTile2.MonstermosInfo.MoleDelta += giver.MonstermosInfo.MoleDelta;
  209. giver.MonstermosInfo.MoleDelta = 0;
  210. }
  211. else
  212. {
  213. // We have enough gas.
  214. otherTile2.MonstermosInfo.CurrentTransferAmount += otherTile2.MonstermosInfo.MoleDelta;
  215. giver.MonstermosInfo.MoleDelta += otherTile2.MonstermosInfo.MoleDelta;
  216. otherTile2.MonstermosInfo.MoleDelta = 0;
  217. }
  218. }
  219. }
  220. }
  221. // Putting this loop here helps make it O(n^2) over O(n^3)
  222. for (var i = queueLength - 1; i >= 0; i--)
  223. {
  224. var otherTile = _equalizeQueue[i];
  225. if (otherTile.MonstermosInfo.CurrentTransferAmount != 0 && otherTile.MonstermosInfo.CurrentTransferDirection != AtmosDirection.Invalid)
  226. {
  227. AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount);
  228. otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]!
  229. .MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
  230. otherTile.MonstermosInfo.CurrentTransferAmount = 0;
  231. }
  232. }
  233. }
  234. }
  235. else
  236. {
  237. for (var j = 0; j < takerTilesLength; j++)
  238. {
  239. var taker = _equalizeTakerTiles[j];
  240. taker.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
  241. taker.MonstermosInfo.CurrentTransferAmount = 0;
  242. var queueCycleSlow = ++gridAtmosphere.EqualizationQueueCycleControl;
  243. var queueLength = 0;
  244. _equalizeQueue[queueLength++] = taker;
  245. taker.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
  246. for (var i = 0; i < queueLength; i++)
  247. {
  248. if (taker.MonstermosInfo.MoleDelta >= 0)
  249. break; // We're done here now. Let's not do more work than needed.
  250. var otherTile = _equalizeQueue[i];
  251. for (var k = 0; k < Atmospherics.Directions; k++)
  252. {
  253. var direction = (AtmosDirection) (1 << k);
  254. if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
  255. var otherTile2 = otherTile.AdjacentTiles[k];
  256. if (taker.MonstermosInfo.MoleDelta >= 0) break; // We're done here now. Let's not do more work than needed.
  257. if (otherTile2 == null || otherTile2.AdjacentBits == 0 || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue;
  258. DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
  259. if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
  260. _equalizeQueue[queueLength++] = otherTile2;
  261. otherTile2.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
  262. otherTile2.MonstermosInfo.CurrentTransferDirection = k.ToOppositeDir();
  263. otherTile2.MonstermosInfo.CurrentTransferAmount = 0;
  264. if (otherTile2.MonstermosInfo.MoleDelta > 0)
  265. {
  266. // This tile has gas we can suck, so let's
  267. if (otherTile2.MonstermosInfo.MoleDelta > -taker.MonstermosInfo.MoleDelta)
  268. {
  269. // They have enough gas
  270. otherTile2.MonstermosInfo.CurrentTransferAmount -= taker.MonstermosInfo.MoleDelta;
  271. otherTile2.MonstermosInfo.MoleDelta += taker.MonstermosInfo.MoleDelta;
  272. taker.MonstermosInfo.MoleDelta = 0;
  273. }
  274. else
  275. {
  276. // They don't have enough gas!
  277. otherTile2.MonstermosInfo.CurrentTransferAmount += otherTile2.MonstermosInfo.MoleDelta;
  278. taker.MonstermosInfo.MoleDelta += otherTile2.MonstermosInfo.MoleDelta;
  279. otherTile2.MonstermosInfo.MoleDelta = 0;
  280. }
  281. }
  282. }
  283. }
  284. for (var i = queueLength - 1; i >= 0; i--)
  285. {
  286. var otherTile = _equalizeQueue[i];
  287. if (otherTile.MonstermosInfo.CurrentTransferAmount == 0 || otherTile.MonstermosInfo.CurrentTransferDirection == AtmosDirection.Invalid)
  288. continue;
  289. AdjustEqMovement(otherTile, otherTile.MonstermosInfo.CurrentTransferDirection, otherTile.MonstermosInfo.CurrentTransferAmount);
  290. otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()]!
  291. .MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
  292. otherTile.MonstermosInfo.CurrentTransferAmount = 0;
  293. }
  294. }
  295. }
  296. for (var i = 0; i < tileCount; i++)
  297. {
  298. var otherTile = _equalizeTiles[i]!;
  299. FinalizeEq(ent, otherTile);
  300. }
  301. for (var i = 0; i < tileCount; i++)
  302. {
  303. var otherTile = _equalizeTiles[i]!;
  304. for (var j = 0; j < Atmospherics.Directions; j++)
  305. {
  306. var direction = (AtmosDirection) (1 << j);
  307. if (!otherTile.AdjacentBits.IsFlagSet(direction))
  308. continue;
  309. var otherTile2 = otherTile.AdjacentTiles[j]!;
  310. if (otherTile2.AdjacentBits == 0)
  311. continue;
  312. DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
  313. if (otherTile2.Air != null && CompareExchange(otherTile2, tile) == GasCompareResult.NoExchange)
  314. continue;
  315. AddActiveTile(gridAtmosphere, otherTile2);
  316. break;
  317. }
  318. }
  319. // We do cleanup.
  320. Array.Clear(_equalizeTiles, 0, Atmospherics.MonstermosHardTileLimit);
  321. Array.Clear(_equalizeGiverTiles, 0, Atmospherics.MonstermosTileLimit);
  322. Array.Clear(_equalizeTakerTiles, 0, Atmospherics.MonstermosTileLimit);
  323. Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
  324. }
  325. private void ExplosivelyDepressurize(
  326. Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
  327. TileAtmosphere tile,
  328. int cycleNum)
  329. {
  330. // Check if explosive depressurization is enabled and if the tile is valid.
  331. if (!MonstermosDepressurization || tile.Air == null)
  332. return;
  333. const int limit = Atmospherics.MonstermosHardTileLimit;
  334. var totalMolesRemoved = 0f;
  335. var (owner, gridAtmosphere, visuals, mapGrid, _) = ent;
  336. var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
  337. var tileCount = 0;
  338. var spaceTileCount = 0;
  339. _depressurizeTiles[tileCount++] = tile;
  340. tile.MonstermosInfo = new MonstermosInfo {LastQueueCycle = queueCycle};
  341. for (var i = 0; i < tileCount; i++)
  342. {
  343. var otherTile = _depressurizeTiles[i];
  344. otherTile.MonstermosInfo.LastCycle = cycleNum;
  345. otherTile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
  346. // Tiles in the _depressurizeTiles array cannot have null air.
  347. if (!otherTile.Space)
  348. {
  349. for (var j = 0; j < Atmospherics.Directions; j++)
  350. {
  351. var otherTile2 = otherTile.AdjacentTiles[j];
  352. if (otherTile2?.Air == null)
  353. continue;
  354. if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle)
  355. continue;
  356. var direction = (AtmosDirection) (1 << j);
  357. DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction));
  358. DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
  359. ConsiderFirelocks(ent, otherTile, otherTile2);
  360. // The firelocks might have closed on us.
  361. if (!otherTile.AdjacentBits.IsFlagSet(direction))
  362. continue;
  363. otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle };
  364. _depressurizeTiles[tileCount++] = otherTile2;
  365. if (tileCount >= limit)
  366. break;
  367. }
  368. }
  369. else
  370. {
  371. _depressurizeSpaceTiles[spaceTileCount++] = otherTile;
  372. otherTile.PressureSpecificTarget = otherTile;
  373. }
  374. if (tileCount < limit && spaceTileCount < limit)
  375. continue;
  376. break;
  377. }
  378. var queueCycleSlow = ++gridAtmosphere.EqualizationQueueCycleControl;
  379. var progressionCount = 0;
  380. for (var i = 0; i < spaceTileCount; i++)
  381. {
  382. var otherTile = _depressurizeSpaceTiles[i];
  383. _depressurizeProgressionOrder[progressionCount++] = otherTile;
  384. otherTile.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
  385. otherTile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
  386. }
  387. // Moving into the room from the breach or airlock
  388. for (var i = 0; i < progressionCount; i++)
  389. {
  390. // From a tile exposed to space
  391. var otherTile = _depressurizeProgressionOrder[i];
  392. for (var j = 0; j < Atmospherics.Directions; j++)
  393. {
  394. // Flood fill into this new direction
  395. var direction = (AtmosDirection) (1 << j);
  396. // Tiles in _depressurizeProgressionOrder cannot have null air.
  397. if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space)
  398. continue;
  399. var tile2 = otherTile.AdjacentTiles[j];
  400. if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle)
  401. continue;
  402. DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
  403. // If flood fill has already reached this tile, continue.
  404. if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow)
  405. continue;
  406. if(tile2.Space)
  407. continue;
  408. tile2.MonstermosInfo.CurrentTransferDirection = j.ToOppositeDir();
  409. tile2.MonstermosInfo.CurrentTransferAmount = 0.0f;
  410. tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget;
  411. tile2.MonstermosInfo.LastSlowQueueCycle = queueCycleSlow;
  412. _depressurizeProgressionOrder[progressionCount++] = tile2;
  413. }
  414. }
  415. // Moving towards the breach from the edges of the flood filled region
  416. for (var i = progressionCount - 1; i >= 0; i--)
  417. {
  418. var otherTile = _depressurizeProgressionOrder[i];
  419. if (otherTile?.Air == null) { continue;}
  420. if (otherTile.MonstermosInfo.CurrentTransferDirection == AtmosDirection.Invalid) continue;
  421. gridAtmosphere.HighPressureDelta.Add(otherTile);
  422. AddActiveTile(gridAtmosphere, otherTile);
  423. var otherTile2 = otherTile.AdjacentTiles[otherTile.MonstermosInfo.CurrentTransferDirection.ToIndex()];
  424. if (otherTile2?.Air == null)
  425. {
  426. // The tile connecting us to space is spaced already. So just space this tile now.
  427. otherTile.Air!.Clear();
  428. otherTile.Air.Temperature = Atmospherics.TCMB;
  429. continue;
  430. }
  431. var sum = otherTile.Air.TotalMoles;
  432. if (SpacingEscapeRatio < 1f)
  433. {
  434. sum *= SpacingEscapeRatio;
  435. if (sum < SpacingMinGas)
  436. {
  437. // Boost the last bit of air draining from the tile.
  438. sum = Math.Min(SpacingMinGas, otherTile.Air.TotalMoles);
  439. }
  440. if (sum + otherTile.MonstermosInfo.CurrentTransferAmount > SpacingMaxWind)
  441. {
  442. // Limit the flow of air out of tiles which have air flowing into them from elsewhere.
  443. sum = Math.Max(SpacingMinGas, SpacingMaxWind - otherTile.MonstermosInfo.CurrentTransferAmount);
  444. }
  445. }
  446. totalMolesRemoved += sum;
  447. otherTile.MonstermosInfo.CurrentTransferAmount += sum;
  448. otherTile2.MonstermosInfo.CurrentTransferAmount += otherTile.MonstermosInfo.CurrentTransferAmount;
  449. otherTile.PressureDifference = otherTile.MonstermosInfo.CurrentTransferAmount;
  450. otherTile.PressureDirection = otherTile.MonstermosInfo.CurrentTransferDirection;
  451. if (otherTile2.MonstermosInfo.CurrentTransferDirection == AtmosDirection.Invalid)
  452. {
  453. otherTile2.PressureDifference = otherTile2.MonstermosInfo.CurrentTransferAmount;
  454. otherTile2.PressureDirection = otherTile.MonstermosInfo.CurrentTransferDirection;
  455. }
  456. if (otherTile.Air != null && otherTile.Air.Pressure - sum > SpacingMinGas * 0.1f)
  457. {
  458. // Transfer the air into the other tile (space wind :)
  459. ReleaseGasTo(otherTile.Air!, otherTile2.Air!, sum);
  460. // And then some magically into space
  461. ReleaseGasTo(otherTile2.Air!, null, sum * 0.3f);
  462. if (otherTile.Air.Temperature > 280.0f)
  463. {
  464. // Temperature reduces as air drains. But nerf the real temperature reduction a bit
  465. // Also, limit the temperature loss to remain > 10 Deg.C for convenience
  466. float realtemploss = (otherTile.Air.TotalMoles - sum) / otherTile.Air.TotalMoles;
  467. otherTile.Air.Temperature *= 0.9f + 0.1f * realtemploss;
  468. }
  469. }
  470. else
  471. {
  472. // This gas mixture cannot be null, no tile in _depressurizeProgressionOrder can have a null gas mixture
  473. otherTile.Air!.Clear();
  474. // This is a little hacky, but hear me out. It makes sense. We have just vacuumed all of the tile's air
  475. // therefore there is no more gas in the tile, therefore the tile should be as cold as space!
  476. otherTile.Air.Temperature = Atmospherics.TCMB;
  477. }
  478. InvalidateVisuals(ent, otherTile);
  479. HandleDecompressionFloorRip(mapGrid, otherTile, otherTile.MonstermosInfo.CurrentTransferAmount);
  480. }
  481. if (GridImpulse && tileCount > 0)
  482. {
  483. var direction = ((Vector2)_depressurizeTiles[tileCount - 1].GridIndices - tile.GridIndices).Normalized();
  484. var gridPhysics = Comp<PhysicsComponent>(owner);
  485. // TODO ATMOS: Come up with better values for these.
  486. _physics.ApplyLinearImpulse(owner, direction * totalMolesRemoved * gridPhysics.Mass, body: gridPhysics);
  487. _physics.ApplyAngularImpulse(owner, Vector2Helpers.Cross(tile.GridIndices - gridPhysics.LocalCenter, direction) * totalMolesRemoved, body: gridPhysics);
  488. }
  489. if (tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
  490. _adminLog.Add(LogType.ExplosiveDepressurization, LogImpact.High,
  491. $"Explosive depressurization removed {totalMolesRemoved} moles from {tileCount} tiles starting from position {tile.GridIndices:position} on grid ID {tile.GridIndex:grid}");
  492. Array.Clear(_depressurizeTiles, 0, Atmospherics.MonstermosHardTileLimit);
  493. Array.Clear(_depressurizeSpaceTiles, 0, Atmospherics.MonstermosHardTileLimit);
  494. Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2);
  495. }
  496. private void ConsiderFirelocks(
  497. Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
  498. TileAtmosphere tile,
  499. TileAtmosphere other)
  500. {
  501. var reconsiderAdjacent = false;
  502. var mapGrid = ent.Comp3;
  503. foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, tile.GridIndices))
  504. {
  505. if (_firelockQuery.TryGetComponent(entity, out var firelock))
  506. reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
  507. }
  508. foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, other.GridIndices))
  509. {
  510. if (_firelockQuery.TryGetComponent(entity, out var firelock))
  511. reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
  512. }
  513. if (!reconsiderAdjacent)
  514. return;
  515. UpdateAdjacentTiles(ent, tile);
  516. UpdateAdjacentTiles(ent, other);
  517. InvalidateVisuals(ent, tile);
  518. InvalidateVisuals(ent, other);
  519. }
  520. private void FinalizeEq(
  521. Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
  522. TileAtmosphere tile)
  523. {
  524. Span<float> transferDirections = stackalloc float[Atmospherics.Directions];
  525. var hasTransferDirs = false;
  526. for (var i = 0; i < Atmospherics.Directions; i++)
  527. {
  528. var amount = tile.MonstermosInfo[i];
  529. if (amount == 0) continue;
  530. transferDirections[i] = amount;
  531. tile.MonstermosInfo[i] = 0; // Set them to 0 to prevent infinite recursion.
  532. hasTransferDirs = true;
  533. }
  534. if (!hasTransferDirs) return;
  535. for(var i = 0; i < Atmospherics.Directions; i++)
  536. {
  537. var direction = (AtmosDirection) (1 << i);
  538. if (!tile.AdjacentBits.IsFlagSet(direction)) continue;
  539. var amount = transferDirections[i];
  540. var otherTile = tile.AdjacentTiles[i];
  541. if (otherTile?.Air == null) continue;
  542. DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction.GetOpposite()));
  543. if (amount <= 0) continue;
  544. // Everything that calls this method already ensures that Air will not be null.
  545. if (tile.Air!.TotalMoles < amount)
  546. FinalizeEqNeighbors(ent, tile, transferDirections);
  547. otherTile.MonstermosInfo[i.ToOppositeDir()] = 0;
  548. Merge(otherTile.Air, tile.Air.Remove(amount));
  549. InvalidateVisuals(ent, tile);
  550. InvalidateVisuals(ent, otherTile);
  551. ConsiderPressureDifference(ent, tile, direction, amount);
  552. }
  553. }
  554. private void FinalizeEqNeighbors(
  555. Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
  556. TileAtmosphere tile, ReadOnlySpan<float> transferDirs)
  557. {
  558. for (var i = 0; i < Atmospherics.Directions; i++)
  559. {
  560. var direction = (AtmosDirection) (1 << i);
  561. var amount = transferDirs[i];
  562. // Since AdjacentBits is set, AdjacentTiles[i] wouldn't be null, and neither would its air.
  563. if(amount < 0 && tile.AdjacentBits.IsFlagSet(direction))
  564. FinalizeEq(ent, tile.AdjacentTiles[i]!); // A bit of recursion if needed.
  565. }
  566. }
  567. private void AdjustEqMovement(TileAtmosphere tile, AtmosDirection direction, float amount)
  568. {
  569. DebugTools.AssertNotNull(tile);
  570. DebugTools.Assert(tile.AdjacentBits.IsFlagSet(direction));
  571. DebugTools.Assert(tile.AdjacentTiles[direction.ToIndex()] != null);
  572. // Every call to this method already ensures that the adjacent tile won't be null.
  573. // Turns out: no they don't. Temporary debug checks to figure out which caller is causing problems:
  574. if (tile == null)
  575. {
  576. Log.Error($"Encountered null-tile in {nameof(AdjustEqMovement)}. Trace: {Environment.StackTrace}");
  577. return;
  578. }
  579. var idx = direction.ToIndex();
  580. var adj = tile.AdjacentTiles[idx];
  581. if (adj == null)
  582. {
  583. var nonNull = tile.AdjacentTiles.Where(x => x != null).Count();
  584. Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: ({tile.GridIndex}, {tile.GridIndices}), non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
  585. return;
  586. }
  587. tile.MonstermosInfo[direction] += amount;
  588. adj.MonstermosInfo[idx.ToOppositeDir()] -= amount;
  589. }
  590. private void HandleDecompressionFloorRip(MapGridComponent mapGrid, TileAtmosphere tile, float sum)
  591. {
  592. if (!MonstermosRipTiles)
  593. return;
  594. var chance = MathHelper.Clamp(0.01f + (sum / SpacingMaxWind) * 0.3f, 0.003f, 0.3f);
  595. if (sum > 20 && _random.Prob(chance))
  596. PryTile(mapGrid, tile.GridIndices);
  597. }
  598. private sealed class TileAtmosphereComparer : IComparer<TileAtmosphere?>
  599. {
  600. public int Compare(TileAtmosphere? a, TileAtmosphere? b)
  601. {
  602. if (a == null && b == null)
  603. return 0;
  604. if (a == null)
  605. return -1;
  606. if (b == null)
  607. return 1;
  608. return a.MonstermosInfo.MoleDelta.CompareTo(b.MonstermosInfo.MoleDelta);
  609. }
  610. }
  611. }
  612. }