]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Fix Respirator Asserts (#38911)
authorPrincess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Thu, 17 Jul 2025 16:46:38 +0000 (09:46 -0700)
committerGitHub <noreply@github.com>
Thu, 17 Jul 2025 16:46:38 +0000 (12:46 -0400)
* Fix errors

* Cleanup CanMetabolizeInhaledAir

* Wait no don't do that

* Revert changes for real

* Fix

* Code cleanup and some safety rails

* Better tests and also comments

* Better comments

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Content.Server/Body/Components/RespiratorComponent.cs
Content.Server/Body/Systems/InternalsSystem.cs
Content.Server/Body/Systems/RespiratorSystem.cs

index 24f4ae85e1bad0f47180213713097396e3520025..2ba7b04b3e9e2ec24fbd096c417a07a1aad3d8f7 100644 (file)
@@ -10,16 +10,6 @@ namespace Content.Server.Body.Components
     [RegisterComponent, Access(typeof(RespiratorSystem)), AutoGenerateComponentPause]
     public sealed partial class RespiratorComponent : Component
     {
-        /// <summary>
-        ///     Gas container for this entity
-        /// </summary>
-        [DataField]
-        public GasMixture Air = new()
-        {
-            Volume = 6, // 6 liters, the average lung capacity for a human according to Google
-            Temperature = Atmospherics.NormalBodyTemperature
-        };
-
         /// <summary>
         ///     Volume of our breath in liters
         /// </summary>
index 72c91fe2bea7f567322b835639536705802dc98e..77f17b384de797168481e0367b8eef6f7a75a70a 100644 (file)
@@ -48,7 +48,7 @@ public sealed class InternalsSystem : SharedInternalsSystem
             return;
 
         // Could the entity metabolise the air in the linked gas tank?
-        if (!_respirator.CanMetabolizeGas(uid, tank.Value.Comp.Air))
+        if (!_respirator.CanMetabolizeInhaledAir(uid, tank.Value.Comp.Air))
             return;
 
         ToggleInternals(uid, uid, force: false, component, ToggleMode.On);
index 4e21236a778d951242577cf916da80a3d5651ca0..c327f235de893cf1c0440fedb58e4852fbce3b9b 100644 (file)
@@ -49,8 +49,13 @@ public sealed class RespiratorSystem : EntitySystem
         UpdatesAfter.Add(typeof(MetabolizerSystem));
         SubscribeLocalEvent<RespiratorComponent, MapInitEvent>(OnMapInit);
         SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
+
+        // BodyComp stuff
         SubscribeLocalEvent<BodyComponent, InhaledGasEvent>(OnGasInhaled);
         SubscribeLocalEvent<BodyComponent, ExhaledGasEvent>(OnGasExhaled);
+        SubscribeLocalEvent<BodyComponent, CanMetabolizeGasEvent>(CanBodyMetabolizeGas);
+        SubscribeLocalEvent<BodyComponent, SuffocationEvent>(OnSuffocation);
+        SubscribeLocalEvent<BodyComponent, StopSuffocatingEvent>(OnStopSuffocating);
     }
 
     private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
@@ -80,11 +85,11 @@ public sealed class RespiratorSystem : EntitySystem
                 switch (respirator.Status)
                 {
                     case RespiratorStatus.Inhaling:
-                        Inhale(uid);
+                        Inhale((uid, respirator));
                         respirator.Status = RespiratorStatus.Exhaling;
                         break;
                     case RespiratorStatus.Exhaling:
-                        Exhale(uid);
+                        Exhale((uid, respirator));
                         respirator.Status = RespiratorStatus.Inhaling;
                         break;
                 }
@@ -111,10 +116,10 @@ public sealed class RespiratorSystem : EntitySystem
         }
     }
 
