mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
Merge branch 'fork/Partmedia/notafet/gas_reactions_lite' into engi-atmos/YAML-gas-reactions
This commit is contained in:
@@ -11,6 +11,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly GenericGasReactionSystem _reaction = default!;
|
||||
|
||||
private GasReactionPrototype[] _gasReactions = Array.Empty<GasReactionPrototype>();
|
||||
private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
|
||||
@@ -443,6 +444,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder)
|
||||
{
|
||||
// First pass: run through the legacy (hard-coded) gas reactions
|
||||
var reaction = ReactionResult.NoReaction;
|
||||
var temperature = mixture.Temperature;
|
||||
var energy = GetThermalEnergy(mixture);
|
||||
@@ -474,7 +476,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
break;
|
||||
}
|
||||
|
||||
return reaction;
|
||||
// Second pass: Regardless of result, run YAML gas reactions
|
||||
return _reaction.ReactAll(GasReactions, mixture, holder);
|
||||
}
|
||||
|
||||
public enum GasCompareResult
|
||||
|
||||
124
Content.Server/Atmos/EntitySystems/GenericGasReactionSystem.cs
Normal file
124
Content.Server/Atmos/EntitySystems/GenericGasReactionSystem.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Shared.Atmos.Reactions;
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
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>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
private float ReactionRate(GasReactionPrototype reaction, GasMixture mix, float dE)
|
||||
{
|
||||
var temp = mix.Temperature;
|
||||
|
||||
// Gas reactions have a MinimumEnergyRequirement which is in spirit activation 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.
|
||||
const float TScaleFactor = 10;
|
||||
var Ea = TScaleFactor * Atmospherics.R * reaction.MinimumTemperatureRequirement + dE;
|
||||
|
||||
// To compute initial rate coefficient A, assume that at temp = min temp we return 1/10.
|
||||
const float RateScaleFactor = 10; // not necessarily the same as TScaleFactor! Don't get confused!
|
||||
var A = MathF.Exp(TScaleFactor) / RateScaleFactor;
|
||||
|
||||
// Prevent divide by zero
|
||||
if (temp < Atmospherics.TCMB)
|
||||
return 0;
|
||||
|
||||
return 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)
|
||||
{
|
||||
var nTotal = mix.TotalMoles;
|
||||
// Guard against very small amounts of gas in mixture
|
||||
if (nTotal < Atmospherics.GasMinMoles)
|
||||
return ReactionResult.NoReaction;
|
||||
|
||||
// Guard against volume div/0.
|
||||
// Realistically, GasMixtures should never have this low of a volume.
|
||||
Debug.Assert(mix.Volume > Atmospherics.GasMinVolumeForReactions);
|
||||
|
||||
foreach (var reaction in reactions)
|
||||
{
|
||||
// Check if this is a generic YAML reaction (has reactants)
|
||||
if (reaction.Reactants.Count == 0)
|
||||
continue;
|
||||
|
||||
// Add concentration-dependent reaction rate
|
||||
// For 1A + 2B -> 3C, the concentration-dependence is [A]^1 * [B]^2
|
||||
var rate = 1f; // rate of this reaction
|
||||
foreach (var (reactant, num) in reaction.Reactants)
|
||||
{
|
||||
var concentration = mix.GetMoles(reactant) / mix.Volume;
|
||||
rate *= MathF.Pow(concentration, num);
|
||||
}
|
||||
|
||||
// Sum catalysts
|
||||
float catalystEnergy = 0;
|
||||
foreach (var (catalyst, dE) in reaction.Catalysts)
|
||||
{
|
||||
var concentration = mix.GetMoles(catalyst) / mix.Volume;
|
||||
catalystEnergy += dE * concentration;
|
||||
}
|
||||
|
||||
// Now apply temperature-dependent reaction rate scaling
|
||||
rate *= ReactionRate(reaction, mix, catalystEnergy);
|
||||
|
||||
// Nothing to do
|
||||
if (rate <= 0)
|
||||
continue;
|
||||
|
||||
// Go through and remove all the reactants
|
||||
// If any of the reactants were zero, then the code above would have already set
|
||||
// rate to zero, so we don't have to check that again here.
|
||||
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)
|
||||
{
|
||||
_atmosphere.AddHeat(mix, reaction.Enthalpy / _atmosphere.HeatScale * rate);
|
||||
if (reaction.Enthalpy > 0)
|
||||
{
|
||||
mix.ReactionResults[(byte)GasReaction.Fire] += rate;
|
||||
if (holder is TileAtmosphere location)
|
||||
{
|
||||
if (mix.Temperature > Atmospherics.FireMinimumTemperatureToExist)
|
||||
{
|
||||
_atmosphere.HotspotExpose(location.GridIndex,
|
||||
location.GridIndices,
|
||||
mix.Temperature,
|
||||
mix.Volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ReactionResult.Reacting;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Reactions;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Atmos.Reactions;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed partial class AmmoniaOxygenReaction : IGasReactionEffect
|
||||
{
|
||||
public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale)
|
||||
{
|
||||
var nAmmonia = mixture.GetMoles(Gas.Ammonia);
|
||||
var nOxygen = mixture.GetMoles(Gas.Oxygen);
|
||||
var nTotal = mixture.TotalMoles;
|
||||
|
||||
// Concentration-dependent reaction rate
|
||||
var fAmmonia = nAmmonia/nTotal;
|
||||
var fOxygen = nOxygen/nTotal;
|
||||
var rate = MathF.Pow(fAmmonia, 2) * MathF.Pow(fOxygen, 2);
|
||||
|
||||
var deltaMoles = nAmmonia / Atmospherics.AmmoniaOxygenReactionRate * 2 * rate;
|
||||
|
||||
if (deltaMoles <= 0 || nAmmonia - deltaMoles < 0)
|
||||
return ReactionResult.NoReaction;
|
||||
|
||||
mixture.AdjustMoles(Gas.Ammonia, -deltaMoles);
|
||||
mixture.AdjustMoles(Gas.Oxygen, -deltaMoles);
|
||||
mixture.AdjustMoles(Gas.NitrousOxide, deltaMoles / 2);
|
||||
mixture.AdjustMoles(Gas.WaterVapor, deltaMoles * 1.5f);
|
||||
|
||||
return ReactionResult.Reacting;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,27 @@ namespace Content.Server.Atmos.Reactions
|
||||
/// </summary>
|
||||
[DataField("effects")] private List<IGasReactionEffect> _effects = new();
|
||||
|
||||
[DataField("enthalpy")]
|
||||
public float Enthalpy;
|
||||
|
||||
/// <summary>
|
||||
/// Integer gas IDs and integer ratios required in the reaction.
|
||||
/// </summary>
|
||||
[DataField("reactants")]
|
||||
public Dictionary<Gas, int> Reactants = new();
|
||||
|
||||
/// <summary>
|
||||
/// Integer gas IDs and integer ratios of reaction products.
|
||||
/// </summary>
|
||||
[DataField("products")]
|
||||
public Dictionary<Gas, int> Products = new();
|
||||
|
||||
/// <summary>
|
||||
/// Integer gas IDs and how much they modify the activation energy (J/mol).
|
||||
/// </summary>
|
||||
[DataField("catalysts")]
|
||||
public Dictionary<Gas, int> Catalysts = new();
|
||||
|
||||
/// <summary>
|
||||
/// Process all reaction effects.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Reactions;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Atmos.Reactions;
|
||||
|
||||
/// <summary>
|
||||
/// Decomposes Nitrous Oxide into Nitrogen and Oxygen.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class N2ODecompositionReaction : IGasReactionEffect
|
||||
{
|
||||
public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale)
|
||||
{
|
||||
var cacheN2O = mixture.GetMoles(Gas.NitrousOxide);
|
||||
|
||||
var burnedFuel = cacheN2O / Atmospherics.N2ODecompositionRate;
|
||||
|
||||
if (burnedFuel <= 0 || cacheN2O - burnedFuel < 0)
|
||||
return ReactionResult.NoReaction;
|
||||
|
||||
mixture.AdjustMoles(Gas.NitrousOxide, -burnedFuel);
|
||||
mixture.AdjustMoles(Gas.Nitrogen, burnedFuel);
|
||||
mixture.AdjustMoles(Gas.Oxygen, burnedFuel / 2);
|
||||
|
||||
return ReactionResult.Reacting;
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,12 @@ namespace Content.Shared.Atmos
|
||||
/// </summary>
|
||||
public const float GasMinMoles = 0.00000005f;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum volume that a <see cref="GasMixture"/> must have to perform reactions.
|
||||
/// Prevents div/0 issues.
|
||||
/// </summary>
|
||||
public const float GasMinVolumeForReactions = 0.0001f;
|
||||
|
||||
public const float OpenHeatTransferCoefficient = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
@@ -274,16 +280,6 @@ namespace Content.Shared.Atmos
|
||||
/// </summary>
|
||||
public const float FrezonProductionConversionRate = 50f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum portion of the N2O that can decompose each reaction tick. (50%)
|
||||
/// </summary>
|
||||
public const float N2ODecompositionRate = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Divisor for Ammonia Oxygen reaction so that it doesn't happen instantaneously.
|
||||
/// </summary>
|
||||
public const float AmmoniaOxygenReactionRate = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// Determines at what pressure the ultra-high pressure red icon is displayed.
|
||||
/// </summary>
|
||||
|
||||
@@ -43,20 +43,22 @@
|
||||
id: AmmoniaOxygenReaction
|
||||
priority: 2
|
||||
minimumTemperature: 323.149
|
||||
minimumRequirements:
|
||||
Oxygen: 0.01
|
||||
Ammonia: 0.01
|
||||
effects:
|
||||
- !type:AmmoniaOxygenReaction {}
|
||||
reactants:
|
||||
Ammonia: 2
|
||||
Oxygen: 2
|
||||
products:
|
||||
NitrousOxide: 1
|
||||
WaterVapor: 3
|
||||
|
||||
- type: gasReaction
|
||||
id: N2ODecomposition
|
||||
priority: 0
|
||||
minimumTemperature: 850
|
||||
minimumRequirements:
|
||||
NitrousOxide: 0.01
|
||||
effects:
|
||||
- !type:N2ODecompositionReaction {}
|
||||
reactants:
|
||||
NitrousOxide: 2
|
||||
products:
|
||||
Nitrogen: 2
|
||||
Oxygen: 1
|
||||
|
||||
#- type: gasReaction
|
||||
# id: WaterVaporPuddle
|
||||
|
||||
Reference in New Issue
Block a user