From d7d83bd87c590a559035c23a41ebca27cba23117 Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Thu, 26 Jun 2025 19:33:43 -0700 Subject: [PATCH] RespiratorSystem Cleanup (#38572) * Respirator Debodied * Forgot about alerts (also respirator testa and events) * Fix Urist eating air and not giving it back * Stop nuke ops from taking in a breath then taking in a second breath causing them to get a headache from carbon dioxide poisoning and failing TryStopNukeOpsFromConstantlyFailing(); * Consts are smelly, * Actually we don't need to raise the entity, just the component * Don't forget to remove the unused code today, said me yesterday * Remove all fallbacks * Debody that too --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> --- .../Body/Components/RespiratorComponent.cs | 24 +++ .../Body/Systems/InternalsSystem.cs | 3 +- Content.Server/Body/Systems/LungSystem.cs | 3 + .../Body/Systems/RespiratorSystem.cs | 182 +++++++++++++----- 4 files changed, 158 insertions(+), 54 deletions(-) diff --git a/Content.Server/Body/Components/RespiratorComponent.cs b/Content.Server/Body/Components/RespiratorComponent.cs index a81062362a..19585e9f00 100644 --- a/Content.Server/Body/Components/RespiratorComponent.cs +++ b/Content.Server/Body/Components/RespiratorComponent.cs @@ -1,4 +1,6 @@ using Content.Server.Body.Systems; +using Content.Shared.Alert; +using Content.Shared.Atmos; using Content.Shared.Chat.Prototypes; using Content.Shared.Damage; using Robust.Shared.Prototypes; @@ -9,6 +11,28 @@ namespace Content.Server.Body.Components [RegisterComponent, Access(typeof(RespiratorSystem))] 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 + /// + [DataField] + public float BreathVolume = Atmospherics.BreathVolume; + + /// + /// How much of the gas we inhale is metabolized? Value range is (0, 1] + /// + [DataField] + public float Ratio = 1.0f; + /// /// The next time that this body will inhale or exhale. /// diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index 93dedf5fff..72c91fe2be 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.EntitySystems; +using Content.Server.Body.Components; using Content.Server.Popups; using Content.Shared.Alert; using Content.Shared.Atmos; @@ -58,7 +59,7 @@ public sealed class InternalsSystem : SharedInternalsSystem if (AreInternalsWorking(ent)) { var gasTank = Comp(ent.Comp.GasTankEntity!.Value); - args.Gas = _gasTank.RemoveAirVolume((ent.Comp.GasTankEntity.Value, gasTank), Atmospherics.BreathVolume); + args.Gas = _gasTank.RemoveAirVolume((ent.Comp.GasTankEntity.Value, gasTank), args.Respirator.BreathVolume); // TODO: Should listen to gas tank updates instead I guess? _alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent)); } diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index 273a8466ca..fdc071c5f1 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -63,6 +63,9 @@ public sealed class LungSystem : EntitySystem _solutionContainerSystem.UpdateChemicals(lung.Solution.Value); } + /* This should really be moved to somewhere in the atmos system and modernized, + so that other systems, like CondenserSystem, can use it. + */ private void GasToReagent(GasMixture gas, Solution solution) { foreach (var gasId in Enum.GetValues()) diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 1b4cf4d698..bc57108720 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -50,6 +50,8 @@ public sealed class RespiratorSystem : EntitySystem SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnApplyMetabolicMultiplier); + SubscribeLocalEvent(OnGasInhaled); + SubscribeLocalEvent(OnGasExhaled); } private void OnMapInit(Entity ent, ref MapInitEvent args) @@ -77,18 +79,18 @@ public sealed class RespiratorSystem : EntitySystem if (_mobState.IsDead(uid)) continue; - UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator); + UpdateSaturation(uid, -(float)respirator.UpdateInterval.TotalSeconds, respirator); if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit. { switch (respirator.Status) { case RespiratorStatus.Inhaling: - Inhale(uid, body); + Inhale(uid); respirator.Status = RespiratorStatus.Exhaling; break; case RespiratorStatus.Exhaling: - Exhale(uid, body); + Exhale(uid); respirator.Status = RespiratorStatus.Inhaling; break; } @@ -99,7 +101,10 @@ public sealed class RespiratorSystem : EntitySystem if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown) { respirator.LastGaspEmoteTime = _gameTiming.CurTime; - _chat.TryEmoteWithChat(uid, respirator.GaspEmote, ChatTransmitRange.HideChat, ignoreActionBlocker: true); + _chat.TryEmoteWithChat(uid, + respirator.GaspEmote, + ChatTransmitRange.HideChat, + ignoreActionBlocker: true); } TakeSuffocationDamage((uid, respirator)); @@ -112,68 +117,54 @@ public sealed class RespiratorSystem : EntitySystem } } - public void Inhale(EntityUid uid, BodyComponent? body = null) + public bool Inhale(Entity entity) { - if (!Resolve(uid, ref body, logMissing: false)) - return; - - var organs = _bodySystem.GetBodyOrganEntityComps((uid, body)); + if (!Resolve(entity, ref entity.Comp, logMissing: false)) + return false; // Inhale gas - var ev = new InhaleLocationEvent(); - RaiseLocalEvent(uid, ref ev); + var ev = new InhaleLocationEvent + { + Respirator = entity.Comp, + }; + RaiseLocalEvent(entity, ref ev); - ev.Gas ??= _atmosSys.GetContainingMixture(uid, excite: true); + ev.Gas ??= _atmosSys.GetContainingMixture(entity.Owner, excite: true); if (ev.Gas is null) { - return; + return false; } - var actualGas = ev.Gas.RemoveVolume(Atmospherics.BreathVolume); + var gas = ev.Gas.RemoveVolume(entity.Comp.BreathVolume); - var lungRatio = 1.0f / organs.Count; - var gas = organs.Count == 1 ? actualGas : actualGas.RemoveRatio(lungRatio); - foreach (var (organUid, lung, _) in organs) - { - // Merge doesn't remove gas from the giver. - _atmosSys.Merge(lung.Air, gas); - _lungSystem.GasToReagent(organUid, lung); - } + var inhaleEv = new InhaledGasEvent(gas); + RaiseLocalEvent(entity, ref inhaleEv); + + return inhaleEv.Handled && inhaleEv.Succeeded; } - public void Exhale(EntityUid uid, BodyComponent? body = null) + public void Exhale(Entity entity) { - if (!Resolve(uid, ref body, logMissing: false)) + if (!Resolve(entity, ref entity.Comp, logMissing: false)) return; - var organs = _bodySystem.GetBodyOrganEntityComps((uid, body)); - // exhale gas var ev = new ExhaleLocationEvent(); - RaiseLocalEvent(uid, ref ev, broadcast: false); + RaiseLocalEvent(entity, ref ev, broadcast: false); if (ev.Gas is null) { - ev.Gas = _atmosSys.GetContainingMixture(uid, excite: true); + ev.Gas = _atmosSys.GetContainingMixture(entity.Owner, excite: true); // Walls and grids without atmos comp return null. I guess it makes sense to not be able to exhale in walls, // but this also means you cannot exhale on some grids. ev.Gas ??= GasMixture.SpaceGas; } - var outGas = new GasMixture(ev.Gas.Volume); - foreach (var (organUid, lung, _) in organs) - { - _atmosSys.Merge(outGas, lung.Air); - lung.Air.Clear(); - - if (_solutionContainerSystem.ResolveSolution(organUid, lung.SolutionName, ref lung.Solution)) - _solutionContainerSystem.RemoveAllSolution(lung.Solution.Value); - } - - _atmosSys.Merge(ev.Gas, outGas); + var exhaleEv = new ExhaledGasEvent(ev.Gas); + RaiseLocalEvent(entity, ref exhaleEv); } /// @@ -199,14 +190,15 @@ public sealed class RespiratorSystem : EntitySystem if (!Resolve(ent, ref ent.Comp)) return false; - var ev = new InhaleLocationEvent(); - RaiseLocalEvent(ent, ref ev); - - var gas = ev.Gas ?? _atmosSys.GetContainingMixture(ent.Owner); - if (gas == null) + if (!Inhale(ent)) return false; - return CanMetabolizeGas(ent, gas); + // If we don't have a body we can't be poisoned by gas, yet... + var success = TryMetabolizeGas((ent, ent.Comp)); + + // Don't keep that gas in our lungs lest it poisons a poor nuclear operative. + Exhale(ent); + return success; } /// @@ -224,7 +216,7 @@ public sealed class RespiratorSystem : EntitySystem gas = new GasMixture(gas); var lungRatio = 1.0f / organs.Count; - gas.Multiply(MathF.Min(lungRatio * gas.Volume/Atmospherics.BreathVolume, lungRatio)); + gas.Multiply(MathF.Min(lungRatio * gas.Volume / ent.Comp.BreathVolume, lungRatio)); var solution = _lungSystem.GasToReagent(gas); float saturation = 0; @@ -238,6 +230,71 @@ public sealed class RespiratorSystem : EntitySystem return saturation > ent.Comp.UpdateInterval.TotalSeconds; } + public bool TryInhaleGasToBody(Entity entity, GasMixture gas) + { + if (!Resolve(entity, ref entity.Comp)) + return false; + + var organs = _bodySystem.GetBodyOrganEntityComps((entity, entity.Comp)); + if (organs.Count == 0) + return false; + + var lungRatio = 1.0f / organs.Count; + var splitGas = organs.Count == 1 ? gas : gas.RemoveRatio(lungRatio); + foreach (var (organUid, lung, _) in organs) + { + // Merge doesn't remove gas from the giver. + _atmosSys.Merge(lung.Air, splitGas); + _lungSystem.GasToReagent(organUid, lung); + } + + return true; + } + + public void RemoveGasFromBody(Entity ent, GasMixture gas) + { + var outGas = new GasMixture(gas.Volume); + + var organs = _bodySystem.GetBodyOrganEntityComps((ent, ent.Comp)); + if (organs.Count == 0) + return; + + foreach (var (organUid, lung, _) in organs) + { + _atmosSys.Merge(outGas, lung.Air); + lung.Air.Clear(); + + if (_solutionContainerSystem.ResolveSolution(organUid, lung.SolutionName, ref lung.Solution)) + _solutionContainerSystem.RemoveAllSolution(lung.Solution.Value); + } + + _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. /// @@ -301,6 +358,8 @@ public sealed class RespiratorSystem : EntitySystem if (ent.Comp.SuffocationCycles == 2) _adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(ent):entity} started suffocating"); + _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 @@ -310,8 +369,6 @@ public sealed class RespiratorSystem : EntitySystem _alertsSystem.ShowAlert(ent, entity.Comp1.Alert); } } - - _damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false); } private void StopSuffocation(Entity ent) @@ -319,18 +376,17 @@ public sealed class RespiratorSystem : EntitySystem if (ent.Comp.SuffocationCycles >= 2) _adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(ent):entity} stopped suffocating"); + _damageableSys.TryChangeDamage(ent, ent.Comp.DamageRecovery); + // 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.ClearAlert(ent, entity.Comp1.Alert); } - - _damageableSys.TryChangeDamage(ent, ent.Comp.DamageRecovery); } - public void UpdateSaturation(EntityUid uid, float amount, - RespiratorComponent? respirator = null) + public void UpdateSaturation(EntityUid uid, float amount, RespiratorComponent? respirator = null) { if (!Resolve(uid, ref respirator, false)) return; @@ -362,10 +418,30 @@ public sealed class RespiratorSystem : EntitySystem ent.Comp.MaxSaturation /= args.Multiplier; ent.Comp.MinSaturation /= args.Multiplier; } + + private void OnGasInhaled(Entity entity, ref InhaledGasEvent args) + { + args.Handled = true; + + args.Succeeded = TryInhaleGasToBody((entity, entity.Comp), args.Gas); + } + + private void OnGasExhaled(Entity entity, ref ExhaledGasEvent args) + { + args.Handled = true; + + RemoveGasFromBody(entity, args.Gas); + } } [ByRefEvent] -public record struct InhaleLocationEvent(GasMixture? Gas); +public record struct InhaleLocationEvent(GasMixture? Gas, RespiratorComponent Respirator); [ByRefEvent] public record struct ExhaleLocationEvent(GasMixture? Gas); + +[ByRefEvent] +public record struct InhaledGasEvent(GasMixture Gas, bool Handled = false, bool Succeeded = false); + +[ByRefEvent] +public record struct ExhaledGasEvent(GasMixture Gas, bool Handled = false); -- 2.51.2