From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:46:38 +0000 (-0700) Subject: Fix Respirator Asserts (#38911) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=535646aefbfe4ed194b60a0877ca939e1e826424;p=space-station-14.git 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> --- 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);