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