-    public bool Inhale(Entity<RespiratorComponent?> entity)
+    public void Inhale(Entity<RespiratorComponent?> entity)
     {
         if (!Resolve(entity, ref entity.Comp, logMissing: false))
-            return false;
+            return;
 
         // Inhale gas
         var ev = new InhaleLocationEvent
@@ -126,23 +131,22 @@ public sealed class RespiratorSystem : EntitySystem
         ev.Gas ??= _atmosSys.GetContainingMixture(entity.Owner, excite: true);
 
         if (ev.Gas is null)
-        {
-            return false;
-        }
+            return;
 
         var gas = ev.Gas.RemoveVolume(entity.Comp.BreathVolume);
 
         var inhaleEv = new InhaledGasEvent(gas);
         RaiseLocalEvent(entity, ref inhaleEv);
 
-        return inhaleEv.Handled && inhaleEv.Succeeded;
+        if (inhaleEv.Handled && inhaleEv.Succeeded)
+            return;
+
+        // If nothing could inhale the gas give it back.
+        _atmosSys.Merge(ev.Gas, gas);
     }
 
-    public void Exhale(Entity<RespiratorComponent?> entity)
+    public void Exhale(Entity<RespiratorComponent> entity)
     {
-        if (!Resolve(entity, ref entity.Comp, logMissing: false))
-            return;
-
         // exhale gas
 
         var ev = new ExhaleLocationEvent();
@@ -157,8 +161,16 @@ public sealed class RespiratorSystem : EntitySystem
             ev.Gas ??= GasMixture.SpaceGas;
         }
 
-        var exhaleEv = new ExhaledGasEvent(ev.Gas);
-        RaiseLocalEvent(entity, ref exhaleEv);
+        Exhale(entity!, ev.Gas);
+    }
+
+    public void Exhale(Entity<RespiratorComponent?> entity, GasMixture gas)
+    {
+        if (!Resolve(entity, ref entity.Comp, logMissing: false))
+            return;
+
+        var ev = new ExhaledGasEvent(gas);
+        RaiseLocalEvent(entity, ref ev);
     }
 
     /// <summary>
@@ -176,52 +188,76 @@ public sealed class RespiratorSystem : EntitySystem
     }
 
     /// <summary>
-    /// Check whether or not an entity can metabolize inhaled air without suffocating or taking damage (i.e., no toxic
-    /// gasses).
+    /// Checks if it's safe for a given entity to breathe the air from the environment it is currently situated in.
     /// </summary>
+    /// <param name="ent">The entity attempting to metabolize the gas.</param>
+    /// <returns>Returns true only if the air is not toxic, and it wouldn't suffocate.</returns>
     public bool CanMetabolizeInhaledAir(Entity<RespiratorComponent?> ent)
     {
         if (!Resolve(ent, ref ent.Comp))
             return false;
 
-        if (!Inhale(ent))
-            return false;
+        // Get the gas at our location but don't actually remove it from the gas mixture.
+        var ev = new InhaleLocationEvent
+        {
+            Respirator = ent.Comp,
+        };
+        RaiseLocalEvent(ent, ref ev);
 
-        // If we don't have a body we can't be poisoned by gas, yet...
-        var success = TryMetabolizeGas((ent, ent.Comp));
+        ev.Gas ??= _atmosSys.GetContainingMixture(ent.Owner, excite: true);
 
-        // Don't keep that gas in our lungs lest it poisons a poor nuclear operative.
-        Exhale(ent);
-        return success;
+        // If there's no air to breathe or we can't metabolize it then internals should be on.
+        return ev.Gas is not null && CanMetabolizeInhaledAir(ent, ev.Gas);
     }
 
     /// <summary>
-    /// Check whether or not an entity can metabolize the given gas mixture without suffocating or taking damage
-    /// (i.e., no toxic gasses).
+    /// Checks if a given entity can safely metabolize a given gas mixture.
     /// </summary>
