Files
tbd-station-14/Content.Server/Atmos/EntitySystems/GenericGasReactionSystem.cs
2023-12-22 17:10:33 -08:00

134 lines
5.1 KiB
C#

using Content.Server.Atmos.Reactions;
using Content.Shared.Atmos;
using JetBrains.Annotations;
using System.Collections;
using System.Linq;
namespace Content.Server.Atmos.EntitySystems;
public sealed class GenericGasReactionSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
/// <summary>
/// Return a reaction rate (in units reactants per second) for a given reaction. Based on the
/// Arrhenius equation (https://en.wikipedia.org/wiki/Arrhenius_equation).
///
/// This means that most reactions scale exponentially above the MinimumTemperatureRequirement.
/// </summary>
private float ReactionRate(GasReactionPrototype reaction, GasMixture mix, float dE)
{
float temp = mix.Temperature;
// Gas reactions have a MinimumEnergyRequirement which is in spirit activiation energy (Ea),
// but no reactions define it. So we have to calculate one to use. One way is to assume that
// Ea = 10*R*MinimumTemperatureRequirement such that Ea >> RT.
float TScaleFactor = 10;
float Ea = TScaleFactor*Atmospherics.R*reaction.MinimumTemperatureRequirement + dE;
// To compute initial rate coefficient A, assume that at temp = min temp we return 1/10.
float RateScaleFactor = 10; // not necessarily the same as TScaleFactor! Don't get confused!
float A = MathF.Exp(TScaleFactor) / RateScaleFactor;
return reaction.RateMultiplier*A*MathF.Exp(-Ea/(Atmospherics.R*temp));
}
/// <summary>
/// Run all of the reactions given on the given gas mixture located in the given container.
/// </summary>
public ReactionResult ReactAll(IEnumerable<GasReactionPrototype> reactions, GasMixture mix, IGasMixtureHolder? holder)
{
// It is possible for reactions to change the specific heat capacity, so we need to save initial
// internal energy so that we can conserve energy at the end
float initialE = _atmosphere.GetThermalEnergy(mix);
float reactionE = 0; // heat added by reaction enthalpy
foreach (var reaction in reactions)
{
float rate = 1f; // rate of this reaction
int reactants = 0;
// Reactions that have a maximum temperature really don't make physical sense since increasing
// kinetic energy always increases reaction rate. But begrudgingly implement this anyway.
if (mix.Temperature > reaction.MaximumTemperatureRequirement)
continue;
// Add concentration-dependent reaction rate
// For 1A + 2B -> 3C, the concentration-dependence is [A]^1 * [B]^2
float nTotal = mix.TotalMoles;
if (nTotal < Atmospherics.GasMinMoles)
continue;
foreach (var (reactant, num) in reaction.Reactants)
{
rate *= MathF.Pow(mix.GetMoles(reactant)/nTotal, num);
reactants++;
}
// No reactants; this is not a generic reaction.
if (reactants == 0)
continue;
// Sum catalysts
float catalystEnergy = 0;
foreach (var (catalyst, dE) in reaction.Catalysts)
{
catalystEnergy += dE;
}
// Now apply temperature-dependent reaction rate scaling
rate *= ReactionRate(reaction, mix, catalystEnergy);
// Nothing to do
if (rate <= 0)
continue;
// Pass to check the maximum rate, limited by the minimum available
// reactant to avoid going negative
float rateLim = rate;
foreach (var (reactant, num) in reaction.Reactants)
{
rateLim = MathF.Min(mix.GetMoles(reactant)/num, rateLim);
}
rate = rateLim;
// Go through and remove all the reactants
foreach (var (reactant, num) in reaction.Reactants)
{
mix.AdjustMoles(reactant, -num*rate);
}
// Go through and add products
foreach (var (product, num) in reaction.Products)
{
mix.AdjustMoles(product, num*rate);
}
// Add heat from the reaction
if (reaction.Enthalpy != 0)
{
reactionE += reaction.Enthalpy/_atmosphere.HeatScale * rate;
if (reaction.Enthalpy > 0)
mix.ReactionResults[GasReaction.Fire] += rate;
}
}
float newHeatCapacity = _atmosphere.GetHeatCapacity(mix, true);
if (newHeatCapacity > Atmospherics.MinimumHeatCapacity)
{
mix.Temperature = (initialE + reactionE)/newHeatCapacity;
}
if (reactionE > 0)
{
var location = holder as TileAtmosphere;
if (location != null)
{
if (mix.Temperature > Atmospherics.FireMinimumTemperatureToExist)
{
_atmosphere.HotspotExpose(location.GridIndex, location.GridIndices, mix.Temperature, mix.Volume);
}
}
}
return ReactionResult.Reacting;
}
}