]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add YAML gas reactions (#22803)
authorKevin Zheng <kevinz5000@gmail.com>
Fri, 22 Dec 2023 07:30:56 +0000 (23:30 -0800)
committerGitHub <noreply@github.com>
Fri, 22 Dec 2023 07:30:56 +0000 (00:30 -0700)
* Add YAML gas reactions

* Convert more reactions

* Use enum names

* Convert more names

* Add migration for MiasmaCanister

* Prevent reactants from going negative

* Fix energy conservation, lift energy calculation

* Fix comment

* Comment

* Add rate multiplier

Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs
Content.Server/Atmos/EntitySystems/GenericGasReactionSystem.cs [new file with mode: 0644]
Content.Server/Atmos/Reactions/AmmoniaOxygenReaction.cs [deleted file]
Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs [deleted file]
Content.Server/Atmos/Reactions/GasReactionPrototype.cs
Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs [deleted file]
Content.Server/Atmos/Reactions/TritiumFireReaction.cs [deleted file]
Content.Shared/Atmos/Atmospherics.cs
Resources/Prototypes/Atmospherics/reactions.yml

index 4c3437e431e6e836a465305e25a214fe009dc1f8..909ec5ec9cf9b1949bc326ae282c86a600233f23 100644 (file)
@@ -10,6 +10,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];
@@ -346,7 +347,7 @@ namespace Content.Server.Atmos.EntitySystems
                     break;
             }
 
-            return reaction;
+            return _reaction.ReactAll(GasReactions, mixture, holder);
         }
 
         public enum GasCompareResult
diff --git a/Content.Server/Atmos/EntitySystems/GenericGasReactionSystem.cs b/Content.Server/Atmos/EntitySystems/GenericGasReactionSystem.cs
new file mode 100644 (file)
index 0000000..a21f85a
--- /dev/null
@@ -0,0 +1,130 @@
+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);
+        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;
+    }
+}
diff --git a/Content.Server/Atmos/Reactions/AmmoniaOxygenReaction.cs b/Content.Server/Atmos/Reactions/AmmoniaOxygenReaction.cs
deleted file mode 100644 (file)
index 197034c..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Shared.Atmos;
-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;
-    }
-}
diff --git a/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs b/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs
deleted file mode 100644 (file)
index 051ee82..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Shared.Atmos;
-using JetBrains.Annotations;
-
-namespace Content.Server.Atmos.Reactions;
-
-/// <summary>
-///     Takes in nitrogen and frezon and cools down the surrounding area.
-/// </summary>
-[UsedImplicitly]
-public sealed partial class FrezonCoolantReaction : IGasReactionEffect
-{
-    public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale)
-    {
-        var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true);
-        var temperature = mixture.Temperature;
-
-        var energyModifier = 1f;
-        var scale = (temperature - Atmospherics.FrezonCoolLowerTemperature) /
-                    (Atmospherics.FrezonCoolMidTemperature - Atmospherics.FrezonCoolLowerTemperature);
-
-        if (scale > 1f)
-        {
-            // Scale energy but not frezon usage if we're in a very, very hot place
-            energyModifier = Math.Min(scale, Atmospherics.FrezonCoolMaximumEnergyModifier);
-            scale = 1f;
-        }
-
-        if (scale <= 0)
-            return ReactionResult.NoReaction;
-
-        var initialNit = mixture.GetMoles(Gas.Nitrogen);
-        var initialFrezon = mixture.GetMoles(Gas.Frezon);
-
-        var burnRate = initialFrezon * scale / Atmospherics.FrezonCoolRateModifier;
-
-        var energyReleased = 0f;
-        if (burnRate > Atmospherics.MinimumHeatCapacity)
-        {
-            var nitAmt = Math.Min(burnRate * Atmospherics.FrezonNitrogenCoolRatio, initialNit);
-            var frezonAmt = Math.Min(burnRate, initialFrezon);
-            mixture.AdjustMoles(Gas.Nitrogen, -nitAmt);
-            mixture.AdjustMoles(Gas.Frezon, -frezonAmt);
-            mixture.AdjustMoles(Gas.NitrousOxide, nitAmt + frezonAmt);
-            energyReleased = burnRate * Atmospherics.FrezonCoolEnergyReleased * energyModifier;
-        }
-
-        energyReleased /= heatScale; // adjust energy to make sure speedup doesn't cause mega temperature rise
-        if (energyReleased >= 0f)
-            return ReactionResult.NoReaction;
-
-        var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true);
-        if (newHeatCapacity > Atmospherics.MinimumHeatCapacity)
-            mixture.Temperature = (temperature * oldHeatCapacity + energyReleased) / newHeatCapacity;
-
-        return ReactionResult.Reacting;
-    }
-}
index 0ee29de3bf196fb7178d0496d8c2fdec310f94ef..b19e4c7a8859614ed8fd87a6570450796109285d 100644 (file)
@@ -25,7 +25,8 @@ namespace Content.Server.Atmos.Reactions
         public string ID { get; private set; } = default!;
 
         /// <summary>
