| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- using System.Linq;
- using System.Runtime.CompilerServices;
- using Content.Server.Atmos.Reactions;
- using Content.Shared.Atmos;
- using Content.Shared.Atmos.Reactions;
- using Robust.Shared.Prototypes;
- using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
- namespace Content.Server.Atmos.EntitySystems
- {
- public sealed partial class AtmosphereSystem
- {
- [Dependency] private readonly IPrototypeManager _protoMan = default!;
- private GasReactionPrototype[] _gasReactions = Array.Empty<GasReactionPrototype>();
- private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
- /// <summary>
- /// List of gas reactions ordered by priority.
- /// </summary>
- public IEnumerable<GasReactionPrototype> GasReactions => _gasReactions;
- /// <summary>
- /// Cached array of gas specific heats.
- /// </summary>
- public float[] GasSpecificHeats => _gasSpecificHeats;
- public string?[] GasReagents = new string[Atmospherics.TotalNumberOfGases];
- private void InitializeGases()
- {
- _gasReactions = _protoMan.EnumeratePrototypes<GasReactionPrototype>().ToArray();
- Array.Sort(_gasReactions, (a, b) => b.Priority.CompareTo(a.Priority));
- Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4));
- for (var i = 0; i < GasPrototypes.Length; i++)
- {
- _gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat / HeatScale;
- GasReagents[i] = GasPrototypes[i].Reagent;
- }
- }
- /// <summary>
- /// Calculates the heat capacity for a gas mixture.
- /// </summary>
- /// <param name="mixture">The mixture whose heat capacity should be calculated</param>
- /// <param name="applyScaling"> Whether the internal heat capacity scaling should be applied. This should not be
- /// used outside of atmospheric related heat transfer.</param>
- /// <returns></returns>
- public float GetHeatCapacity(GasMixture mixture, bool applyScaling)
- {
- var scale = GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
- // By default GetHeatCapacityCalculation() has the heat-scale divisor pre-applied.
- // So if we want the un-scaled heat capacity, we have to multiply by the scale.
- return applyScaling ? scale : scale * HeatScale;
- }
- private float GetHeatCapacity(GasMixture mixture)
- => GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private float GetHeatCapacityCalculation(float[] moles, bool space)
- {
- // Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
- if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
- {
- return Atmospherics.SpaceHeatCapacity;
- }
- Span<float> tmp = stackalloc float[moles.Length];
- NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
- // Adjust heat capacity by speedup, because this is primarily what
- // determines how quickly gases heat up/cool.
- return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
- }
- /// <summary>
- /// Return speedup factor for pumped or flow-based devices that depend on MaxTransferRate.
- /// </summary>
- public float PumpSpeedup()
- {
- return Speedup;
- }
- /// <summary>
- /// Calculates the thermal energy for a gas mixture.
- /// </summary>
- public float GetThermalEnergy(GasMixture mixture)
- {
- return mixture.Temperature * GetHeatCapacity(mixture);
- }
- /// <summary>
- /// Calculates the thermal energy for a gas mixture, using a cached heat capacity value.
- /// </summary>
- public float GetThermalEnergy(GasMixture mixture, float cachedHeatCapacity)
- {
- return mixture.Temperature * cachedHeatCapacity;
- }
- /// <summary>
- /// Add 'dQ' Joules of energy into 'mixture'.
- /// </summary>
- public void AddHeat(GasMixture mixture, float dQ)
- {
- var c = GetHeatCapacity(mixture);
- float dT = dQ / c;
- mixture.Temperature += dT;
- }
- /// <summary>
- /// Merges the <see cref="giver"/> gas mixture into the <see cref="receiver"/> gas mixture.
- /// The <see cref="giver"/> gas mixture is not modified by this method.
- /// </summary>
- public void Merge(GasMixture receiver, GasMixture giver)
- {
- if (receiver.Immutable) return;
- if (MathF.Abs(receiver.Temperature - giver.Temperature) > Atmospherics.MinimumTemperatureDeltaToConsider)
- {
- var receiverHeatCapacity = GetHeatCapacity(receiver);
- var giverHeatCapacity = GetHeatCapacity(giver);
- var combinedHeatCapacity = receiverHeatCapacity + giverHeatCapacity;
- if (combinedHeatCapacity > Atmospherics.MinimumHeatCapacity)
- {
- receiver.Temperature = (GetThermalEnergy(giver, giverHeatCapacity) + GetThermalEnergy(receiver, receiverHeatCapacity)) / combinedHeatCapacity;
- }
- }
- NumericsHelpers.Add(receiver.Moles, giver.Moles);
- }
- /// <summary>
- /// Divides a source gas mixture into several recipient mixtures, scaled by their relative volumes. Does not
- /// modify the source gas mixture. Used for pipe network splitting. Note that the total destination volume
- /// may be larger or smaller than the source mixture.
- /// </summary>
- public void DivideInto(GasMixture source, List<GasMixture> receivers)
- {
- var totalVolume = 0f;
- foreach (var receiver in receivers)
- {
- if (!receiver.Immutable)
- totalVolume += receiver.Volume;
- }
- float? sourceHeatCapacity = null;
- var buffer = new float[Atmospherics.AdjustedNumberOfGases];
- foreach (var receiver in receivers)
- {
- if (receiver.Immutable)
- continue;
- var fraction = receiver.Volume / totalVolume;
- // Set temperature, if necessary.
- if (MathF.Abs(receiver.Temperature - source.Temperature) > Atmospherics.MinimumTemperatureDeltaToConsider)
- {
- // Often this divides a pipe net into new and completely empty pipe nets
- if (receiver.TotalMoles == 0)
- receiver.Temperature = source.Temperature;
- else
- {
- sourceHeatCapacity ??= GetHeatCapacity(source);
- var receiverHeatCapacity = GetHeatCapacity(receiver);
- var combinedHeatCapacity = receiverHeatCapacity + sourceHeatCapacity.Value * fraction;
- if (combinedHeatCapacity > Atmospherics.MinimumHeatCapacity)
- receiver.Temperature = (GetThermalEnergy(source, sourceHeatCapacity.Value * fraction) + GetThermalEnergy(receiver, receiverHeatCapacity)) / combinedHeatCapacity;
- }
- }
- // transfer moles
- NumericsHelpers.Multiply(source.Moles, fraction, buffer);
- NumericsHelpers.Add(receiver.Moles, buffer);
- }
- }
- /// <summary>
- /// Releases gas from this mixture to the output mixture.
- /// If the output mixture is null, then this is being released into space.
- /// It can't transfer air to a mixture with higher pressure.
- /// </summary>
- public bool ReleaseGasTo(GasMixture mixture, GasMixture? output, float targetPressure)
- {
- var outputStartingPressure = output?.Pressure ?? 0;
- var inputStartingPressure = mixture.Pressure;
- if (outputStartingPressure >= MathF.Min(targetPressure, inputStartingPressure - 10))
- // No need to pump gas if the target is already reached or input pressure is too low.
- // Need at least 10 kPa difference to overcome friction in the mechanism.
- return false;
- if (!(mixture.TotalMoles > 0) || !(mixture.Temperature > 0)) return false;
- // We calculate the necessary moles to transfer with the ideal gas law.
- var pressureDelta = MathF.Min(targetPressure - outputStartingPressure, (inputStartingPressure - outputStartingPressure) / 2f);
- var transferMoles = pressureDelta * (output?.Volume ?? Atmospherics.CellVolume) / (mixture.Temperature * Atmospherics.R);
- // And now we transfer the gas.
- var removed = mixture.Remove(transferMoles);
- if(output != null)
- Merge(output, removed);
- return true;
- }
- /// <summary>
- /// Pump gas from this mixture to the output mixture.
- /// Amount depends on target pressure.
- /// </summary>
- /// <param name="mixture">The mixture to pump the gas from</param>
- /// <param name="output">The mixture to pump the gas to</param>
- /// <param name="targetPressure">The target pressure to reach</param>
- /// <returns>Whether we could pump air to the output or not</returns>
- public bool PumpGasTo(GasMixture mixture, GasMixture output, float targetPressure)
- {
- var outputStartingPressure = output.Pressure;
- var pressureDelta = targetPressure - outputStartingPressure;
- if (pressureDelta < 0.01)
- // No need to pump gas, we've reached the target.
- return false;
- if (!(mixture.TotalMoles > 0) || !(mixture.Temperature > 0)) return false;
- // We calculate the necessary moles to transfer with the ideal gas law.
- var transferMoles = pressureDelta * output.Volume / (mixture.Temperature * Atmospherics.R);
- // And now we transfer the gas.
- var removed = mixture.Remove(transferMoles);
- Merge(output, removed);
- return true;
- }
- /// <summary>
- /// Scrubs specified gases from a gas mixture into a <see cref="destination"/> gas mixture.
- /// </summary>
- public void ScrubInto(GasMixture mixture, GasMixture destination, IReadOnlyCollection<Gas> filterGases)
- {
- var buffer = new GasMixture(mixture.Volume){Temperature = mixture.Temperature};
- foreach (var gas in filterGases)
- {
- buffer.AdjustMoles(gas, mixture.GetMoles(gas));
- mixture.SetMoles(gas, 0f);
- }
- Merge(destination, buffer);
- }
- /// <summary>
- /// Checks whether a gas mixture is probably safe.
- /// This only checks temperature and pressure, not gas composition.
- /// </summary>
- /// <param name="air">Mixture to be checked.</param>
- /// <returns>Whether the mixture is probably safe.</returns>
- public bool IsMixtureProbablySafe(GasMixture? air)
- {
- // Note that oxygen mix isn't checked, but survival boxes make that not necessary.
- if (air == null)
- return false;
- switch (air.Pressure)
- {
- case <= Atmospherics.WarningLowPressure:
- case >= Atmospherics.WarningHighPressure:
- return false;
- }
- switch (air.Temperature)
- {
- case <= 260:
- case >= 360:
- return false;
- }
- return true;
- }
- /// <summary>
- /// Compares two TileAtmospheres to see if they are within acceptable ranges for group processing to be enabled.
- /// </summary>
- public GasCompareResult CompareExchange(TileAtmosphere sample, TileAtmosphere otherSample)
- {
- if (sample.AirArchived == null || otherSample.AirArchived == null)
- return GasCompareResult.NoExchange;
- return CompareExchange(sample.AirArchived, otherSample.AirArchived);
- }
- /// <summary>
- /// Compares two gas mixtures to see if they are within acceptable ranges for group processing to be enabled.
- /// </summary>
- public GasCompareResult CompareExchange(GasMixture sample, GasMixture otherSample)
- {
- var moles = 0f;
- for(var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
- {
- var gasMoles = sample.Moles[i];
- var delta = MathF.Abs(gasMoles - otherSample.Moles[i]);
- if (delta > Atmospherics.MinimumMolesDeltaToMove && (delta > gasMoles * Atmospherics.MinimumAirRatioToMove))
- return (GasCompareResult)i; // We can move gases!
- moles += gasMoles;
- }
- if (moles > Atmospherics.MinimumMolesDeltaToMove)
- {
- var tempDelta = MathF.Abs(sample.Temperature - otherSample.Temperature);
- if (tempDelta > Atmospherics.MinimumTemperatureDeltaToSuspend)
- return GasCompareResult.TemperatureExchange; // There can be temperature exchange.
- }
- // No exchange at all!
- return GasCompareResult.NoExchange;
- }
- /// <summary>
- /// Performs reactions for a given gas mixture on an optional holder.
- /// </summary>
- public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder)
- {
- var reaction = ReactionResult.NoReaction;
- var temperature = mixture.Temperature;
- var energy = GetThermalEnergy(mixture);
- foreach (var prototype in GasReactions)
- {
- if (energy < prototype.MinimumEnergyRequirement ||
- temperature < prototype.MinimumTemperatureRequirement ||
- temperature > prototype.MaximumTemperatureRequirement)
- continue;
- var doReaction = true;
- for (var i = 0; i < prototype.MinimumRequirements.Length; i++)
- {
- if(i >= Atmospherics.TotalNumberOfGases)
- throw new IndexOutOfRangeException("Reaction Gas Minimum Requirements Array Prototype exceeds total number of gases!");
- var req = prototype.MinimumRequirements[i];
- if (!(mixture.GetMoles(i) < req))
- continue;
- doReaction = false;
- break;
- }
- if (!doReaction)
- continue;
- reaction = prototype.React(mixture, holder, this, HeatScale);
- if(reaction.HasFlag(ReactionResult.StopReactions))
- break;
- }
- return reaction;
- }
- public enum GasCompareResult
- {
- NoExchange = -2,
- TemperatureExchange = -1,
- }
- }
- }
|