-    public bool CanMetabolizeGas(Entity<RespiratorComponent?> ent, GasMixture gas)
+    /// <param name="ent">The entity attempting to metabolize the gas.</param>
+    /// <param name="gas">The gas mixture we are trying to metabolize.</param>
+    /// <returns>Returns true only if the gas mixture is not toxic, and it wouldn't suffocate.</returns>
+    public bool CanMetabolizeInhaledAir(Entity<RespiratorComponent?> ent, GasMixture gas)
     {
         if (!Resolve(ent, ref ent.Comp))
             return false;
 
+        var ev = new CanMetabolizeGasEvent(gas);
+        RaiseLocalEvent(ent, ref ev);
+
+        if (!ev.Handled || ev.Toxic)
+            return false;
+
+        return ev.Saturation > ent.Comp.UpdateInterval.TotalSeconds;
+    }
+
+    /// <summary>
+    /// Tries to safely metabolize the current solutions in a body's lungs.
+    /// </summary>
+    private void CanBodyMetabolizeGas(Entity<BodyComponent> ent, ref CanMetabolizeGasEvent args)
+    {
+        if (args.Handled)
+            return;
+
         var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
         if (organs.Count == 0)
-            return false;
+            return;
 
-        gas = new GasMixture(gas);
-        var lungRatio = 1.0f / organs.Count;
-        gas.Multiply(MathF.Min(lungRatio * gas.Volume / ent.Comp.BreathVolume, lungRatio));
-        var solution = _lungSystem.GasToReagent(gas);
+        var solution = _lungSystem.GasToReagent(args.Gas);
 
-        float saturation = 0;
+        var saturation = 0f;
         foreach (var organ in organs)
         {
             saturation += GetSaturation(solution, organ.Owner, out var toxic);
-            if (toxic)
-                return false;
+            if (!toxic)
+                continue;
+
+            args.Handled = true;
+            args.Toxic = true;
+            return;
         }
 
-        return saturation > ent.Comp.UpdateInterval.TotalSeconds;
+        args.Handled = true;
+        args.Saturation = saturation;
     }
 
     public bool TryInhaleGasToBody(Entity<BodyComponent?> entity, GasMixture gas)
@@ -265,30 +301,6 @@ public sealed class RespiratorSystem : EntitySystem
         _atmosSys.Merge(gas, outGas);
     }
 
-    /// <summary>
-    /// Tries to safely metabolize the current solutions in a body's lungs.
-    /// </summary>
-    private bool TryMetabolizeGas(Entity<RespiratorComponent, BodyComponent?> ent)
-    {
-        if (!Resolve(ent, ref ent.Comp2))
-            return false;
-
-        var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
-        if (organs.Count == 0)
-            return false;
-
-        float saturation = 0;
-        foreach (var organ in organs)
-        {
-            var solution = _lungSystem.GasToReagent(organ.Comp1.Air);
-            saturation += GetSaturation(solution, organ.Owner, out var toxic);
-            if (toxic)
-                return false;
-        }
-
-        return saturation > ent.Comp1.UpdateInterval.TotalSeconds;
-    }
-
     /// <summary>
     /// Get the amount of saturation that would be generated if the lung were to metabolize the given solution.
     /// </summary>
@@ -354,15 +366,11 @@ public sealed class RespiratorSystem : EntitySystem
 
         _damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false);
 
-        if (ent.Comp.SuffocationCycles >= ent.Comp.SuffocationCycleThreshold)
-        {
-            // TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
-            var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
-            foreach (var entity in organs)
-            {
-                _alertsSystem.ShowAlert(ent, entity.Comp1.Alert);
-            }
-        }
+        if (ent.Comp.SuffocationCycles < ent.Comp.SuffocationCycleThreshold)
+            return;
+
+        var ev = new SuffocationEvent();
+        RaiseLocalEvent(ent, ref ev);
     }
 
     private void StopSuffocation(Entity<RespiratorComponent> ent)
@@ -372,6 +380,22 @@ public sealed class RespiratorSystem : EntitySystem
 
         _damageableSys.TryChangeDamage(ent, ent.Comp.DamageRecovery);
 