-        ///     Minimum gas amount requirements.
+        ///     Minimum gas amount requirements. Reactions that meet these minimum mole requirements
+        ///     have their reaction effects run. Generic gas reactions do not have minimum requirements.
         /// </summary>
         [DataField("minimumRequirements")]
         public float[] MinimumRequirements { get; private set; } = new float[Atmospherics.TotalNumberOfGases];
@@ -42,6 +43,13 @@ namespace Content.Server.Atmos.Reactions
         [DataField("minimumTemperature")]
         public float MinimumTemperatureRequirement { get; private set; } = Atmospherics.TCMB;
 
+        /// <summary>
+        /// If this is a generic gas reaction, multiply the initial rate by this. The default is reasonable for
+        /// synthesis reactions. Consider raising this for fires.
+        /// </summary>
+        [DataField("rateMultiplier")]
+        public float RateMultiplier = 1f;
+
         /// <summary>
         ///     Minimum energy requirement.
         /// </summary>
@@ -60,6 +68,31 @@ namespace Content.Server.Atmos.Reactions
         /// </summary>
         [DataField("effects")] private List<IGasReactionEffect> _effects = new();
 
+        /// <summary>
+        ///     Energy released by the reaction.
+        /// </summary>
+        [DataField("enthalpy")]
+        public float Enthalpy;
+
+        /// <summary>
+        /// Integer gas IDs and integer ratios required in the reaction. If this is defined, the
+        /// generic gas reaction will run.
+        /// </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>
diff --git a/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs b/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs
deleted file mode 100644 (file)
index 7fce663..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Shared.Atmos;
-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;
-    }
-}
diff --git a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs
deleted file mode 100644 (file)
index c52b431..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Shared.Atmos;
-using JetBrains.Annotations;
-
-namespace Content.Server.Atmos.Reactions
-{
-    [UsedImplicitly]
-    [DataDefinition]
-    public sealed partial class TritiumFireReaction : IGasReactionEffect
-    {
-        public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale)
-        {
-            var energyReleased = 0f;
-            var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true);
-            var temperature = mixture.Temperature;
-            var location = holder as TileAtmosphere;
-            mixture.ReactionResults[GasReaction.Fire] = 0f;
-            var burnedFuel = 0f;
-            var initialTrit = mixture.GetMoles(Gas.Tritium);
-
-            if (mixture.GetMoles(Gas.Oxygen) < initialTrit ||
-                Atmospherics.MinimumTritiumOxyburnEnergy > (temperature * oldHeatCapacity))
-            {
-                burnedFuel = mixture.GetMoles(Gas.Oxygen) / Atmospherics.TritiumBurnOxyFactor;
-                if (burnedFuel > initialTrit)
-                    burnedFuel = initialTrit;
-
-                mixture.AdjustMoles(Gas.Tritium, -burnedFuel);
-            }
-            else
-            {
-                burnedFuel = initialTrit;
-                mixture.SetMoles(Gas.Tritium, mixture.GetMoles(Gas.Tritium ) * (1 - 1 / Atmospherics.TritiumBurnTritFactor));
-                mixture.AdjustMoles(Gas.Oxygen, -mixture.GetMoles(Gas.Tritium));
-                energyReleased += (Atmospherics.FireHydrogenEnergyReleased * burnedFuel * (Atmospherics.TritiumBurnTritFactor - 1));
-            }
-
-            if (burnedFuel > 0)
-            {
-                energyReleased += (Atmospherics.FireHydrogenEnergyReleased * burnedFuel);
-
-                // TODO ATMOS Radiation pulse here!
-
-                // Conservation of mass is important.
-                mixture.AdjustMoles(Gas.WaterVapor, burnedFuel);
-
-                mixture.ReactionResults[GasReaction.Fire] += burnedFuel;
-            }
-
-            energyReleased /= heatScale; // adjust energy to make sure speedup doesn't cause mega temperature rise
-            if (energyReleased > 0)
-            {
-                var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true);
-                if (newHeatCapacity > Atmospherics.MinimumHeatCapacity)
-                    mixture.Temperature = ((temperature * oldHeatCapacity + energyReleased) / newHeatCapacity);
-            }
-
-            if (location != null)
-            {
-                temperature = mixture.Temperature;
-                if (temperature > Atmospherics.FireMinimumTemperatureToExist)
-                {
-                    atmosphereSystem.HotspotExpose(location.GridIndex, location.GridIndices, temperature, mixture.Volume);
-                }
-            }
-
-            return mixture.ReactionResults[GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction;
-        }
-    }
-}
index 7765832ee43ee79195e43739d88976e4aff8a94a..39b24de74661a74e695592beb68dca1d35b8a290 100644 (file)
@@ -195,30 +195,6 @@ namespace Content.Shared.Atmos
         public const float PlasmaOxygenFullburn = 10f;
         public const float PlasmaBurnRateDelta = 9f;
 
