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];
break;
}
- return _reaction.ReactAll(GasReactions, mixture, holder);
+ return reaction;
}
public enum GasCompareResult
+++ /dev/null
-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;
- }
-}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
public string ID { get; private set; } = default!;
/// <summary>
- /// Minimum gas amount requirements. Reactions that meet these minimum mole requirements
- /// have their reaction effects run. Generic gas reactions do not have minimum requirements.
+ /// Minimum gas amount requirements.
/// </summary>
[DataField("minimumRequirements")]
public float[] MinimumRequirements { get; private set; } = new float[Atmospherics.TotalNumberOfGases];
[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>
/// </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>
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+ }
+}
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>
/// </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>
id: TritiumFire
priority: -1
minimumTemperature: 373.149 # Same as Atmospherics.FireMinimumTemperatureToExist
- enthalpy: 284000
- reactants:
- Tritium: 2
- Oxygen: 1
- products:
- WaterVapor: 2
+ 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 {}
- type: gasReaction
id: FrezonCoolant
priority: 1
minimumTemperature: 23.15
- enthalpy: -600000
- reactants:
- Frezon: 1
- Nitrogen: 5
- products:
- NitrousOxide: 6
+ 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 {}
- type: gasReaction
id: FrezonProduction
id: AmmoniaOxygenReaction
priority: 2
minimumTemperature: 323.149
- reactants:
- Ammonia: 2
- Oxygen: 2
- products:
- NitrousOxide: 1
- WaterVapor: 3
+ 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 {}
- type: gasReaction
id: N2ODecomposition
priority: 0
minimumTemperature: 850
- reactants:
- NitrousOxide: 2
- products:
- Nitrogen: 2
- Oxygen: 1
+ minimumRequirements:
+ - 0 # oxygen
+ - 0 # nitrogen
+ - 0 # carbon dioxide
+ - 0 # plasma
+ - 0 # tritium
+ - 0 # vapor
+ - 0 # ammonia
+ - 0.01 # n2o
+ - 0 # frezon
+ effects:
+ - !type:N2ODecompositionReaction {}
#- type: gasReaction
# id: WaterVaporPuddle