--- /dev/null
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using Content.IntegrationTests;
+using Content.IntegrationTests.Pair;
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos;
+using Robust.Shared;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+
+namespace Content.Benchmarks;
+
+[Virtual]
+[GcServer(true)]
+[MemoryDiagnoser]
+public class HeatCapacityBenchmark
+{
+ private TestPair _pair = default!;
+ private IEntityManager _sEntMan = default!;
+ private IEntityManager _cEntMan = default!;
+ private Client.Atmos.EntitySystems.AtmosphereSystem _cAtmos = default!;
+ private AtmosphereSystem _sAtmos = default!;
+ private GasMixture _mix;
+
+ [GlobalSetup]
+ public async Task SetupAsync()
+ {
+ ProgramShared.PathOffset = "../../../../";
+ PoolManager.Startup();
+ _pair = await PoolManager.GetServerClient();
+ await _pair.Connect();
+ _cEntMan = _pair.Client.ResolveDependency<IEntityManager>();
+ _sEntMan = _pair.Server.ResolveDependency<IEntityManager>();
+ _cAtmos = _cEntMan.System<Client.Atmos.EntitySystems.AtmosphereSystem>();
+ _sAtmos = _sEntMan.System<AtmosphereSystem>();
+
+ const float volume = 2500f;
+ const float temperature = 293.15f;
+
+ const float o2 = 12.3f;
+ const float n2 = 45.6f;
+ const float co2 = 0.42f;
+ const float plasma = 0.05f;
+
+ _mix = new GasMixture(volume) { Temperature = temperature };
+
+ _mix.AdjustMoles(Gas.Oxygen, o2);
+ _mix.AdjustMoles(Gas.Nitrogen, n2);
+ _mix.AdjustMoles(Gas.CarbonDioxide, co2);
+ _mix.AdjustMoles(Gas.Plasma, plasma);
+ }
+
+ [Benchmark]
+ public async Task ClientHeatCapacityBenchmark()
+ {
+ await _pair.Client.WaitPost(delegate
+ {
+ for (var i = 0; i < 10000; i++)
+ {
+ _cAtmos.GetHeatCapacity(_mix, applyScaling: true);
+ }
+ });
+ }
+
+ [Benchmark]
+ public async Task ServerHeatCapacityBenchmark()
+ {
+ await _pair.Server.WaitPost(delegate
+ {
+ for (var i = 0; i < 10000; i++)
+ {
+ _sAtmos.GetHeatCapacity(_mix, applyScaling: true);
+ }
+ });
+ }
+
+ [GlobalCleanup]
+ public async Task CleanupAsync()
+ {
+ await _pair.DisposeAsync();
+ PoolManager.Shutdown();
+ }
+}
--- /dev/null
+using System.Runtime.CompilerServices;
+using Content.Shared.Atmos;
+
+namespace Content.Client.Atmos.EntitySystems;
+
+public sealed partial class AtmosphereSystem
+{
+ /*
+ Partial class for operations involving GasMixtures.
+
+ Any method that is overridden here is usually because the server-sided implementation contains
+ code that would escape sandbox. As such these methods are overridden here with a safe
+ implementation.
+ */
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override float GetHeatCapacityCalculation(float[] moles, bool space)
+ {
+ // Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
+ if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
+ {
+ return Atmospherics.SpaceHeatCapacity;
+ }
+
+ // explicit stackalloc call is banned on client tragically.
+ // the JIT does not stackalloc this during runtime,
+ // though this isnt the hottest code path so it should be fine
+ // the gc can eat a little as a treat
+ var tmp = new float[moles.Length];
+ NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp);
+ // Adjust heat capacity by speedup, because this is primarily what
+ // determines how quickly gases heat up/cool.
+ return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity);
+ }
+}
namespace Content.Client.Atmos.EntitySystems;
-public sealed class AtmosphereSystem : SharedAtmosphereSystem
+public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
{
public override void Initialize()
{
public abstract class AtmosTest : InteractionTest
{
protected AtmosphereSystem SAtmos = default!;
+ protected Content.Client.Atmos.EntitySystems.AtmosphereSystem CAtmos = default!;
protected EntityLookupSystem LookupSystem = default!;
protected Entity<GridAtmosphereComponent> RelevantAtmos;
await base.Setup();
SAtmos = SEntMan.System<AtmosphereSystem>();
+ CAtmos = CEntMan.System<Content.Client.Atmos.EntitySystems.AtmosphereSystem>();
LookupSystem = SEntMan.System<EntityLookupSystem>();
SEntMan.TryGetComponent<GridAtmosphereComponent>(MapData.Grid, out var gridAtmosComp);
--- /dev/null
+using Content.Client.Atmos.EntitySystems;
+using Content.IntegrationTests.Pair;
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.EntitySystems;
+using Content.Shared.CCVar;
+using Robust.Shared.Configuration;
+using Robust.Shared.GameObjects;
+using Robust.UnitTesting;
+
+namespace Content.IntegrationTests.Tests.Atmos;
+
+/// <summary>
+/// Tests for asserting that various gas specific heat operations agree with each other and do not deviate
+/// across client and server.
+/// </summary>
+[TestOf(nameof(SharedAtmosphereSystem))]
+public sealed class SharedGasSpecificHeatsTest
+{
+ private IConfigurationManager _sConfig;
+ private IConfigurationManager _cConfig;
+
+ private TestPair _pair = default!;
+
+ private RobustIntegrationTest.ServerIntegrationInstance Server => _pair.Server;
+ private RobustIntegrationTest.ClientIntegrationInstance Client => _pair.Client;
+
+ private IEntityManager _sEntMan = default!;
+ private Content.Server.Atmos.EntitySystems.AtmosphereSystem _sAtmos = default!;
+
+ private IEntityManager _cEntMan = default!;
+ private AtmosphereSystem _cAtmos = default!;
+
+ [SetUp]
+ public async Task SetUp()
+ {
+ var poolSettings = new PoolSettings
+ {
+ Connected = true,
+ };
+ _pair = await PoolManager.GetServerClient(poolSettings);
+
+ _sEntMan = Server.ResolveDependency<IEntityManager>();
+ _cEntMan = Client.ResolveDependency<IEntityManager>();
+
+ _sAtmos = _sEntMan.System<Content.Server.Atmos.EntitySystems.AtmosphereSystem>();
+ _cAtmos = _cEntMan.System<AtmosphereSystem>();
+ }
+
+ /// <summary>
+ /// Asserts that the cached gas specific heat arrays agree with each other.
+ /// </summary>
+ [Test]
+ public async Task GasSpecificHeats_Agree()
+ {
+ var serverSpecificHeats = Array.Empty<float>();
+ var clientSpecificHeats = Array.Empty<float>();
+ await Server.WaitPost(delegate
+ {
+ serverSpecificHeats = _sAtmos.GasSpecificHeats;
+ });
+
+ await Client.WaitPost(delegate
+ {
+ clientSpecificHeats = _cAtmos.GasSpecificHeats;
+ });
+
+ Assert.That(serverSpecificHeats,
+ Is.EqualTo(clientSpecificHeats),
+ "Server and client gas specific heat arrays do not agree.");
+ }
+
+ /// <summary>
+ /// Asserts that heat capacity calculations agree for the same gas mixture.
+ /// </summary>
+ [Test]
+ public async Task HeatCapacity_Agree()
+ {
+ const float volume = 2500f;
+ const float temperature = 293.15f;
+
+ const float o2 = 12.3f;
+ const float n2 = 45.6f;
+ const float co2 = 0.42f;
+ const float plasma = 0.05f;
+
+ var serverScaled = 0f;
+ var serverUnscaled = 0f;
+ var clientScaled = 0f;
+ var clientUnscaled = 0f;
+
+ await Server.WaitPost(delegate
+ {
+ var mix = new GasMixture(volume) { Temperature = temperature };
+ mix.AdjustMoles(Gas.Oxygen, o2);
+ mix.AdjustMoles(Gas.Nitrogen, n2);
+ mix.AdjustMoles(Gas.CarbonDioxide, co2);
+ mix.AdjustMoles(Gas.Plasma, plasma);
+
+ serverScaled = _sAtmos.GetHeatCapacity(mix, applyScaling: true);
+ serverUnscaled = _sAtmos.GetHeatCapacity(mix, applyScaling: false);
+ });
+
+ await Client.WaitPost(delegate
+ {
+ var mix = new GasMixture(volume) { Temperature = temperature };
+ mix.AdjustMoles(Gas.Oxygen, o2);
+ mix.AdjustMoles(Gas.Nitrogen, n2);
+ mix.AdjustMoles(Gas.CarbonDioxide, co2);
+ mix.AdjustMoles(Gas.Plasma, plasma);
+
+ clientScaled = _cAtmos.GetHeatCapacity(mix, applyScaling: true);
+ clientUnscaled = _cAtmos.GetHeatCapacity(mix, applyScaling: false);
+ });
+
+ // none of these should be exploding or nonzero.
+ // they could potentially agree at insane values and pass the test
+ // so check for if they're sane.
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(serverScaled,
+ Is.GreaterThan(0f),
+ "Heat capacity calculated on server with scaling is not greater than zero.");
+ Assert.That(serverUnscaled,
+ Is.GreaterThan(0f),
+ "Heat capacity calculated on server without scaling is not greater than zero.");
+ Assert.That(clientScaled,
+ Is.GreaterThan(0f),
+ "Heat capacity calculated on client with scaling is not greater than zero.");
+ Assert.That(clientUnscaled,
+ Is.GreaterThan(0f),
+ "Heat capacity calculated on client without scaling is not greater than zero.");
+
+ Assert.That(float.IsFinite(serverScaled),
+ Is.True,
+ "Heat capacity calculated on server with scaling is not finite.");
+ Assert.That(float.IsFinite(serverUnscaled),
+ Is.True,
+ "Heat capacity calculated on server without scaling is not finite.");
+ Assert.That(float.IsFinite(clientScaled),
+ Is.True,
+ "Heat capacity calculated on client with scaling is not finite.");
+ Assert.That(float.IsFinite(clientUnscaled),
+ Is.True,
+ "Heat capacity calculated on client without scaling is not finite.");
+ }
+
+ const float epsilon = 1e-4f;
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(serverScaled,
+ Is.EqualTo(clientScaled).Within(epsilon),
+ "Heat capacity calculated with scaling does not agree between client and server.");
+ Assert.That(serverUnscaled,
+ Is.EqualTo(clientUnscaled).Within(epsilon),
+ "Heat capacity calculated without scaling does not agree between client and server.");
+
+ Assert.That(serverUnscaled,
+ Is.EqualTo(serverScaled * _sAtmos.HeatScale).Within(epsilon),
+ "Heat capacity calculated on server without scaling does not equal scaled value multiplied by HeatScale.");
+ Assert.That(clientUnscaled,
+ Is.EqualTo(clientScaled * _cAtmos.HeatScale).Within(epsilon),
+ "Heat capacity calculated on client without scaling does not equal scaled value multiplied by HeatScale.");
+ }
+ }
+
+ /// <summary>
+ /// HeatScale CVAR is required for specific heat calculations.
+ /// Assert that they agree across client and server, and that changing the CVAR
+ /// replicates properly and updates the cached value.
+ /// Also assert that calculations using the updated HeatScale agree properly.
+ /// </summary>
+ [Test]
+ public async Task HeatScaleCVar_Replicates_Agree()
+ {
+ // ensure that replicated value changes by testing a new value
+ const float newHeatScale = 13f;
+
+ _sConfig = Server.ResolveDependency<IConfigurationManager>();
+ _cConfig = Client.ResolveDependency<IConfigurationManager>();
+
+ await Server.WaitPost(delegate
+ {
+ _sConfig.SetCVar(CCVars.AtmosHeatScale, newHeatScale);
+ });
+
+ await Server.WaitRunTicks(5);
+ await Client.WaitRunTicks(5);
+
+ // assert agreement between client and server
+ float serverCVar = 0;
+ float clientCVar = 0;
+ float serverHeatScale = 0;
+ float clientHeatScale = 0;
+
+ await Server.WaitPost(delegate
+ {
+ serverCVar = _sConfig.GetCVar(CCVars.AtmosHeatScale);
+ serverHeatScale = _sAtmos.HeatScale;
+ });
+
+ await Client.WaitPost(delegate
+ {
+ clientCVar = _cConfig.GetCVar(CCVars.AtmosHeatScale);
+ clientHeatScale = _cAtmos.HeatScale;
+ });
+
+ const float epsilon = 1e-4f;
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(serverCVar,
+ Is.EqualTo(newHeatScale).Within(epsilon),
+ "Server CVAR value for AtmosHeatScale does not equal the set value.");
+ Assert.That(clientCVar,
+ Is.EqualTo(newHeatScale).Within(epsilon),
+ "Client CVAR value for AtmosHeatScale does not equal the set value.");
+
+ Assert.That(serverHeatScale,
+ Is.EqualTo(newHeatScale).Within(epsilon),
+ "Server cached HeatScale does not equal the set CVAR value.");
+ Assert.That(clientHeatScale,
+ Is.EqualTo(newHeatScale).Within(epsilon),
+ "Client cached HeatScale does not equal the set CVAR value.");
+
+ Assert.That(serverHeatScale,
+ Is.EqualTo(clientHeatScale).Within(epsilon),
+ "Client and server cached HeatScale values do not agree.");
+ }
+
+ // verify that anything calculated using the shared HeatScale agrees properly
+ const float volume = 2500f;
+ const float temperature = 293.15f;
+
+ var sScaled = 0f;
+ var sUnscaled = 0f;
+ var cScaled = 0f;
+ var cUnscaled = 0f;
+
+ await Server.WaitPost(delegate
+ {
+ var mix = new GasMixture(volume) { Temperature = temperature };
+ mix.AdjustMoles(Gas.Oxygen, 10f);
+ mix.AdjustMoles(Gas.Nitrogen, 20f);
+
+ sScaled = _sAtmos.GetHeatCapacity(mix, applyScaling: true);
+ sUnscaled = _sAtmos.GetHeatCapacity(mix, applyScaling: false);
+ });
+
+ await Client.WaitPost(delegate
+ {
+ var mix = new GasMixture(volume) { Temperature = temperature };
+ mix.AdjustMoles(Gas.Oxygen, 10f);
+ mix.AdjustMoles(Gas.Nitrogen, 20f);
+
+ cScaled = _cAtmos.GetHeatCapacity(mix, applyScaling: true);
+ cUnscaled = _cAtmos.GetHeatCapacity(mix, applyScaling: false);
+ });
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(sScaled,
+ Is.GreaterThan(0f),
+ "Heat capacity calculated on server with scaling is not greater than zero after CVAR change.");
+ Assert.That(cScaled,
+ Is.GreaterThan(0f),
+ "Heat capacity calculated on client with scaling is not greater than zero after CVAR change.");
+
+ Assert.That(sUnscaled,
+ Is.EqualTo(sScaled * serverHeatScale).Within(epsilon),
+ "Heat capacity calculated on server without scaling does not equal scaled value multiplied by updated HeatScale.");
+ Assert.That(cUnscaled,
+ Is.EqualTo(cScaled * clientHeatScale).Within(epsilon),
+ "Heat capacity calculated on client without scaling does not equal scaled value multiplied by updated HeatScale.");
+ }
+ }
+}
public float AtmosMaxProcessTime { get; private set; }
public float AtmosTickRate { get; private set; }
public float Speedup { get; private set; }
- public float HeatScale { get; private set; }
public bool DeltaPressureDamage { get; private set; }
public int DeltaPressureParallelProcessPerIteration { get; private set; }
public int DeltaPressureParallelBatchSize { get; private set; }
Subs.CVar(_cfg, CCVars.AtmosMaxProcessTime, value => AtmosMaxProcessTime = value, true);
Subs.CVar(_cfg, CCVars.AtmosTickRate, value => AtmosTickRate = value, true);
Subs.CVar(_cfg, CCVars.AtmosSpeedup, value => Speedup = value, true);
- Subs.CVar(_cfg, CCVars.AtmosHeatScale, value => { HeatScale = value; InitializeGases(); }, true);
Subs.CVar(_cfg, CCVars.ExcitedGroups, value => ExcitedGroups = value, true);
Subs.CVar(_cfg, CCVars.ExcitedGroupsSpaceIsAllConsuming, value => ExcitedGroupsSpaceIsAllConsuming = value, true);
Subs.CVar(_cfg, CCVars.DeltaPressureDamage, value => DeltaPressureDamage = value, true);
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
- private GasReactionPrototype[] _gasReactions = Array.Empty<GasReactionPrototype>();
- private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
+ private GasReactionPrototype[] _gasReactions = [];
/// <summary>
/// List of gas reactions ordered by priority.
/// </summary>
public IEnumerable<GasReactionPrototype> GasReactions => _gasReactions;
- /// <summary>
- /// Cached array of gas specific heats.
- /// </summary>
- public float[] GasSpecificHeats => _gasSpecificHeats;
-
- private void InitializeGases()
+ public override void InitializeGases()
{
+ base.InitializeGases();
+
_gasReactions = _protoMan.EnumeratePrototypes<GasReactionPrototype>().ToArray();
Array.Sort(_gasReactions, (a, b) => b.Priority.CompareTo(a.Priority));
-
- Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4));
-
- for (var i = 0; i < GasPrototypes.Length; i++)
- {
- _gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat / HeatScale;
- }
}
- /// <summary>
- /// Calculates the heat capacity for a gas mixture.
- /// </summary>
- /// <param name="mixture">The mixture whose heat capacity should be calculated</param>
- /// <param name="applyScaling"> Whether the internal heat capacity scaling should be applied. This should not be
- /// used outside of atmospheric related heat transfer.</param>
- /// <returns></returns>
- public float GetHeatCapacity(GasMixture mixture, bool applyScaling)
- {
- var scale = GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
-
- // By default GetHeatCapacityCalculation() has the heat-scale divisor pre-applied.
- // So if we want the un-scaled heat capacity, we have to multiply by the scale.
- return applyScaling ? scale : scale * HeatScale;
- }
-
- private float GetHeatCapacity(GasMixture mixture)
- => GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private float GetHeatCapacityCalculation(float[] moles, bool space)
+ protected override float GetHeatCapacityCalculation(float[] moles, bool space)
{
// Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms.
if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f))
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.CCVar;
+
+namespace Content.Shared.Atmos.EntitySystems;
+
+public abstract partial class SharedAtmosphereSystem
+{
+ /*
+ Partial class for storing shared configuration values.
+ */
+
+ public float HeatScale { get; private set; }
+
+ [SuppressMessage("ReSharper", "BadExpressionBracesLineBreaks")]
+ [SuppressMessage("ReSharper", "MultipleStatementsOnOneLine")]
+ private void InitializeCVars()
+ {
+ Subs.CVar(_cfg, CCVars.AtmosHeatScale, value => { HeatScale = value; InitializeGases(); }, true);
+ }
+}
--- /dev/null
+using System.Runtime.CompilerServices;
+using Content.Shared.Atmos.Prototypes;
+
+namespace Content.Shared.Atmos.EntitySystems;
+
+public abstract partial class SharedAtmosphereSystem
+{
+ /*
+ Partial class for operations involving GasMixtures.
+
+ Sometimes methods here are abstract because they need different client/server implementations
+ due to sandboxing.
+ */
+
+ /// <summary>
+ /// Cached array of gas specific heats.
+ /// </summary>
+ public float[] GasSpecificHeats => _gasSpecificHeats;
+ private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases];
+
+ public string?[] GasReagents = new string[Atmospherics.TotalNumberOfGases];
+ protected readonly GasPrototype[] GasPrototypes = new GasPrototype[Atmospherics.TotalNumberOfGases];
+
+ public virtual void InitializeGases()
+ {
+ foreach (var gas in Enum.GetValues<Gas>())
+ {
+ var idx = (int)gas;
+ // Log an error if the corresponding prototype isn't found
+ if (!_prototypeManager.TryIndex<GasPrototype>(gas.ToString(), out var gasPrototype))
+ {
+ Log.Error($"Failed to find corresponding {nameof(GasPrototype)} for gas ID {(int)gas} ({gas}) with expected ID \"{gas.ToString()}\". Is your prototype named correctly?");
+ continue;
+ }
+ GasPrototypes[idx] = gasPrototype;
+ GasReagents[idx] = gasPrototype.Reagent;
+ }
+
+ Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4));
+
+ for (var i = 0; i < GasPrototypes.Length; i++)
+ {
+ _gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat / HeatScale;
+ }
+ }
+
+ /// <summary>
+ /// Calculates the heat capacity for a gas mixture.
+ /// </summary>
+ /// <param name="mixture">The mixture whose heat capacity should be calculated</param>
+ /// <param name="applyScaling"> Whether the internal heat capacity scaling should be applied. This should not be
+ /// used outside of atmospheric related heat transfer.</param>
+ /// <returns></returns>
+ public float GetHeatCapacity(GasMixture mixture, bool applyScaling)
+ {
+ var scale = GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
+
+ // By default GetHeatCapacityCalculation() has the heat-scale divisor pre-applied.
+ // So if we want the un-scaled heat capacity, we have to multiply by the scale.
+ return applyScaling ? scale : scale * HeatScale;
+ }
+
+ protected float GetHeatCapacity(GasMixture mixture)
+ {
+ return GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable);
+ }
+
+ /// <summary>
+ /// Gets the heat capacity for a <see cref="GasMixture"/>.
+ /// </summary>
+ /// <param name="moles">The moles array of the <see cref="GasMixture"/></param>
+ /// <param name="space">Whether this <see cref="GasMixture"/> represents space,
+ /// and thus experiences space-specific mechanics (we cheat and make it a bit cooler).</param>
+ /// <returns></returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected abstract float GetHeatCapacityCalculation(float[] moles, bool space);
+}
using Content.Shared.Atmos.Prototypes;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
+using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Shared.Atmos.EntitySystems
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInternalsSystem _internals = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
private EntityQuery<InternalsComponent> _internalsQuery;
- public string?[] GasReagents = new string[Atmospherics.TotalNumberOfGases];
-
- protected readonly GasPrototype[] GasPrototypes = new GasPrototype[Atmospherics.TotalNumberOfGases];
-
public override void Initialize()
{
base.Initialize();
_internalsQuery = GetEntityQuery<InternalsComponent>();
InitializeBreathTool();
-
- foreach (var gas in Enum.GetValues<Gas>())
- {
- var idx = (int)gas;
- // Log an error if the corresponding prototype isn't found
- if (!_prototypeManager.TryIndex<GasPrototype>(gas.ToString(), out var gasPrototype))
- {
- Log.Error($"Failed to find corresponding {nameof(GasPrototype)} for gas ID {(int)gas} ({gas}) with expected ID \"{gas.ToString()}\". Is your prototype named correctly?");
- continue;
- }
- GasPrototypes[idx] = gasPrototype;
- GasReagents[idx] = gasPrototype.Reagent;
- }
+ InitializeGases();
+ InitializeCVars();
}
public GasPrototype GetGas(int gasId) => GasPrototypes[gasId];
/// gases heat up and cool down 64x faster than real life.
/// </summary>
public static readonly CVarDef<float> AtmosHeatScale =
- CVarDef.Create("atmos.heat_scale", 8f, CVar.SERVERONLY);
+ CVarDef.Create("atmos.heat_scale", 8f, CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Maximum explosion radius for explosions caused by bursting a gas tank ("max caps").