-        /// <summary>
-        ///     This is calculated to help prevent singlecap bombs (Overpowered tritium/oxygen single tank bombs)
-        /// </summary>
-        public const float MinimumTritiumOxyburnEnergy = 143000f;
-
-        public const float TritiumBurnOxyFactor = 100f;
-        public const float TritiumBurnTritFactor = 10f;
-
-        public const float FrezonCoolLowerTemperature = 23.15f;
-
-        /// <summary>
-        ///     Frezon cools better at higher temperatures.
-        /// </summary>
-        public const float FrezonCoolMidTemperature = 373.15f;
-
-        public const float FrezonCoolMaximumEnergyModifier = 10f;
-
-        /// <summary>
-        ///     Remove X mol of nitrogen for each mol of frezon.
-        /// </summary>
-        public const float FrezonNitrogenCoolRatio = 5;
-        public const float FrezonCoolEnergyReleased = -600e3f;
-        public const float FrezonCoolRateModifier = 20f;
-
         public const float FrezonProductionMaxEfficiencyTemperature = 73.15f;
 
         /// <summary>
@@ -236,16 +212,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>
index d226c81f6cc5fef04616cace99ee47089c5d6f9c..03c563b125c2a58f5d8dfb45e444ee7a48e1e805 100644 (file)
   id: TritiumFire
   priority: -1
   minimumTemperature: 373.149 # Same as Atmospherics.FireMinimumTemperatureToExist
-  minimumRequirements: # In this case, same as minimum mole count.
-    - 0.01  # oxygen
-    - 0     # nitrogen
-    - 0     # carbon dioxide
-    - 0     # plasma
-    - 0.01  # tritium
-  effects:
-    - !type:TritiumFireReaction {}
+  enthalpy: 284000
+  reactants:
+    Tritium: 2
+    Oxygen: 1
+  products:
+    WaterVapor: 2
 
 - type: gasReaction
   id: FrezonCoolant
   priority: 1
   minimumTemperature: 23.15
-  minimumRequirements:
-  - 0     # oxygen
-  - 0.01  # nitrogen
-  - 0     # carbon dioxide
-  - 0     # plasma
-  - 0     # tritium
-  - 0     # vapor
-  - 0     # ammonia
-  - 0     # n2o
-  - 0.01  # frezon
-  effects:
-  - !type:FrezonCoolantReaction {}
+  enthalpy: -600000
+  reactants:
+    Frezon: 1
+    Nitrogen: 5
+  products:
+    NitrousOxide: 6
 
 - type: gasReaction
   id: FrezonProduction
   id: AmmoniaOxygenReaction
   priority: 2
   minimumTemperature: 323.149
-  minimumRequirements:
-  - 0.01  # oxygen
-  - 0     # nitrogen
-  - 0     # carbon dioxide
-  - 0     # plasma
-  - 0     # tritium
-  - 0     # vapor
-  - 0.01  # ammonia
-  - 0     # n2o
-  - 0     # frezon
-  effects:
-  - !type:AmmoniaOxygenReaction {}
+  reactants:
+    Ammonia: 2
+    Oxygen: 2
+  products:
+    NitrousOxide: 1
+    WaterVapor: 3
 
 - type: gasReaction
   id: N2ODecomposition
   priority: 0
   minimumTemperature: 850
-  minimumRequirements:
-  - 0     # oxygen
-  - 0     # nitrogen
-  - 0     # carbon dioxide
-  - 0     # plasma
-  - 0     # tritium
-  - 0     # vapor
-  - 0     # ammonia
-  - 0.01  # n2o
-  - 0     # frezon
-  effects:
-  - !type:N2ODecompositionReaction {}
+  reactants:
+    NitrousOxide: 2
+  products:
+    Nitrogen: 2
+    Oxygen: 1
 
 #- type: gasReaction
 #  id: WaterVaporPuddle