From 535646aefbfe4ed194b60a0877ca939e1e826424 Mon Sep 17 00:00:00 2001
From: Princess Cheeseballs
<66055347+Princess-Cheeseballs@users.noreply.github.com>
Date: Thu, 17 Jul 2025 09:46:38 -0700
Subject: [PATCH] Fix Respirator Asserts (#38911)
* 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>
---
.../Body/Components/RespiratorComponent.cs | 10 -
.../Body/Systems/InternalsSystem.cs | 2 +-
.../Body/Systems/RespiratorSystem.cs | 208 ++++++++++++------
3 files changed, 141 insertions(+), 79 deletions(-)
diff --git a/Content.Server/Body/Components/RespiratorComponent.cs b/Content.Server/Body/Components/RespiratorComponent.cs
index 24f4ae85e1..2ba7b04b3e 100644
--- a/Content.Server/Body/Components/RespiratorComponent.cs
+++ b/Content.Server/Body/Components/RespiratorComponent.cs
@@ -10,16 +10,6 @@ namespace Content.Server.Body.Components
[RegisterComponent, Access(typeof(RespiratorSystem)), AutoGenerateComponentPause]
public sealed partial class RespiratorComponent : Component
{
- ///
- /// Gas container for this entity
- ///
- [DataField]
- public GasMixture Air = new()
- {
- Volume = 6, // 6 liters, the average lung capacity for a human according to Google
- Temperature = Atmospherics.NormalBodyTemperature
- };
-
///
/// Volume of our breath in liters
///
diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs
index 72c91fe2be..77f17b384d 100644
--- a/Content.Server/Body/Systems/InternalsSystem.cs
+++ b/Content.Server/Body/Systems/InternalsSystem.cs
@@ -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);
diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs
index 4e21236a77..c327f235de 100644
--- a/Content.Server/Body/Systems/RespiratorSystem.cs
+++ b/Content.Server/Body/Systems/RespiratorSystem.cs
@@ -49,8 +49,13 @@ public sealed class RespiratorSystem : EntitySystem
UpdatesAfter.Add(typeof(MetabolizerSystem));
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnApplyMetabolicMultiplier);
+
+ // BodyComp stuff
SubscribeLocalEvent(OnGasInhaled);
SubscribeLocalEvent(OnGasExhaled);
+ SubscribeLocalEvent(CanBodyMetabolizeGas);
+ SubscribeLocalEvent(OnSuffocation);
+ SubscribeLocalEvent(OnStopSuffocating);
}
private void OnMapInit(Entity 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 entity)
+ public void Inhale(Entity 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 entity)
+ public void Exhale(Entity 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 entity, GasMixture gas)
+ {
+ if (!Resolve(entity, ref entity.Comp, logMissing: false))
+ return;
+
+ var ev = new ExhaledGasEvent(gas);
+ RaiseLocalEvent(entity, ref ev);
}
///
@@ -176,52 +188,76 @@ public sealed class RespiratorSystem : EntitySystem
}
///
- /// 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.
///
+ /// The entity attempting to metabolize the gas.
+ /// Returns true only if the air is not toxic, and it wouldn't suffocate.
public bool CanMetabolizeInhaledAir(Entity 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);
}
///
- /// 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.
///
- public bool CanMetabolizeGas(Entity ent, GasMixture gas)
+ /// The entity attempting to metabolize the gas.
+ /// The gas mixture we are trying to metabolize.
+ /// Returns true only if the gas mixture is not toxic, and it wouldn't suffocate.
+ public bool CanMetabolizeInhaledAir(Entity 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;
+ }
+
+ ///
+ /// Tries to safely metabolize the current solutions in a body's lungs.
+ ///
+ private void CanBodyMetabolizeGas(Entity ent, ref CanMetabolizeGasEvent args)
+ {
+ if (args.Handled)
+ return;
+
var organs = _bodySystem.GetBodyOrganEntityComps((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 entity, GasMixture gas)
@@ -265,30 +301,6 @@ public sealed class RespiratorSystem : EntitySystem
_atmosSys.Merge(gas, outGas);
}
- ///
- /// Tries to safely metabolize the current solutions in a body's lungs.
- ///
- private bool TryMetabolizeGas(Entity ent)
- {
- if (!Resolve(ent, ref ent.Comp2))
- return false;
-
- var organs = _bodySystem.GetBodyOrganEntityComps((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;
- }
-
///
/// Get the amount of saturation that would be generated if the lung were to metabolize the given solution.
///
@@ -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((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 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 ent, ref SuffocationEvent args)
+ {
+ // TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
+ var organs = _bodySystem.GetBodyOrganEntityComps((ent, null));
+ foreach (var entity in organs)
+ {
+ _alertsSystem.ShowAlert(ent, entity.Comp1.Alert);
+ }
+ }
+
+ private void OnStopSuffocating(Entity ent, ref StopSuffocatingEvent args)
+ {
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
var organs = _bodySystem.GetBodyOrganEntityComps((ent, null));
foreach (var entity in organs)
@@ -397,6 +421,9 @@ public sealed class RespiratorSystem : EntitySystem
private void OnGasInhaled(Entity 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 entity, ref ExhaledGasEvent args)
{
+ if (args.Handled)
+ return;
+
args.Handled = true;
RemoveGasFromBody(entity, args.Gas);
}
}
+///
+/// Event raised when an entity first tries to inhale that returns a GasMixture from a given location.
+///
+/// The gas that gets returned, null if there is none.
+/// The Respirator component of the entity attempting to inhale
[ByRefEvent]
public record struct InhaleLocationEvent(GasMixture? Gas, RespiratorComponent Respirator);
+///
+/// Event raised when an entity first tries to exhale a gas, determines where the gas they're exhaling will be sent.
+///
+/// The gas mixture that the exhaled gas will be merged into.
[ByRefEvent]
public record struct ExhaleLocationEvent(GasMixture? Gas);
+///
+/// Event raised when an entity successfully inhales a gas, attempts to find a place to put the gas.
+///
+/// The gas we're inhaling.
+/// Whether a system has responded appropriately.
+/// Whether we successfully managed to inhale the gas
[ByRefEvent]
public record struct InhaledGasEvent(GasMixture Gas, bool Handled = false, bool Succeeded = false);
+///
+/// Event raised when an entity is exhaling
+///
+/// The gas mixture we're exhaling into.
+/// Whether we have successfully exhaled or not.
[ByRefEvent]
public record struct ExhaledGasEvent(GasMixture Gas, bool Handled = false);
+
+///
+/// Raised when an entity starts suffocating and when suffocation progresses.
+///
+[ByRefEvent]
+public record struct SuffocationEvent;
+
+///
+/// Raised when an entity that was suffocating stops suffocating.
+///
+[ByRefEvent]
+public record struct StopSuffocatingEvent;
+
+///
+/// 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.
+///
+/// The gas mixture we are testing.
+/// Whether the gas returns as toxic to any respirator.
+/// The amount of saturation we got from the gas.
+[ByRefEvent]
+public record struct CanMetabolizeGasEvent(GasMixture Gas, bool Toxic = false, float Saturation = 0f, bool Handled = false);
--
2.52.0