From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:17:40 +0000 (+0100) Subject: Suffocation alerts for nitrogen breathers (#24373) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=2737c80169853223660948931aadba894723fcaf;p=space-station-14.git Suffocation alerts for nitrogen breathers (#24373) * Respiratorsystem namespace * WIP gas alert update, does not work * Finally --- diff --git a/Content.Server/Body/Components/LungComponent.cs b/Content.Server/Body/Components/LungComponent.cs index 1d997a9950..0656ef8fad 100644 --- a/Content.Server/Body/Components/LungComponent.cs +++ b/Content.Server/Body/Components/LungComponent.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos; using Content.Server.Body.Systems; +using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Chemistry.Components; @@ -27,4 +28,10 @@ public sealed partial class LungComponent : Component /// [DataField] public Entity? Solution = null; + + /// + /// The type of gas this lung needs. Used only for the breathing alerts, not actual metabolism. + /// + [DataField] + public AlertType Alert = AlertType.LowOxygen; } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index b814422181..0fd61a9cb7 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -13,202 +13,211 @@ using Content.Shared.Mobs.Systems; using JetBrains.Annotations; using Robust.Shared.Timing; -namespace Content.Server.Body.Systems +namespace Content.Server.Body.Systems; + +[UsedImplicitly] +public sealed class RespiratorSystem : EntitySystem { - [UsedImplicitly] - public sealed class RespiratorSystem : EntitySystem + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; + [Dependency] private readonly AtmosphereSystem _atmosSys = default!; + [Dependency] private readonly BodySystem _bodySystem = default!; + [Dependency] private readonly DamageableSystem _damageableSys = default!; + [Dependency] private readonly LungSystem _lungSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + + public override void Initialize() { - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly AlertsSystem _alertsSystem = default!; - [Dependency] private readonly AtmosphereSystem _atmosSys = default!; - [Dependency] private readonly BodySystem _bodySystem = default!; - [Dependency] private readonly DamageableSystem _damageableSys = default!; - [Dependency] private readonly LungSystem _lungSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; - - public override void Initialize() - { - base.Initialize(); + base.Initialize(); - // We want to process lung reagents before we inhale new reagents. - UpdatesAfter.Add(typeof(MetabolizerSystem)); - SubscribeLocalEvent(OnApplyMetabolicMultiplier); - } + // We want to process lung reagents before we inhale new reagents. + UpdatesAfter.Add(typeof(MetabolizerSystem)); + SubscribeLocalEvent(OnApplyMetabolicMultiplier); + } - public override void Update(float frameTime) - { - base.Update(frameTime); + public override void Update(float frameTime) + { + base.Update(frameTime); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var respirator, out var body)) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var respirator, out var body)) + { + if (_mobState.IsDead(uid)) { - if (_mobState.IsDead(uid)) - { - continue; - } + continue; + } - respirator.AccumulatedFrametime += frameTime; + respirator.AccumulatedFrametime += frameTime; - if (respirator.AccumulatedFrametime < respirator.CycleDelay) - continue; - respirator.AccumulatedFrametime -= respirator.CycleDelay; - UpdateSaturation(uid, -respirator.CycleDelay, respirator); + if (respirator.AccumulatedFrametime < respirator.CycleDelay) + continue; + respirator.AccumulatedFrametime -= respirator.CycleDelay; + UpdateSaturation(uid, -respirator.CycleDelay, respirator); - if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit. + if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit. + { + switch (respirator.Status) { - switch (respirator.Status) - { - case RespiratorStatus.Inhaling: - Inhale(uid, body); - respirator.Status = RespiratorStatus.Exhaling; - break; - case RespiratorStatus.Exhaling: - Exhale(uid, body); - respirator.Status = RespiratorStatus.Inhaling; - break; - } + case RespiratorStatus.Inhaling: + Inhale(uid, body); + respirator.Status = RespiratorStatus.Exhaling; + break; + case RespiratorStatus.Exhaling: + Exhale(uid, body); + respirator.Status = RespiratorStatus.Inhaling; + break; } + } - if (respirator.Saturation < respirator.SuffocationThreshold) + if (respirator.Saturation < respirator.SuffocationThreshold) + { + if (_gameTiming.CurTime >= respirator.LastGaspPopupTime + respirator.GaspPopupCooldown) { - if (_gameTiming.CurTime >= respirator.LastGaspPopupTime + respirator.GaspPopupCooldown) - { - respirator.LastGaspPopupTime = _gameTiming.CurTime; - _popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid); - } - - TakeSuffocationDamage(uid, respirator); - respirator.SuffocationCycles += 1; - continue; + respirator.LastGaspPopupTime = _gameTiming.CurTime; + _popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid); } - StopSuffocation(uid, respirator); - respirator.SuffocationCycles = 0; + TakeSuffocationDamage(uid, respirator); + respirator.SuffocationCycles += 1; + continue; } - } - public void Inhale(EntityUid uid, BodyComponent? body = null) - { - if (!Resolve(uid, ref body, false)) - return; - - var organs = _bodySystem.GetBodyOrganComponents(uid, body); + StopSuffocation(uid, respirator); + respirator.SuffocationCycles = 0; + } + } - // Inhale gas - var ev = new InhaleLocationEvent(); - RaiseLocalEvent(uid, ev); + public void Inhale(EntityUid uid, BodyComponent? body = null) + { + if (!Resolve(uid, ref body, false)) + return; - ev.Gas ??= _atmosSys.GetContainingMixture(uid, false, true); + var organs = _bodySystem.GetBodyOrganComponents(uid, body); - if (ev.Gas == null) - { - return; - } + // Inhale gas + var ev = new InhaleLocationEvent(); + RaiseLocalEvent(uid, ev); - var actualGas = ev.Gas.RemoveVolume(Atmospherics.BreathVolume); + ev.Gas ??= _atmosSys.GetContainingMixture(uid, false, true); - var lungRatio = 1.0f / organs.Count; - var gas = organs.Count == 1 ? actualGas : actualGas.RemoveRatio(lungRatio); - foreach (var (lung, _) in organs) - { - // Merge doesn't remove gas from the giver. - _atmosSys.Merge(lung.Air, gas); - _lungSystem.GasToReagent(lung.Owner, lung); - } + if (ev.Gas == null) + { + return; } - public void Exhale(EntityUid uid, BodyComponent? body = null) + var actualGas = ev.Gas.RemoveVolume(Atmospherics.BreathVolume); + + var lungRatio = 1.0f / organs.Count; + var gas = organs.Count == 1 ? actualGas : actualGas.RemoveRatio(lungRatio); + foreach (var (lung, _) in organs) { - if (!Resolve(uid, ref body, false)) - return; + // Merge doesn't remove gas from the giver. + _atmosSys.Merge(lung.Air, gas); + _lungSystem.GasToReagent(lung.Owner, lung); + } + } - var organs = _bodySystem.GetBodyOrganComponents(uid, body); + public void Exhale(EntityUid uid, BodyComponent? body = null) + { + if (!Resolve(uid, ref body, false)) + return; - // exhale gas + var organs = _bodySystem.GetBodyOrganComponents(uid, body); - var ev = new ExhaleLocationEvent(); - RaiseLocalEvent(uid, ev, false); + // exhale gas - if (ev.Gas == null) - { - ev.Gas = _atmosSys.GetContainingMixture(uid, false, true); + var ev = new ExhaleLocationEvent(); + RaiseLocalEvent(uid, ev, false); - // 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; - } + if (ev.Gas == null) + { + ev.Gas = _atmosSys.GetContainingMixture(uid, false, true); - var outGas = new GasMixture(ev.Gas.Volume); - foreach (var (lung, _) in organs) - { - _atmosSys.Merge(outGas, lung.Air); - lung.Air.Clear(); + // 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; + } - if (_solutionContainerSystem.ResolveSolution(lung.Owner, lung.SolutionName, ref lung.Solution)) - _solutionContainerSystem.RemoveAllSolution(lung.Solution.Value); - } + var outGas = new GasMixture(ev.Gas.Volume); + foreach (var (lung, _) in organs) + { + _atmosSys.Merge(outGas, lung.Air); + lung.Air.Clear(); - _atmosSys.Merge(ev.Gas, outGas); + if (_solutionContainerSystem.ResolveSolution(lung.Owner, lung.SolutionName, ref lung.Solution)) + _solutionContainerSystem.RemoveAllSolution(lung.Solution.Value); } - private void TakeSuffocationDamage(EntityUid uid, RespiratorComponent respirator) - { - if (respirator.SuffocationCycles == 2) - _adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} started suffocating"); + _atmosSys.Merge(ev.Gas, outGas); + } + + private void TakeSuffocationDamage(EntityUid uid, RespiratorComponent respirator) + { + if (respirator.SuffocationCycles == 2) + _adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} started suffocating"); - if (respirator.SuffocationCycles >= respirator.SuffocationCycleThreshold) + if (respirator.SuffocationCycles >= respirator.SuffocationCycleThreshold) + { + // TODO: This is not going work with multiple different lungs, if that ever becomes a possibility + var organs = _bodySystem.GetBodyOrganComponents(uid); + foreach (var (comp, _) in organs) { - _alertsSystem.ShowAlert(uid, AlertType.LowOxygen); + _alertsSystem.ShowAlert(uid, comp.Alert); } - - _damageableSys.TryChangeDamage(uid, respirator.Damage, false, false); } - private void StopSuffocation(EntityUid uid, RespiratorComponent respirator) - { - if (respirator.SuffocationCycles >= 2) - _adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} stopped suffocating"); + _damageableSys.TryChangeDamage(uid, respirator.Damage, false, false); + } - _alertsSystem.ClearAlert(uid, AlertType.LowOxygen); + private void StopSuffocation(EntityUid uid, RespiratorComponent respirator) + { + if (respirator.SuffocationCycles >= 2) + _adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} stopped suffocating"); - _damageableSys.TryChangeDamage(uid, respirator.DamageRecovery); + // TODO: This is not going work with multiple different lungs, if that ever becomes a possibility + var organs = _bodySystem.GetBodyOrganComponents(uid); + foreach (var (comp, _) in organs) + { + _alertsSystem.ClearAlert(uid, comp.Alert); } - public void UpdateSaturation(EntityUid uid, float amount, - RespiratorComponent? respirator = null) - { - if (!Resolve(uid, ref respirator, false)) - return; + _damageableSys.TryChangeDamage(uid, respirator.DamageRecovery); + } - respirator.Saturation += amount; - respirator.Saturation = - Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation); - } + public void UpdateSaturation(EntityUid uid, float amount, + RespiratorComponent? respirator = null) + { + if (!Resolve(uid, ref respirator, false)) + return; - private void OnApplyMetabolicMultiplier(EntityUid uid, RespiratorComponent component, + respirator.Saturation += amount; + respirator.Saturation = + Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation); + } + + private void OnApplyMetabolicMultiplier(EntityUid uid, RespiratorComponent component, ApplyMetabolicMultiplierEvent args) + { + if (args.Apply) { - if (args.Apply) - { - component.CycleDelay *= args.Multiplier; - component.Saturation *= args.Multiplier; - component.MaxSaturation *= args.Multiplier; - component.MinSaturation *= args.Multiplier; - return; - } - - // This way we don't have to worry about it breaking if the stasis bed component is destroyed - component.CycleDelay /= args.Multiplier; - component.Saturation /= args.Multiplier; - component.MaxSaturation /= args.Multiplier; - component.MinSaturation /= args.Multiplier; - // Reset the accumulator properly - if (component.AccumulatedFrametime >= component.CycleDelay) - component.AccumulatedFrametime = component.CycleDelay; + component.CycleDelay *= args.Multiplier; + component.Saturation *= args.Multiplier; + component.MaxSaturation *= args.Multiplier; + component.MinSaturation *= args.Multiplier; + return; } + + // This way we don't have to worry about it breaking if the stasis bed component is destroyed + component.CycleDelay /= args.Multiplier; + component.Saturation /= args.Multiplier; + component.MaxSaturation /= args.Multiplier; + component.MinSaturation /= args.Multiplier; + // Reset the accumulator properly + if (component.AccumulatedFrametime >= component.CycleDelay) + component.AccumulatedFrametime = component.CycleDelay; } } diff --git a/Content.Server/Disposal/Unit/EntitySystems/BeingDisposedSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/BeingDisposedSystem.cs index 72d86bd64b..6fbfb1523a 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/BeingDisposedSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/BeingDisposedSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.EntitySystems; +using Content.Server.Body.Systems; using Content.Server.Disposal.Unit.Components; namespace Content.Server.Disposal.Unit.EntitySystems; diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index fe903655b5..eef9bcb8ec 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -16,6 +16,7 @@ using Content.Shared.Popups; using Content.Shared.Tools.Components; using Content.Shared.Verbs; using Content.Shared.Wires; +using Content.Server.Body.Systems; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; diff --git a/Content.Server/Medical/InsideCryoPodSystem.cs b/Content.Server/Medical/InsideCryoPodSystem.cs index a2252fa395..21827c105f 100644 --- a/Content.Server/Medical/InsideCryoPodSystem.cs +++ b/Content.Server/Medical/InsideCryoPodSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.EntitySystems; +using Content.Server.Body.Systems; using Content.Server.Medical.Components; using Content.Shared.Medical.Cryogenics; diff --git a/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs b/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs index 9bc49165ee..176fd9b9ed 100644 --- a/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Content.Server.Atmos.EntitySystems; +using Content.Server.Body.Systems; using Content.Server.Construction; using Content.Server.Construction.Components; using Content.Server.Storage.Components; diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index 68db360231..b917dd692d 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -9,6 +9,7 @@ namespace Content.Shared.Alert { Error, LowOxygen, + LowNitrogen, LowPressure, HighPressure, Fire, diff --git a/Resources/Locale/en-US/alerts/alerts.ftl b/Resources/Locale/en-US/alerts/alerts.ftl index 1e9b8a2017..795d740141 100644 --- a/Resources/Locale/en-US/alerts/alerts.ftl +++ b/Resources/Locale/en-US/alerts/alerts.ftl @@ -1,6 +1,9 @@ alerts-low-oxygen-name = [color=red]Low Oxygen[/color] alerts-low-oxygen-desc = There is [color=red]not enough oxygen[/color] in the air you are breathing. Put on [color=green]internals[/color]. +alerts-low-nitrogen-name = [color=red]Low Nitrogen[/color] +alerts-low-nitrogen-desc = There is [color=red]not enough nitrogen[/color] in the air you are breathing. Put on [color=green]internals[/color]. + alerts-high-toxin-name = [color=red]High Toxin Level[/color] alerts-high-toxin-desc = There are [color=red]too many toxins[/color] in the air you are breathing. Put on [color=green]internals[/color] or get away. diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index bc3137b07f..2d1c9062e6 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -34,6 +34,15 @@ name: alerts-low-oxygen-name description: alerts-low-oxygen-desc +- type: alert + id: LowNitrogen + category: Breathing + icons: + - sprite: /Textures/Interface/Alerts/breathing.rsi + state: not_enough_nitro + name: alerts-low-nitrogen-name + description: alerts-low-nitrogen-desc + - type: alert id: Toxins category: Toxins diff --git a/Resources/Prototypes/Body/Organs/Animal/slimes.yml b/Resources/Prototypes/Body/Organs/Animal/slimes.yml index f5d9455449..3bdb1aef8f 100644 --- a/Resources/Prototypes/Body/Organs/Animal/slimes.yml +++ b/Resources/Prototypes/Body/Organs/Animal/slimes.yml @@ -37,6 +37,7 @@ - state: lung-l-slime - state: lung-r-slime - type: Lung + Alert: LowNitrogen - type: Metabolizer removeEmpty: true solutionOnBody: false diff --git a/Resources/Prototypes/Body/Organs/slime.yml b/Resources/Prototypes/Body/Organs/slime.yml index a532c1223a..46b1801077 100644 --- a/Resources/Prototypes/Body/Organs/slime.yml +++ b/Resources/Prototypes/Body/Organs/slime.yml @@ -43,6 +43,7 @@ - state: lung-l-slime - state: lung-r-slime - type: Lung + alert: LowNitrogen - type: Metabolizer removeEmpty: true solutionOnBody: false diff --git a/Resources/Prototypes/Body/Organs/vox.yml b/Resources/Prototypes/Body/Organs/vox.yml index b928020444..1b4d12116f 100644 --- a/Resources/Prototypes/Body/Organs/vox.yml +++ b/Resources/Prototypes/Body/Organs/vox.yml @@ -5,4 +5,5 @@ components: - type: Metabolizer metabolizerTypes: [ Vox ] - + - type: Lung + alert: LowNitrogen