From: Kevin Zheng Date: Fri, 22 Dec 2023 07:30:56 +0000 (-0800) Subject: Add YAML gas reactions (#22803) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=054321d2c2c17eb55a1640150131c61c29a3eb2b;p=space-station-14.git Add YAML gas reactions (#22803) * 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 --- diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index 4c3437e431..909ec5ec9c 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -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(); 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 index 0000000000..a21f85ae95 --- /dev/null +++ b/Content.Server/Atmos/EntitySystems/GenericGasReactionSystem.cs @@ -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!; + + /// + /// 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. + /// + 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)); + } + + /// + /// Run all of the reactions given on the given gas mixture located in the given container. + /// + public ReactionResult ReactAll(IEnumerable 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 index 197034ce54..0000000000 --- a/Content.Server/Atmos/Reactions/AmmoniaOxygenReaction.cs +++ /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 index 051ee8202d..0000000000 --- a/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using JetBrains.Annotations; - -namespace Content.Server.Atmos.Reactions; - -/// -/// Takes in nitrogen and frezon and cools down the surrounding area. -/// -[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; - } -} diff --git a/Content.Server/Atmos/Reactions/GasReactionPrototype.cs b/Content.Server/Atmos/Reactions/GasReactionPrototype.cs index 0ee29de3bf..b19e4c7a88 100644 --- a/Content.Server/Atmos/Reactions/GasReactionPrototype.cs +++ b/Content.Server/Atmos/Reactions/GasReactionPrototype.cs @@ -25,7 +25,8 @@ namespace Content.Server.Atmos.Reactions public string ID { get; private set; } = default!; /// - /// 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. /// [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; + /// + /// 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. + /// + [DataField("rateMultiplier")] + public float RateMultiplier = 1f; + /// /// Minimum energy requirement. /// @@ -60,6 +68,31 @@ namespace Content.Server.Atmos.Reactions /// [DataField("effects")] private List _effects = new(); + /// + /// Energy released by the reaction. + /// + [DataField("enthalpy")] + public float Enthalpy; + + /// + /// Integer gas IDs and integer ratios required in the reaction. If this is defined, the + /// generic gas reaction will run. + /// + [DataField("reactants")] + public Dictionary Reactants = new(); + + /// + /// Integer gas IDs and integer ratios of reaction products. + /// + [DataField("products")] + public Dictionary Products = new(); + + /// + /// Integer gas IDs and how much they modify the activation energy (J/mol). + /// + [DataField("catalysts")] + public Dictionary Catalysts = new(); + /// /// Process all reaction effects. /// diff --git a/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs b/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs deleted file mode 100644 index 7fce663dc3..0000000000 --- a/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using JetBrains.Annotations; - -namespace Content.Server.Atmos.Reactions; - -/// -/// Decomposes Nitrous Oxide into Nitrogen and Oxygen. -/// -[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 index c52b431fd4..0000000000 --- a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs +++ /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; - } - } -} diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 7765832ee4..39b24de746 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -195,30 +195,6 @@ namespace Content.Shared.Atmos public const float PlasmaOxygenFullburn = 10f; public const float PlasmaBurnRateDelta = 9f; - /// - /// This is calculated to help prevent singlecap bombs (Overpowered tritium/oxygen single tank bombs) - /// - public const float MinimumTritiumOxyburnEnergy = 143000f; - - public const float TritiumBurnOxyFactor = 100f; - public const float TritiumBurnTritFactor = 10f; - - public const float FrezonCoolLowerTemperature = 23.15f; - - /// - /// Frezon cools better at higher temperatures. - /// - public const float FrezonCoolMidTemperature = 373.15f; - - public const float FrezonCoolMaximumEnergyModifier = 10f; - - /// - /// Remove X mol of nitrogen for each mol of frezon. - /// - public const float FrezonNitrogenCoolRatio = 5; - public const float FrezonCoolEnergyReleased = -600e3f; - public const float FrezonCoolRateModifier = 20f; - public const float FrezonProductionMaxEfficiencyTemperature = 73.15f; /// @@ -236,16 +212,6 @@ namespace Content.Shared.Atmos /// public const float FrezonProductionConversionRate = 50f; - /// - /// The maximum portion of the N2O that can decompose each reaction tick. (50%) - /// - public const float N2ODecompositionRate = 2f; - - /// - /// Divisor for Ammonia Oxygen reaction so that it doesn't happen instantaneously. - /// - public const float AmmoniaOxygenReactionRate = 10f; - /// /// Determines at what pressure the ultra-high pressure red icon is displayed. /// diff --git a/Resources/Prototypes/Atmospherics/reactions.yml b/Resources/Prototypes/Atmospherics/reactions.yml index d226c81f6c..03c563b125 100644 --- a/Resources/Prototypes/Atmospherics/reactions.yml +++ b/Resources/Prototypes/Atmospherics/reactions.yml @@ -14,31 +14,23 @@ 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 @@ -61,35 +53,22 @@ 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