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.
*/
///
/// Cached array of molar heat capacities of the gases.
///
public float[] GasMolarHeatCapacities => _gasMolarHeatCapacities;
private float[] _gasMolarHeatCapacities = new float[Atmospherics.TotalNumberOfGases];
///
/// Mask used to determine if a gas is flammable or not.
///
/// This is used to quickly determine if a 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.
/// This is done instead of a massive if statement of doom everywhere.
/// Say Plasma has the 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.
/// 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.
protected readonly float[] GasFuelMask = new float[Atmospherics.AdjustedNumberOfGases];
///
/// Mask used to determine if a gas is an oxidizer or not.
/// Used in the same way as .
/// Nothing really super special.
///
protected readonly float[] GasOxidizerMask = new float[Atmospherics.AdjustedNumberOfGases];
///
/// 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.
///
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())
{
var idx = (int)gas;
// Log an error if the corresponding prototype isn't found
if (!ProtoMan.TryIndex(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 _gasMolarHeatCapacities, 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.
*/
_gasMolarHeatCapacities[i] = GasPrototypes[i].MolarHeatCapacity / 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];
}
}
///
/// Gets only the moles that are considered a fuel and an oxidizer in a .
///
/// The to get the flammable moles for.
/// A buffer to write the flammable moles into. Must be the same length as the number of gases.
/// A of moles where only the flammable and oxidizer moles are returned, and the rest are 0.
[PublicAPI]
public void GetFlammableMoles(GasMixture mixture, float[] buffer)
{
NumericsHelpers.Multiply(mixture.Moles, GasOxidiserFuelMask, buffer);
}
///
/// Determines if a is ignitable or not.
/// This is a combination of determining if a mixture both has oxidizer and fuel.
///
/// The to determine.
/// The minimum amount of moles at which a is
/// considered ignitable, for both oxidizer and fuel.
/// True if the is ignitable, otherwise, false.
[PublicAPI]
public bool IsMixtureIgnitable(GasMixture mixture, float epsilon = Atmospherics.Epsilon)
{
return IsMixtureFuel(mixture, epsilon) && IsMixtureOxidizer(mixture, epsilon);
}
///
/// Determines if a has fuel gases in it or not.
///
/// The to determine.
/// The minimum amount of moles at which a
/// is considered fuel.
/// True if the is fuel, otherwise, false.
[PublicAPI]
public abstract bool IsMixtureFuel(GasMixture mixture, float epsilon = Atmospherics.Epsilon);
///
/// Determines if a has oxidizer gases in it or not.
///
/// The to determine.
/// The minimum amount of moles at which a
/// is considered an oxidizer.
/// True if the is an oxidizer, otherwise, false.
[PublicAPI]
public abstract bool IsMixtureOxidizer(GasMixture mixture, float epsilon = Atmospherics.Epsilon);
///
/// Calculates the heat capacity for a .
///
/// The to calculate the heat capacity for.
/// 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 for more info.
/// The heat capacity of the .
[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;
}
///
/// Calculates the thermal energy for a .
///
/// The to calculate the thermal
/// energy of.
/// The 's thermal energy in joules.
[PublicAPI]
public float GetThermalEnergy(GasMixture mixture)
{
return mixture.Temperature * GetHeatCapacity(mixture);
}
///
/// Calculates the thermal energy for a gas mixture,
/// using a provided cached heat capacity value.
///
/// The to calculate the thermal energy of.
/// A cached heat capacity value for the gas mixture,
/// to avoid redundant heat capacity calculations.
/// The 's thermal energy in joules.
[PublicAPI]
public float GetThermalEnergy(GasMixture mixture, float cachedHeatCapacity)
{
return mixture.Temperature * cachedHeatCapacity;
}
///
/// Merges one into another, modifying the receiver.
///
/// The to merge into. This will be modified.
/// The to merge from. This will not be modified.
[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);
}
///
/// Performs reactions for a given gas mixture on an optional holder.
///
/// The to perform reactions on.
/// that holds the .
/// used by Atmospherics to determine locality for certain reaction effects.
/// The of the reactions performed.
[PublicAPI]
public abstract ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder);
///
/// Gets the heat capacity for a .
///
/// The to calculate the heat capacity for.
/// The heat capacity of the .
/// 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
/// in order to allow Atmospherics to cool down space.
protected float GetHeatCapacity(GasMixture mixture)
{
return GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
}
///
/// Gets the heat capacity for a .
///
/// The moles array of the
/// Whether this represents space,
/// and thus experiences space-specific mechanics (we cheat and make it a bit cooler).
/// See .
/// The heat capacity of the .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected abstract float GetHeatCapacityCalculation(float[] moles, bool space);
}