AtmosphereSystem.Gases.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. using System.Linq;
  2. using System.Runtime.CompilerServices;
  3. using Content.Server.Atmos.Reactions;
  4. using Content.Shared.Atmos;
  5. using Content.Shared.Atmos.Reactions;
  6. using Robust.Shared.Prototypes;
  7. using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
  8. namespace Content.Server.Atmos.EntitySystems
  9. {
  10. public sealed partial class AtmosphereSystem
  11. {
  12. [Dependency] private readonly IPrototypeManager _protoMan = default!;
  13. private GasReactionPrototype[] _gasReactions = Array.Empty<GasReactionPrototype>();
  14. private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
  15. /// <summary>
  16. /// List of gas reactions ordered by priority.
  17. /// </summary>
  18. public IEnumerable<GasReactionPrototype> GasReactions => _gasReactions;
  19. /// <summary>
  20. /// Cached array of gas specific heats.
  21. /// </summary>
  22. public float[] GasSpecificHeats => _gasSpecificHeats;
  23. public string?[] GasReagents = new string[Atmospherics.TotalNumberOfGases];
  24. private void InitializeGases()
  25. {
  26. _gasReactions = _protoMan.EnumeratePrototypes<GasReactionPrototype>().ToArray();
  27. Array.Sort(_gasReactions, (a, b) => b.Priority.CompareTo(a.Priority));
  28. Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4));
  29. for (var i = 0; i < GasPrototypes.Length; i++)
  30. {
  31. _gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat / HeatScale;
  32. GasReagents[i] = GasPrototypes[i].Reagent;
  33. }
  34. }
  35. /// <summary>
  36. /// Calculates the heat capacity for a gas mixture.
  37. /// </summary>
  38. /// <param name="mixture">The mixture whose heat capacity should be calculated</param>
  39. /// <param name="applyScaling"> Whether the internal heat capacity scaling should be applied. This should not be
  40. /// used outside of atmospheric related heat transfer.</param>
  41. /// <returns></returns>
  42. public float GetHeatCapacity(GasMixture mixture, bool applyScaling)
  43. {
  44. var scale = GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
  45. // By default GetHeatCapacityCalculation() has the heat-scale divisor pre-applied.
  46. // So if we want the un-scaled heat capacity, we have to multiply by the scale.
  47. return applyScaling ? scale : scale * HeatScale;
  48. }
  49. private float GetHeatCapacity(GasMixture mixture)
  50. => GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
  51. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  52. private float GetHeatCapacityCalculation(float[] moles, bool space)
  53. {
  54. // Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
  55. if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
  56. {
  57. return Atmospherics.SpaceHeatCapacity;
  58. }
  59. Span<float> tmp = stackalloc float[moles.Length];
  60. NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
  61. // Adjust heat capacity by speedup, because this is primarily what
  62. // determines how quickly gases heat up/cool.
  63. return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
  64. }
  65. /// <summary>
  66. /// Return speedup factor for pumped or flow-based devices that depend on MaxTransferRate.
  67. /// </summary>
  68. public float PumpSpeedup()
  69. {
  70. return Speedup;
  71. }
  72. /// <summary>
  73. /// Calculates the thermal energy for a gas mixture.
  74. /// </summary>
  75. public float GetThermalEnergy(GasMixture mixture)
  76. {
  77. return mixture.Temperature * GetHeatCapacity(mixture);
  78. }
  79. /// <summary>
  80. /// Calculates the thermal energy for a gas mixture, using a cached heat capacity value.
  81. /// </summary>
  82. public float GetThermalEnergy(GasMixture mixture, float cachedHeatCapacity)
  83. {
  84. return mixture.Temperature * cachedHeatCapacity;
  85. }
  86. /// <summary>
  87. /// Add 'dQ' Joules of energy into 'mixture'.
  88. /// </summary>
  89. public void AddHeat(GasMixture mixture, float dQ)
  90. {
  91. var c = GetHeatCapacity(mixture);
  92. float dT = dQ / c;
  93. mixture.Temperature += dT;
  94. }
  95. /// <summary>
  96. /// Merges the <see cref="giver"/> gas mixture into the <see cref="receiver"/> gas mixture.
  97. /// The <see cref="giver"/> gas mixture is not modified by this method.
  98. /// </summary>
  99. public void Merge(GasMixture receiver, GasMixture giver)
  100. {
  101. if (receiver.Immutable) return;
  102. if (MathF.Abs(receiver.Temperature - giver.Temperature) > Atmospherics.MinimumTemperatureDeltaToConsider)
  103. {
  104. var receiverHeatCapacity = GetHeatCapacity(receiver);
  105. var giverHeatCapacity = GetHeatCapacity(giver);
  106. var combinedHeatCapacity = receiverHeatCapacity + giverHeatCapacity;
  107. if (combinedHeatCapacity > Atmospherics.MinimumHeatCapacity)
  108. {
  109. receiver.Temperature = (GetThermalEnergy(giver, giverHeatCapacity) + GetThermalEnergy(receiver, receiverHeatCapacity)) / combinedHeatCapacity;
  110. }
  111. }
  112. NumericsHelpers.Add(receiver.Moles, giver.Moles);
  113. }
  114. /// <summary>
  115. /// Divides a source gas mixture into several recipient mixtures, scaled by their relative volumes. Does not
  116. /// modify the source gas mixture. Used for pipe network splitting. Note that the total destination volume
  117. /// may be larger or smaller than the source mixture.
  118. /// </summary>
  119. public void DivideInto(GasMixture source, List<GasMixture> receivers)
  120. {
  121. var totalVolume = 0f;
  122. foreach (var receiver in receivers)
  123. {
  124. if (!receiver.Immutable)
  125. totalVolume += receiver.Volume;
  126. }
  127. float? sourceHeatCapacity = null;
  128. var buffer = new float[Atmospherics.AdjustedNumberOfGases];
  129. foreach (var receiver in receivers)
  130. {
  131. if (receiver.Immutable)
  132. continue;
  133. var fraction = receiver.Volume / totalVolume;
  134. // Set temperature, if necessary.
  135. if (MathF.Abs(receiver.Temperature - source.Temperature) > Atmospherics.MinimumTemperatureDeltaToConsider)
  136. {
  137. // Often this divides a pipe net into new and completely empty pipe nets
  138. if (receiver.TotalMoles == 0)
  139. receiver.Temperature = source.Temperature;
  140. else
  141. {
  142. sourceHeatCapacity ??= GetHeatCapacity(source);
  143. var receiverHeatCapacity = GetHeatCapacity(receiver);
  144. var combinedHeatCapacity = receiverHeatCapacity + sourceHeatCapacity.Value * fraction;
  145. if (combinedHeatCapacity > Atmospherics.MinimumHeatCapacity)
  146. receiver.Temperature = (GetThermalEnergy(source, sourceHeatCapacity.Value * fraction) + GetThermalEnergy(receiver, receiverHeatCapacity)) / combinedHeatCapacity;
  147. }
  148. }
  149. // transfer moles
  150. NumericsHelpers.Multiply(source.Moles, fraction, buffer);
  151. NumericsHelpers.Add(receiver.Moles, buffer);
  152. }
  153. }
  154. /// <summary>
  155. /// Releases gas from this mixture to the output mixture.
  156. /// If the output mixture is null, then this is being released into space.
  157. /// It can't transfer air to a mixture with higher pressure.
  158. /// </summary>
  159. public bool ReleaseGasTo(GasMixture mixture, GasMixture? output, float targetPressure)
  160. {
  161. var outputStartingPressure = output?.Pressure ?? 0;
  162. var inputStartingPressure = mixture.Pressure;
  163. if (outputStartingPressure >= MathF.Min(targetPressure, inputStartingPressure - 10))
  164. // No need to pump gas if the target is already reached or input pressure is too low.
  165. // Need at least 10 kPa difference to overcome friction in the mechanism.
  166. return false;
  167. if (!(mixture.TotalMoles > 0) || !(mixture.Temperature > 0)) return false;
  168. // We calculate the necessary moles to transfer with the ideal gas law.
  169. var pressureDelta = MathF.Min(targetPressure - outputStartingPressure, (inputStartingPressure - outputStartingPressure) / 2f);
  170. var transferMoles = pressureDelta * (output?.Volume ?? Atmospherics.CellVolume) / (mixture.Temperature * Atmospherics.R);
  171. // And now we transfer the gas.
  172. var removed = mixture.Remove(transferMoles);
  173. if(output != null)
  174. Merge(output, removed);
  175. return true;
  176. }
  177. /// <summary>
  178. /// Pump gas from this mixture to the output mixture.
  179. /// Amount depends on target pressure.
  180. /// </summary>
  181. /// <param name="mixture">The mixture to pump the gas from</param>
  182. /// <param name="output">The mixture to pump the gas to</param>
  183. /// <param name="targetPressure">The target pressure to reach</param>
  184. /// <returns>Whether we could pump air to the output or not</returns>
  185. public bool PumpGasTo(GasMixture mixture, GasMixture output, float targetPressure)
  186. {
  187. var outputStartingPressure = output.Pressure;
  188. var pressureDelta = targetPressure - outputStartingPressure;
  189. if (pressureDelta < 0.01)
  190. // No need to pump gas, we've reached the target.
  191. return false;
  192. if (!(mixture.TotalMoles > 0) || !(mixture.Temperature > 0)) return false;
  193. // We calculate the necessary moles to transfer with the ideal gas law.
  194. var transferMoles = pressureDelta * output.Volume / (mixture.Temperature * Atmospherics.R);
  195. // And now we transfer the gas.
  196. var removed = mixture.Remove(transferMoles);
  197. Merge(output, removed);
  198. return true;
  199. }
  200. /// <summary>
  201. /// Scrubs specified gases from a gas mixture into a <see cref="destination"/> gas mixture.
  202. /// </summary>
  203. public void ScrubInto(GasMixture mixture, GasMixture destination, IReadOnlyCollection<Gas> filterGases)
  204. {
  205. var buffer = new GasMixture(mixture.Volume){Temperature = mixture.Temperature};
  206. foreach (var gas in filterGases)
  207. {
  208. buffer.AdjustMoles(gas, mixture.GetMoles(gas));
  209. mixture.SetMoles(gas, 0f);
  210. }
  211. Merge(destination, buffer);
  212. }
  213. /// <summary>
  214. /// Checks whether a gas mixture is probably safe.
  215. /// This only checks temperature and pressure, not gas composition.
  216. /// </summary>
  217. /// <param name="air">Mixture to be checked.</param>
  218. /// <returns>Whether the mixture is probably safe.</returns>
  219. public bool IsMixtureProbablySafe(GasMixture? air)
  220. {
  221. // Note that oxygen mix isn't checked, but survival boxes make that not necessary.
  222. if (air == null)
  223. return false;
  224. switch (air.Pressure)
  225. {
  226. case <= Atmospherics.WarningLowPressure:
  227. case >= Atmospherics.WarningHighPressure:
  228. return false;
  229. }
  230. switch (air.Temperature)
  231. {
  232. case <= 260:
  233. case >= 360:
  234. return false;
  235. }
  236. return true;
  237. }
  238. /// <summary>
  239. /// Compares two TileAtmospheres to see if they are within acceptable ranges for group processing to be enabled.
  240. /// </summary>
  241. public GasCompareResult CompareExchange(TileAtmosphere sample, TileAtmosphere otherSample)
  242. {
  243. if (sample.AirArchived == null || otherSample.AirArchived == null)
  244. return GasCompareResult.NoExchange;
  245. return CompareExchange(sample.AirArchived, otherSample.AirArchived);
  246. }
  247. /// <summary>
  248. /// Compares two gas mixtures to see if they are within acceptable ranges for group processing to be enabled.
  249. /// </summary>
  250. public GasCompareResult CompareExchange(GasMixture sample, GasMixture otherSample)
  251. {
  252. var moles = 0f;
  253. for(var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
  254. {
  255. var gasMoles = sample.Moles[i];
  256. var delta = MathF.Abs(gasMoles - otherSample.Moles[i]);
  257. if (delta > Atmospherics.MinimumMolesDeltaToMove && (delta > gasMoles * Atmospherics.MinimumAirRatioToMove))
  258. return (GasCompareResult)i; // We can move gases!
  259. moles += gasMoles;
  260. }
  261. if (moles > Atmospherics.MinimumMolesDeltaToMove)
  262. {
  263. var tempDelta = MathF.Abs(sample.Temperature - otherSample.Temperature);
  264. if (tempDelta > Atmospherics.MinimumTemperatureDeltaToSuspend)
  265. return GasCompareResult.TemperatureExchange; // There can be temperature exchange.
  266. }
  267. // No exchange at all!
  268. return GasCompareResult.NoExchange;
  269. }
  270. /// <summary>
  271. /// Performs reactions for a given gas mixture on an optional holder.
  272. /// </summary>
  273. public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder)
  274. {
  275. var reaction = ReactionResult.NoReaction;
  276. var temperature = mixture.Temperature;
  277. var energy = GetThermalEnergy(mixture);
  278. foreach (var prototype in GasReactions)
  279. {
  280. if (energy < prototype.MinimumEnergyRequirement ||
  281. temperature < prototype.MinimumTemperatureRequirement ||
  282. temperature > prototype.MaximumTemperatureRequirement)
  283. continue;
  284. var doReaction = true;
  285. for (var i = 0; i < prototype.MinimumRequirements.Length; i++)
  286. {
  287. if(i >= Atmospherics.TotalNumberOfGases)
  288. throw new IndexOutOfRangeException("Reaction Gas Minimum Requirements Array Prototype exceeds total number of gases!");
  289. var req = prototype.MinimumRequirements[i];
  290. if (!(mixture.GetMoles(i) < req))
  291. continue;
  292. doReaction = false;
  293. break;
  294. }
  295. if (!doReaction)
  296. continue;
  297. reaction = prototype.React(mixture, holder, this, HeatScale);
  298. if(reaction.HasFlag(ReactionResult.StopReactions))
  299. break;
  300. }
  301. return reaction;
  302. }
  303. public enum GasCompareResult
  304. {
  305. NoExchange = -2,
  306. TemperatureExchange = -1,
  307. }
  308. }
  309. }