From 0e840d816ace750525329094d9bb2b92d8eb230f Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 18 Jan 2025 00:50:22 +0100 Subject: [PATCH 1/2] Make GasMixture enumerable I noticed that enumerating gases is frequently done in an annoying way with Enum.GetValues. So I made it better. Now GasMixture is IEnumerable<(Gas gas, float moles)> and it just works. --- Content.Shared/Atmos/GasMixture.cs | 45 ++++++++++++++++++-- Content.Tests/Shared/Atmos/GasMixtureTest.cs | 30 +++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 Content.Tests/Shared/Atmos/GasMixtureTest.cs diff --git a/Content.Shared/Atmos/GasMixture.cs b/Content.Shared/Atmos/GasMixture.cs index 0f1efba976..deca8faaed 100644 --- a/Content.Shared/Atmos/GasMixture.cs +++ b/Content.Shared/Atmos/GasMixture.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using Content.Shared.Atmos.EntitySystems; @@ -13,12 +14,12 @@ namespace Content.Shared.Atmos /// [Serializable] [DataDefinition] - public sealed partial class GasMixture : IEquatable, ISerializationHooks + public sealed partial class GasMixture : IEquatable, ISerializationHooks, IEnumerable<(Gas gas, float moles)> { public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true}; // No access, to ensure immutable mixtures are never accidentally mutated. - [Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), Other = AccessPermissions.None)] + [Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), typeof(GasEnumerator), Other = AccessPermissions.None)] [DataField] public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases]; @@ -249,6 +250,16 @@ namespace Content.Shared.Atmos return new GasMixtureStringRepresentation(TotalMoles, Temperature, Pressure, molesPerGas); } + GasEnumerator GetEnumerator() + { + return new GasEnumerator(this); + } + + IEnumerator<(Gas gas, float moles)> IEnumerable<(Gas gas, float moles)>.GetEnumerator() + { + return GetEnumerator(); + } + public override bool Equals(object? obj) { if (obj is GasMixture mix) @@ -289,6 +300,11 @@ namespace Content.Shared.Atmos return hashCode.ToHashCode(); } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + public GasMixture Clone() { if (Immutable) @@ -302,5 +318,28 @@ namespace Content.Shared.Atmos }; return newMixture; } + + public struct GasEnumerator(GasMixture mixture) : IEnumerator<(Gas gas, float moles)> + { + private int _idx = -1; + + public void Dispose() + { + // Nada. + } + + public bool MoveNext() + { + return ++_idx < Atmospherics.TotalNumberOfGases; + } + + public void Reset() + { + _idx = -1; + } + + public (Gas gas, float moles) Current => ((Gas)_idx, mixture.Moles[_idx]); + object? IEnumerator.Current => Current; + } } } diff --git a/Content.Tests/Shared/Atmos/GasMixtureTest.cs b/Content.Tests/Shared/Atmos/GasMixtureTest.cs new file mode 100644 index 0000000000..5068d48581 --- /dev/null +++ b/Content.Tests/Shared/Atmos/GasMixtureTest.cs @@ -0,0 +1,30 @@ +using Content.Shared.Atmos; +using NUnit.Framework; + +namespace Content.Tests.Shared.Atmos; + +[TestFixture, TestOf(typeof(GasMixture))] +[Parallelizable(ParallelScope.All)] +public sealed class GasMixtureTest +{ + [Test] + public void TestEnumerate() + { + var mixture = new GasMixture(); + mixture.SetMoles(Gas.Oxygen, 20); + mixture.SetMoles(Gas.Nitrogen, 10); + mixture.SetMoles(Gas.Plasma, 80); + + var expectedList = new (Gas, float)[Atmospherics.TotalNumberOfGases]; + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) + { + expectedList[i].Item1 = (Gas)i; + } + + expectedList[(int)Gas.Oxygen].Item2 = 20f; + expectedList[(int)Gas.Nitrogen].Item2 = 10f; + expectedList[(int)Gas.Plasma].Item2 = 80f; + + Assert.That(mixture, Is.EquivalentTo(expectedList)); + } +} From f63eb691f0bf961ffd3d3256b76e3ef17ad0ee03 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 18 Jan 2025 00:51:32 +0100 Subject: [PATCH 2/2] Improve canister admin logs. 1. Now clearly says "opened"/"closed" when changing the release valve. 2. Clearly says whether the valve was opened while a canister was inserted or not. 3. When a tank is ejected, logs if the valve is open and the ejection started spilling into the environment. Fixes #34488 --- .../Unary/EntitySystems/GasCanisterSystem.cs | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index 584928def7..292b6d94f8 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -109,7 +109,15 @@ public sealed class GasCanisterSystem : EntitySystem var item = canister.GasTankSlot.Item; _slots.TryEjectToHands(uid, canister.GasTankSlot, args.Actor); - _adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}"); + + if (canister.ReleaseValve) + { + _adminLogger.Add(LogType.CanisterTankEjected, LogImpact.High, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister} while the valve was open, releasing [{GetContainedGasesString((uid, canister))}] to atmosphere"); + } + else + { + _adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}"); + } } private void OnCanisterChangeReleasePressure(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleasePressureMessage args) @@ -124,24 +132,24 @@ public sealed class GasCanisterSystem : EntitySystem private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleaseValveMessage args) { - var impact = LogImpact.High; // filling a jetpack with plasma is less important than filling a room with it - impact = canister.GasTankSlot.HasItem ? LogImpact.Medium : LogImpact.High; + var hasItem = canister.GasTankSlot.HasItem; + var impact = hasItem ? LogImpact.Medium : LogImpact.High; - var containedGasDict = new Dictionary(); - var containedGasArray = Enum.GetValues(typeof(Gas)); - - for (int i = 0; i < containedGasArray.Length; i++) - { - containedGasDict.Add((Gas)i, canister.Air[i]); - } - - _adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Actor):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]"); + _adminLogger.Add( + LogType.CanisterValve, + impact, + $"{ToPrettyString(args.Actor):player} {(args.Valve ? "opened" : "closed")} the valve on {ToPrettyString(uid):canister} to {(hasItem ? "inserted tank" : "environment")} while it contained [{GetContainedGasesString((uid, canister))}]"); canister.ReleaseValve = args.Valve; DirtyUI(uid, canister); } + private static string GetContainedGasesString(Entity canister) + { + return string.Join(", ", canister.Comp.Air); + } + private void OnCanisterUpdated(EntityUid uid, GasCanisterComponent canister, ref AtmosDeviceUpdateEvent args) { _atmos.React(canister.Air, canister);