246 lines
12 KiB
C#
246 lines
12 KiB
C#
using System.Runtime.CompilerServices;
|
|
using Content.Shared.Atmos.Prototypes;
|
|
using Content.Shared.Atmos.Reactions;
|
|
using Content.Shared.CCVar;
|
|
using JetBrains.Annotations;
|
|
|
|
namespace Content.Shared.Atmos.EntitySystems;
|
|
|
|
public abstract partial class SharedAtmosphereSystem
|
|
{
|
|
/*
|
|
Partial class for operations involving GasMixtures.
|
|
|
|
Sometimes methods here are abstract because they need different client/server implementations
|
|
due to sandboxing.
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Cached array of gas specific heats.
|
|
/// </summary>
|
|
public float[] GasSpecificHeats => _gasSpecificHeats;
|
|
private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
|
|
|
|
/// <summary>
|
|
/// Mask used to determine if a gas is flammable or not.
|
|
/// </summary>
|
|
/// <para>This is used to quickly determine if a <see cref="GasMixture"/> contains any flammable gas.
|
|
/// When determining flammability, the float is multiplied with the mask and then
|
|
/// added to see if the mixture is flammable, and how many moles are considered flammable.</para>
|
|
/// <para>This is done instead of a massive if statement of doom everywhere.</para>
|
|
/// <example><para>Say Plasma has the <see cref="GasPrototype.IsFuel"/> bool set to true.
|
|
/// Atmospherics will place a 1 in the spot where plasma goes in the masking array.
|
|
/// Whenever we need to determine if a GasMixture contains fuel gases, we multiply the
|
|
/// gas array by the mask. Fuel gases will keep their value (being multiplied by one)
|
|
/// whereas non-fuel gases will be multiplied by zero and be zeroed out.
|
|
/// The resulting array can be HorizontalAdded, with any value above zero indicating fuel gases.</para>
|
|
/// <para>This works for multiple fuel gases at the same time, so it's a fairly quick way
|
|
/// to determine if a mixture has the gases we care about.</para></example>
|
|
protected readonly float[] GasFuelMask = new float[Atmospherics.AdjustedNumberOfGases];
|
|
|
|
/// <summary>
|
|
/// Mask used to determine if a gas is an oxidizer or not.
|
|
/// <para>Used in the same way as <see cref="GasFuelMask"/>.
|
|
/// Nothing really super special.</para>
|
|
/// </summary>
|
|
protected readonly float[] GasOxidizerMask = new float[Atmospherics.AdjustedNumberOfGases];
|
|
|
|
/// <summary>
|
|
/// Mask used to determine both fuel and oxidizer properties of a gas at the same time.
|
|
/// Primarily used to quickly report the specific moles in a mixture that caused a flammable reaction to occur.
|
|
/// </summary>
|
|
protected readonly float[] GasOxidiserFuelMask = new float[Atmospherics.TotalNumberOfGases];
|
|
|
|
public string?[] GasReagents = new string[Atmospherics.TotalNumberOfGases];
|
|
protected readonly GasPrototype[] GasPrototypes = new GasPrototype[Atmospherics.TotalNumberOfGases];
|
|
|
|
public virtual void InitializeGases()
|
|
{
|
|
foreach (var gas in Enum.GetValues<Gas>())
|
|
{
|
|
var idx = (int)gas;
|
|
// Log an error if the corresponding prototype isn't found
|
|
if (!ProtoMan.TryIndex<GasPrototype>(gas.ToString(), out var gasPrototype))
|
|
{
|
|
Log.Error($"Failed to find corresponding {nameof(GasPrototype)} for gas ID {(int)gas} ({gas}) with expected ID \"{gas.ToString()}\". Is your prototype named correctly?");
|
|
continue;
|
|
}
|
|
GasPrototypes[idx] = gasPrototype;
|
|
GasReagents[idx] = gasPrototype.Reagent;
|
|
}
|
|
|
|
Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4));
|
|
|
|
for (var i = 0; i < GasPrototypes.Length; i++)
|
|
{
|
|
/*
|
|
As an optimization routine we pre-divide the specific heat by the heat scale here,
|
|
so we don't have to do it every time we calculate heat capacity.
|
|
Most usages are going to want the scaled value anyway.
|
|
|
|
If you would like the unscaled specific heat, you'd need to multiply by HeatScale again.
|
|
TODO ATMOS: please just make this 2 separate arrays instead of invoking multiplication every time.
|
|
*/
|
|
_gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat / HeatScale;
|
|
|
|
// """Mask""" built here. Used to determine if a gas is fuel/oxidizer or not decently quickly and clearly.
|
|
GasFuelMask[i] = GasPrototypes[i].IsFuel ? 1 : 0;
|
|
|
|
// Same for oxidizer mask.
|
|
GasOxidizerMask[i] = GasPrototypes[i].IsOxidizer ? 1 : 0;
|
|
|
|
// OxidiserFuel mask is just fuel and oxidizer combined, because both are required for a reaction to occur.
|
|
GasOxidiserFuelMask[i] = GasFuelMask[i] * GasOxidizerMask[i];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets only the moles that are considered a fuel and an oxidizer in a <see cref="GasMixture"/>.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to get the flammable moles for.</param>
|
|
/// <param name="buffer">A buffer to write the flammable moles into. Must be the same length as the number of gases.</param>
|
|
/// <returns>A <see cref="Span{T}"/> of moles where only the flammable and oxidizer moles are returned, and the rest are 0.</returns>
|
|
[PublicAPI]
|
|
public void GetFlammableMoles(GasMixture mixture, float[] buffer)
|
|
{
|
|
NumericsHelpers.Multiply(mixture.Moles, GasOxidiserFuelMask, buffer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if a <see cref="GasMixture"/> is ignitable or not.
|
|
/// This is a combination of determining if a mixture both has oxidizer and fuel.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to determine.</param>
|
|
/// <param name="epsilon">The minimum amount of moles at which a <see cref="GasMixture"/> is
|
|
/// considered ignitable, for both oxidizer and fuel.</param>
|
|
/// <returns>True if the <see cref="GasMixture"/> is ignitable, otherwise, false.</returns>
|
|
[PublicAPI]
|
|
public bool IsMixtureIgnitable(GasMixture mixture, float epsilon = Atmospherics.Epsilon)
|
|
{
|
|
return IsMixtureFuel(mixture, epsilon) && IsMixtureOxidizer(mixture, epsilon);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if a <see cref="GasMixture"/> has fuel gases in it or not.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to determine.</param>
|
|
/// <param name="epsilon">The minimum amount of moles at which a <see cref="GasMixture"/>
|
|
/// is considered fuel.</param>
|
|
/// <returns>True if the <see cref="GasMixture"/> is fuel, otherwise, false.</returns>
|
|
[PublicAPI]
|
|
public abstract bool IsMixtureFuel(GasMixture mixture, float epsilon = Atmospherics.Epsilon);
|
|
|
|
/// <summary>
|
|
/// Determines if a <see cref="GasMixture"/> has oxidizer gases in it or not.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to determine.</param>
|
|
/// <param name="epsilon">The minimum amount of moles at which a <see cref="GasMixture"/>
|
|
/// is considered an oxidizer.</param>
|
|
/// <returns>True if the <see cref="GasMixture"/> is an oxidizer, otherwise, false.</returns>
|
|
[PublicAPI]
|
|
public abstract bool IsMixtureOxidizer(GasMixture mixture, float epsilon = Atmospherics.Epsilon);
|
|
|
|
/// <summary>
|
|
/// Calculates the heat capacity for a <see cref="GasMixture"/>.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to calculate the heat capacity for.</param>
|
|
/// <param name="applyScaling">Whether to apply the heat capacity scaling factor.
|
|
/// This is an extremely important boolean to consider or else you will get heat transfer wrong.
|
|
/// See <see cref="CCVars.AtmosHeatScale"/> for more info.</param>
|
|
/// <returns>The heat capacity of the <see cref="GasMixture"/>.</returns>
|
|
[PublicAPI]
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the thermal energy for a <see cref="GasMixture"/>.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to calculate the thermal
|
|
/// energy of.</param>
|
|
/// <returns>The <see cref="GasMixture"/>'s thermal energy in joules.</returns>
|
|
[PublicAPI]
|
|
public float GetThermalEnergy(GasMixture mixture)
|
|
{
|
|
return mixture.Temperature * GetHeatCapacity(mixture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the thermal energy for a gas mixture,
|
|
/// using a provided cached heat capacity value.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to calculate the thermal energy of.</param>
|
|
/// <param name="cachedHeatCapacity">A cached heat capacity value for the gas mixture,
|
|
/// to avoid redundant heat capacity calculations.</param>
|
|
/// <returns>The <see cref="GasMixture"/>'s thermal energy in joules.</returns>
|
|
[PublicAPI]
|
|
public float GetThermalEnergy(GasMixture mixture, float cachedHeatCapacity)
|
|
{
|
|
return mixture.Temperature * cachedHeatCapacity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Merges one <see cref="GasMixture"/> into another, modifying the receiver.
|
|
/// </summary>
|
|
/// <param name="receiver">The <see cref="GasMixture"/> to merge into. This will be modified.</param>
|
|
/// <param name="giver">The <see cref="GasMixture"/> to merge from. This will not be modified.</param>
|
|
[PublicAPI]
|
|
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>
|
|
/// Performs reactions for a given gas mixture on an optional holder.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to perform reactions on.</param>
|
|
/// <param name="holder"><see cref="IGasMixtureHolder"/> that holds the <see cref="GasMixture"/>.
|
|
/// used by Atmospherics to determine locality for certain reaction effects.</param>
|
|
/// <returns>The <see cref="ReactionResult"/> of the reactions performed.</returns>
|
|
[PublicAPI]
|
|
public abstract ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder);
|
|
|
|
/// <summary>
|
|
/// Gets the heat capacity for a <see cref="GasMixture"/>.
|
|
/// </summary>
|
|
/// <param name="mixture">The <see cref="GasMixture"/> to calculate the heat capacity for.</param>
|
|
/// <returns>The heat capacity of the <see cref="GasMixture"/>.</returns>
|
|
/// <remarks>Note that the heat capacity of the mixture may be slightly different from
|
|
/// "real life" as we intentionally fake a heat capacity for space in <see cref="Atmospherics.SpaceHeatCapacity"/>
|
|
/// in order to allow Atmospherics to cool down space.</remarks>
|
|
protected float GetHeatCapacity(GasMixture mixture)
|
|
{
|
|
return GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the heat capacity for a <see cref="GasMixture"/>.
|
|
/// </summary>
|
|
/// <param name="moles">The moles array of the <see cref="GasMixture"/></param>
|
|
/// <param name="space">Whether this <see cref="GasMixture"/> represents space,
|
|
/// and thus experiences space-specific mechanics (we cheat and make it a bit cooler).
|
|
/// See <see cref="Atmospherics.SpaceHeatCapacity"/>.</param>
|
|
/// <returns>The heat capacity of the <see cref="GasMixture"/>.</returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected abstract float GetHeatCapacityCalculation(float[] moles, bool space);
|
|
}
|