+        var ev = new StopSuffocatingEvent();
+        RaiseLocalEvent(ent, ref ev);
+    }
+
+    private void OnSuffocation(Entity<BodyComponent> ent, ref SuffocationEvent args)
+    {
+        // TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
+        var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
+        foreach (var entity in organs)
+        {
+            _alertsSystem.ShowAlert(ent, entity.Comp1.Alert);
+        }
+    }
+
+    private void OnStopSuffocating(Entity<BodyComponent> ent, ref StopSuffocatingEvent args)
+    {
         // TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
         var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((ent, null));
         foreach (var entity in organs)
@@ -397,6 +421,9 @@ public sealed class RespiratorSystem : EntitySystem
 
     private void OnGasInhaled(Entity<BodyComponent> entity, ref InhaledGasEvent args)
     {
+        if (args.Handled)
+            return;
+
         args.Handled = true;
 
         args.Succeeded = TryInhaleGasToBody((entity, entity.Comp), args.Gas);
@@ -404,20 +431,65 @@ public sealed class RespiratorSystem : EntitySystem
 
     private void OnGasExhaled(Entity<BodyComponent> entity, ref ExhaledGasEvent args)
     {
+        if (args.Handled)
+            return;
+
         args.Handled = true;
 
         RemoveGasFromBody(entity, args.Gas);
     }
 }
 
+/// <summary>
+/// Event raised when an entity first tries to inhale that returns a GasMixture from a given location.
+/// </summary>
+/// <param name="Gas">The gas that gets returned, null if there is none.</param>
+/// <param name="Respirator">The Respirator component of the entity attempting to inhale</param>
 [ByRefEvent]
 public record struct InhaleLocationEvent(GasMixture? Gas, RespiratorComponent Respirator);
 
+/// <summary>
+/// Event raised when an entity first tries to exhale a gas, determines where the gas they're exhaling will be sent.
+/// </summary>
+/// <param name="Gas">The gas mixture that the exhaled gas will be merged into.</param>
 [ByRefEvent]
 public record struct ExhaleLocationEvent(GasMixture? Gas);
 
+/// <summary>
+/// Event raised when an entity successfully inhales a gas, attempts to find a place to put the gas.
+/// </summary>
+/// <param name="Gas">The gas we're inhaling.</param>
+/// <param name="Handled">Whether a system has responded appropriately.</param>
+/// <param name="Succeeded">Whether we successfully managed to inhale the gas</param>
 [ByRefEvent]
 public record struct InhaledGasEvent(GasMixture Gas, bool Handled = false, bool Succeeded = false);
 
+/// <summary>
+/// Event raised when an entity is exhaling
+/// </summary>
+/// <param name="Gas">The gas mixture we're exhaling into.</param>
+/// <param name="Handled">Whether we have successfully exhaled or not.</param>
 [ByRefEvent]
 public record struct ExhaledGasEvent(GasMixture Gas, bool Handled = false);
+
+/// <summary>
+/// Raised when an entity starts suffocating and when suffocation progresses.
+/// </summary>
+[ByRefEvent]
+public record struct SuffocationEvent;
+
+/// <summary>
+/// Raised when an entity that was suffocating stops suffocating.
+/// </summary>
+[ByRefEvent]
+public record struct StopSuffocatingEvent;
+
+/// <summary>
+/// An event raised to inhalation handlers that asks them nicely to simulate what it would be like to metabolize
+/// a given volume of gas, without actually metabolizing it.
+/// </summary>
+/// <param name="Gas">The gas mixture we are testing.</param>
+/// <param name="Toxic">Whether the gas returns as toxic to any respirator.</param>
+/// <param name="Saturation">The amount of saturation we got from the gas.</param>
+[ByRefEvent]
+public record struct CanMetabolizeGasEvent(GasMixture Gas, bool Toxic = false, float Saturation = 0f, bool Handled = false);