AtmosphereSystem.LINDA.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. using Content.Server.Atmos.Components;
  2. using Content.Shared.Atmos;
  3. using Content.Shared.Atmos.Components;
  4. using Robust.Shared.Map.Components;
  5. using Robust.Shared.Utility;
  6. namespace Content.Server.Atmos.EntitySystems
  7. {
  8. public sealed partial class AtmosphereSystem
  9. {
  10. private void ProcessCell(
  11. Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
  12. TileAtmosphere tile, int fireCount)
  13. {
  14. var gridAtmosphere = ent.Comp1;
  15. // Can't process a tile without air
  16. if (tile.Air == null)
  17. {
  18. RemoveActiveTile(gridAtmosphere, tile);
  19. return;
  20. }
  21. if (tile.ArchivedCycle < fireCount)
  22. Archive(tile, fireCount);
  23. tile.CurrentCycle = fireCount;
  24. var adjacentTileLength = 0;
  25. for (var i = 0; i < Atmospherics.Directions; i++)
  26. {
  27. var direction = (AtmosDirection) (1 << i);
  28. if(tile.AdjacentBits.IsFlagSet(direction))
  29. adjacentTileLength++;
  30. }
  31. for(var i = 0; i < Atmospherics.Directions; i++)
  32. {
  33. var direction = (AtmosDirection) (1 << i);
  34. if (!tile.AdjacentBits.IsFlagSet(direction)) continue;
  35. var enemyTile = tile.AdjacentTiles[i];
  36. // If the tile is null or has no air, we don't do anything for it.
  37. if(enemyTile?.Air == null) continue;
  38. if (fireCount <= enemyTile.CurrentCycle) continue;
  39. Archive(enemyTile, fireCount);
  40. var shouldShareAir = false;
  41. if (ExcitedGroups && tile.ExcitedGroup != null && enemyTile.ExcitedGroup != null)
  42. {
  43. if (tile.ExcitedGroup != enemyTile.ExcitedGroup)
  44. {
  45. ExcitedGroupMerge(gridAtmosphere, tile.ExcitedGroup, enemyTile.ExcitedGroup);
  46. }
  47. shouldShareAir = true;
  48. } else if (CompareExchange(tile, enemyTile) != GasCompareResult.NoExchange)
  49. {
  50. AddActiveTile(gridAtmosphere, enemyTile);
  51. if (ExcitedGroups)
  52. {
  53. var excitedGroup = tile.ExcitedGroup;
  54. excitedGroup ??= enemyTile.ExcitedGroup;
  55. if (excitedGroup == null)
  56. {
  57. excitedGroup = new ExcitedGroup();
  58. gridAtmosphere.ExcitedGroups.Add(excitedGroup);
  59. }
  60. if (tile.ExcitedGroup == null)
  61. ExcitedGroupAddTile(excitedGroup, tile);
  62. if(enemyTile.ExcitedGroup == null)
  63. ExcitedGroupAddTile(excitedGroup, enemyTile);
  64. }
  65. shouldShareAir = true;
  66. }
  67. if (shouldShareAir)
  68. {
  69. var difference = Share(tile, enemyTile, adjacentTileLength);
  70. // Monstermos already handles this, so let's not handle it ourselves.
  71. if (!MonstermosEqualization)
  72. {
  73. if (difference >= 0)
  74. {
  75. ConsiderPressureDifference(gridAtmosphere, tile, direction, difference);
  76. }
  77. else
  78. {
  79. ConsiderPressureDifference(gridAtmosphere, enemyTile, i.ToOppositeDir(), -difference);
  80. }
  81. }
  82. LastShareCheck(tile);
  83. }
  84. }
  85. if(tile.Air != null)
  86. React(tile.Air, tile);
  87. InvalidateVisuals(ent, tile);
  88. var remove = true;
  89. if(tile.Air!.Temperature > Atmospherics.MinimumTemperatureStartSuperConduction)
  90. if (ConsiderSuperconductivity(gridAtmosphere, tile, true))
  91. remove = false;
  92. if(ExcitedGroups && tile.ExcitedGroup == null && remove)
  93. RemoveActiveTile(gridAtmosphere, tile);
  94. }
  95. private void Archive(TileAtmosphere tile, int fireCount)
  96. {
  97. if (tile.Air != null)
  98. tile.AirArchived = new GasMixture(tile.Air);
  99. tile.ArchivedCycle = fireCount;
  100. }
  101. private void LastShareCheck(TileAtmosphere tile)
  102. {
  103. if (tile.Air == null || tile.ExcitedGroup == null)
  104. return;
  105. switch (tile.LastShare)
  106. {
  107. case > Atmospherics.MinimumAirToSuspend:
  108. ExcitedGroupResetCooldowns(tile.ExcitedGroup);
  109. break;
  110. case > Atmospherics.MinimumMolesDeltaToMove:
  111. tile.ExcitedGroup.DismantleCooldown = 0;
  112. break;
  113. }
  114. }
  115. /// <summary>
  116. /// Makes a tile become active and start processing. Does NOT check if the tile belongs to the grid atmos.
  117. /// </summary>
  118. /// <param name="gridAtmosphere">Grid Atmosphere where to get the tile.</param>
  119. /// <param name="tile">Tile Atmosphere to be activated.</param>
  120. private void AddActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile)
  121. {
  122. if (tile.Air == null || tile.Excited)
  123. return;
  124. tile.Excited = true;
  125. gridAtmosphere.ActiveTiles.Add(tile);
  126. }
  127. /// <summary>
  128. /// Makes a tile become inactive and stop processing.
  129. /// </summary>
  130. /// <param name="gridAtmosphere">Grid Atmosphere where to get the tile.</param>
  131. /// <param name="tile">Tile Atmosphere to be deactivated.</param>
  132. /// <param name="disposeExcitedGroup">Whether to dispose of the tile's <see cref="ExcitedGroup"/></param>
  133. private void RemoveActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, bool disposeExcitedGroup = true)
  134. {
  135. DebugTools.Assert(tile.Excited == gridAtmosphere.ActiveTiles.Contains(tile));
  136. DebugTools.Assert(tile.Excited || tile.ExcitedGroup == null);
  137. if (!tile.Excited)
  138. return;
  139. tile.Excited = false;
  140. gridAtmosphere.ActiveTiles.Remove(tile);
  141. if (tile.ExcitedGroup == null)
  142. return;
  143. if (disposeExcitedGroup)
  144. ExcitedGroupDispose(gridAtmosphere, tile.ExcitedGroup);
  145. else
  146. ExcitedGroupRemoveTile(tile.ExcitedGroup, tile);
  147. }
  148. /// <summary>
  149. /// Calculates the heat capacity for a gas mixture, using the archived values.
  150. /// </summary>
  151. public float GetHeatCapacityArchived(TileAtmosphere tile)
  152. {
  153. if (tile.AirArchived == null)
  154. return tile.HeatCapacity;
  155. return GetHeatCapacity(tile.AirArchived);
  156. }
  157. /// <summary>
  158. /// Shares gas between two tiles. Part of LINDA.
  159. /// </summary>
  160. public float Share(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, int atmosAdjacentTurfs)
  161. {
  162. if (tileReceiver.Air is not {} receiver || tileSharer.Air is not {} sharer ||
  163. tileReceiver.AirArchived == null || tileSharer.AirArchived == null)
  164. return 0f;
  165. var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature;
  166. var absTemperatureDelta = Math.Abs(temperatureDelta);
  167. var oldHeatCapacity = 0f;
  168. var oldSharerHeatCapacity = 0f;
  169. if (absTemperatureDelta > Atmospherics.MinimumTemperatureDeltaToConsider)
  170. {
  171. oldHeatCapacity = GetHeatCapacity(receiver);
  172. oldSharerHeatCapacity = GetHeatCapacity(sharer);
  173. }
  174. var heatCapacityToSharer = 0f;
  175. var heatCapacitySharerToThis = 0f;
  176. var movedMoles = 0f;
  177. var absMovedMoles = 0f;
  178. for(var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
  179. {
  180. var thisValue = receiver.Moles[i];
  181. var sharerValue = sharer.Moles[i];
  182. var delta = (thisValue - sharerValue) / (atmosAdjacentTurfs + 1);
  183. if (!(MathF.Abs(delta) >= Atmospherics.GasMinMoles)) continue;
  184. if (absTemperatureDelta > Atmospherics.MinimumTemperatureDeltaToConsider)
  185. {
  186. var gasHeatCapacity = delta * GasSpecificHeats[i];
  187. if (delta > 0)
  188. {
  189. heatCapacityToSharer += gasHeatCapacity;
  190. }
  191. else
  192. {
  193. heatCapacitySharerToThis -= gasHeatCapacity;
  194. }
  195. }
  196. if (!receiver.Immutable) receiver.Moles[i] -= delta;
  197. if (!sharer.Immutable) sharer.Moles[i] += delta;
  198. movedMoles += delta;
  199. absMovedMoles += MathF.Abs(delta);
  200. }
  201. tileReceiver.LastShare = absMovedMoles;
  202. if (absTemperatureDelta > Atmospherics.MinimumTemperatureDeltaToConsider)
  203. {
  204. var newHeatCapacity = oldHeatCapacity + heatCapacitySharerToThis - heatCapacityToSharer;
  205. var newSharerHeatCapacity = oldSharerHeatCapacity + heatCapacityToSharer - heatCapacitySharerToThis;
  206. // Transfer of thermal energy (via changed heat capacity) between self and sharer.
  207. if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity)
  208. {
  209. receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * tileReceiver.AirArchived.Temperature) + (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature)) / newHeatCapacity;
  210. }
  211. if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity)
  212. {
  213. sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature) + (heatCapacityToSharer * tileReceiver.AirArchived.Temperature)) / newSharerHeatCapacity;
  214. }
  215. // Thermal energy of the system (self and sharer) is unchanged.
  216. if (MathF.Abs(oldSharerHeatCapacity) > Atmospherics.MinimumHeatCapacity)
  217. {
  218. if (MathF.Abs(newSharerHeatCapacity / oldSharerHeatCapacity - 1) < 0.1)
  219. {
  220. TemperatureShare(tileReceiver, tileSharer, Atmospherics.OpenHeatTransferCoefficient);
  221. }
  222. }
  223. }
  224. if (!(temperatureDelta > Atmospherics.MinimumTemperatureToMove) &&
  225. !(MathF.Abs(movedMoles) > Atmospherics.MinimumMolesDeltaToMove)) return 0f;
  226. var moles = receiver.TotalMoles;
  227. var theirMoles = sharer.TotalMoles;
  228. return (tileReceiver.AirArchived.Temperature * (moles + movedMoles)) - (tileSharer.AirArchived.Temperature * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume;
  229. }
  230. /// <summary>
  231. /// Shares temperature between two mixtures, taking a conduction coefficient into account.
  232. /// </summary>
  233. public float TemperatureShare(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, float conductionCoefficient)
  234. {
  235. if (tileReceiver.Air is not { } receiver || tileSharer.Air is not { } sharer ||
  236. tileReceiver.AirArchived == null || tileSharer.AirArchived == null)
  237. return 0f;
  238. var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature;
  239. if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
  240. {
  241. var heatCapacity = GetHeatCapacityArchived(tileReceiver);
  242. var sharerHeatCapacity = GetHeatCapacityArchived(tileSharer);
  243. if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity)
  244. {
  245. var heat = conductionCoefficient * temperatureDelta * (heatCapacity * sharerHeatCapacity / (heatCapacity + sharerHeatCapacity));
  246. if (!receiver.Immutable)
  247. receiver.Temperature = MathF.Abs(MathF.Max(receiver.Temperature - heat / heatCapacity, Atmospherics.TCMB));
  248. if (!sharer.Immutable)
  249. sharer.Temperature = MathF.Abs(MathF.Max(sharer.Temperature + heat / sharerHeatCapacity, Atmospherics.TCMB));
  250. }
  251. }
  252. return sharer.Temperature;
  253. }
  254. /// <summary>
  255. /// Shares temperature between a gas mixture and an abstract sharer, taking a conduction coefficient into account.
  256. /// </summary>
  257. public float TemperatureShare(TileAtmosphere tileReceiver, float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity)
  258. {
  259. if (tileReceiver.Air is not {} receiver || tileReceiver.AirArchived == null)
  260. return 0;
  261. var temperatureDelta = tileReceiver.AirArchived.Temperature - sharerTemperature;
  262. if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
  263. {
  264. var heatCapacity = GetHeatCapacityArchived(tileReceiver);
  265. if (sharerHeatCapacity > Atmospherics.MinimumHeatCapacity && heatCapacity > Atmospherics.MinimumHeatCapacity)
  266. {
  267. var heat = conductionCoefficient * temperatureDelta * (heatCapacity * sharerHeatCapacity / (heatCapacity + sharerHeatCapacity));
  268. if (!receiver.Immutable)
  269. receiver.Temperature = MathF.Abs(MathF.Max(receiver.Temperature - heat / heatCapacity, Atmospherics.TCMB));
  270. sharerTemperature = MathF.Abs(MathF.Max(sharerTemperature + heat / sharerHeatCapacity, Atmospherics.TCMB));
  271. }
  272. }
  273. return sharerTemperature;
  274. }
  275. }
  276. }