diff --git a/Content.Server/Medical/CPR/CPRSystem.cs b/Content.Server/Medical/CPR/CPRSystem.cs new file mode 100644 index 00000000000..fe4521a15de --- /dev/null +++ b/Content.Server/Medical/CPR/CPRSystem.cs @@ -0,0 +1,124 @@ +using Content.Server.Atmos.Rotting; +using Content.Server.DoAfter; +using Content.Server.Nutrition.EntitySystems; +using Content.Server.Popups; +using Content.Shared.Atmos.Rotting; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Inventory; +using Content.Shared.Medical; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Verbs; +using Robust.Server.Audio; +using Robust.Shared.Audio; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.Medical.CPR; + +public sealed class CPRSystem : EntitySystem +{ + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly FoodSystem _foodSystem = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly RottingSystem _rottingSystem = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly AudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent>(AddCPRVerb); + SubscribeLocalEvent(OnCPRDoAfter); + } + + private void AddCPRVerb(Entity performer, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess || !TryComp(args.Target, out var targetState) + || targetState.CurrentState == MobState.Alive) + return; + + var target = args.Target; + InnateVerb verb = new() + { + Act = () => { StartCPR(performer, target); }, + Text = Loc.GetString("cpr-verb"), + Icon = new SpriteSpecifier.Rsi(new("Interface/Alerts/human_alive.rsi"), "health4"), + Priority = 2 + }; + + args.Verbs.Add(verb); + } + + private void StartCPR(Entity performer, EntityUid target) + { + if (HasComp(target)) + { + _popupSystem.PopupEntity(Loc.GetString("cpr-target-rotting", ("entity", target)), performer, performer); + return; + } + + if (_inventory.TryGetSlotEntity(target, "outerClothing", out var outer)) + { + _popupSystem.PopupEntity(Loc.GetString("cpr-must-remove", ("clothing", outer)), performer, performer); + return; + } + + if (_foodSystem.IsMouthBlocked(performer, performer) || _foodSystem.IsMouthBlocked(target, performer)) + return; + + _popupSystem.PopupEntity(Loc.GetString("cpr-start-second-person", ("target", target)), target, performer); + _popupSystem.PopupEntity(Loc.GetString("cpr-start-second-person-patient", ("user", performer)), target, target); + + var doAfterArgs = new DoAfterArgs( + EntityManager, performer, performer.Comp.DoAfterDuration, new CPRDoAfterEvent(), performer, target, + performer) + { + BreakOnMove = true, + NeedHand = true, + BlockDuplicate = true + }; + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + + var playingStream = _audio.PlayPvs(performer.Comp.CPRSound, performer, AudioParams.Default.WithLoop(true)); + if (!playingStream.HasValue) + return; + + performer.Comp.CPRPlayingStream = playingStream.Value.Entity; + } + + private void OnCPRDoAfter(Entity performer, ref CPRDoAfterEvent args) + { + if (args.Cancelled || args.Handled || !args.Target.HasValue) + { + performer.Comp.CPRPlayingStream = _audio.Stop(performer.Comp.CPRPlayingStream); + return; + } + + if (!performer.Comp.CPRHealing.Empty) + _damageable.TryChangeDamage(args.Target, performer.Comp.CPRHealing, true, origin: performer); + + if (performer.Comp.RotReductionMultiplier > 0) + _rottingSystem.ReduceAccumulator( + (EntityUid)args.Target, performer.Comp.DoAfterDuration * performer.Comp.RotReductionMultiplier); + + if (_robustRandom.Prob(performer.Comp.ResuscitationChance) + && _mobThreshold.TryGetThresholdForState((EntityUid)args.Target, MobState.Dead, out var threshold) + && TryComp(args.Target, out var damageableComponent) + && TryComp(args.Target, out var state) + && damageableComponent.TotalDamage < threshold) + _mobStateSystem.ChangeMobState(args.Target.Value, MobState.Critical, state, performer); + + var isAlive = _mobStateSystem.IsAlive(args.Target.Value); + args.Repeat = !isAlive; + if (isAlive) + performer.Comp.CPRPlayingStream = _audio.Stop(performer.Comp.CPRPlayingStream); + } +} diff --git a/Content.Server/Medical/CPR/CPRTrainingComponent.cs b/Content.Server/Medical/CPR/CPRTrainingComponent.cs new file mode 100644 index 00000000000..f398249244b --- /dev/null +++ b/Content.Server/Medical/CPR/CPRTrainingComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.Damage; +using Robust.Shared.Audio; + +namespace Content.Server.Medical.CPR; + +[RegisterComponent] +public sealed partial class CPRTrainingComponent : Component +{ + [DataField] + public SoundSpecifier CPRSound = new SoundPathSpecifier("/Audio/Effects/CPR.ogg"); + + [DataField] + public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(3); + + [DataField] public DamageSpecifier CPRHealing = new() + { + DamageDict = + { + ["Asphyxiation"] = -6 + } + }; + + [DataField] public float CrackRibsModifier = 1f; + + [DataField] public float ResuscitationChance = 0.1f; + + [DataField] public float RotReductionMultiplier; + + public EntityUid? CPRPlayingStream; +} diff --git a/Content.Shared/Medical/CPR/Components/CPRTrainingComponent.cs b/Content.Shared/Medical/CPR/Components/CPRTrainingComponent.cs deleted file mode 100644 index e01250858a1..00000000000 --- a/Content.Shared/Medical/CPR/Components/CPRTrainingComponent.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Robust.Shared.GameStates; -using Content.Shared.DoAfter; -using Robust.Shared.Audio; -using Robust.Shared.Serialization; - -namespace Content.Shared.Medical.CPR -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class CPRTrainingComponent : Component - { - [DataField] - public SoundSpecifier CPRSound = new SoundPathSpecifier("/Audio/Effects/CPR.ogg"); - - /// - /// How long the doafter for CPR takes - /// - [DataField] - public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(3); - - [DataField] - public int AirlossHeal = 6; - - [DataField] - public float CrackRibsModifier = 1f; - public EntityUid? CPRPlayingStream; - } - - [Serializable, NetSerializable] - public sealed partial class CPRDoAfterEvent : SimpleDoAfterEvent - { - - } -} diff --git a/Content.Shared/Medical/CPR/Systems/CPRSystem.CVars.cs b/Content.Shared/Medical/CPR/Systems/CPRSystem.CVars.cs deleted file mode 100644 index 9840b8ffbd4..00000000000 --- a/Content.Shared/Medical/CPR/Systems/CPRSystem.CVars.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Shared.CCVar; -using Robust.Shared.Configuration; - -namespace Content.Shared.Medical.CPR -{ - public sealed partial class CPRSystem - { - [Dependency] private readonly IConfigurationManager _cfg = default!; - - public bool EnableCPR { get; private set; } - public bool HealsAirloss { get; private set; } - public bool ReducesRot { get; private set; } - public float ResuscitationChance { get; private set; } - public float RotReductionMultiplier { get; private set; } - public float AirlossReductionMultiplier { get; private set; } - - private void InitializeCVars() - { - Subs.CVar(_cfg, CCVars.EnableCPR, value => EnableCPR = value, true); - Subs.CVar(_cfg, CCVars.CPRHealsAirloss, value => HealsAirloss = value, true); - Subs.CVar(_cfg, CCVars.CPRReducesRot, value => ReducesRot = value, true); - Subs.CVar(_cfg, CCVars.CPRResuscitationChance, value => ResuscitationChance = value, true); - Subs.CVar(_cfg, CCVars.CPRRotReductionMultiplier, value => RotReductionMultiplier = value, true); - Subs.CVar(_cfg, CCVars.CPRAirlossReductionMultiplier, value => AirlossReductionMultiplier = value, true); - } - } -} diff --git a/Content.Shared/Medical/CPR/Systems/CPRSystem.cs b/Content.Shared/Medical/CPR/Systems/CPRSystem.cs deleted file mode 100644 index d82a7ee202b..00000000000 --- a/Content.Shared/Medical/CPR/Systems/CPRSystem.cs +++ /dev/null @@ -1,137 +0,0 @@ -using Content.Shared.Popups; -using Content.Shared.Atmos.Rotting; -using Content.Shared.Damage; -using Content.Shared.DoAfter; -using Content.Shared.Inventory; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Content.Shared.Mobs.Systems; -using Content.Shared.Verbs; -using Robust.Shared.Network; -using Robust.Shared.Utility; -using Robust.Shared.Random; -using Robust.Shared.Audio.Systems; - -namespace Content.Shared.Medical.CPR -{ - public sealed partial class CPRSystem : EntitySystem - { - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly DamageableSystem _damageable = default!; - [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; - [Dependency] private readonly IRobustRandom _robustRandom = default!; - [Dependency] private readonly SharedRottingSystem _rottingSystem = default!; - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly INetManager _net = default!; - public override void Initialize() - { - base.Initialize(); - InitializeCVars(); - SubscribeLocalEvent>(AddCPRVerb); - SubscribeLocalEvent(OnCPRDoAfter); - } - - private void AddCPRVerb(EntityUid uid, CPRTrainingComponent component, GetVerbsEvent args) - { - if (!EnableCPR || !args.CanInteract || !args.CanAccess - || !TryComp(args.Target, out var targetState) - || targetState.CurrentState == MobState.Alive) - return; - - InnateVerb verb = new() - { - Act = () => - { - StartCPR(uid, args.Target, component); - }, - Text = Loc.GetString("cpr-verb"), - Icon = new SpriteSpecifier.Rsi(new("Interface/Alerts/human_alive.rsi"), "health4"), - Priority = 2 - }; - args.Verbs.Add(verb); - } - - private void StartCPR(EntityUid performer, EntityUid target, CPRTrainingComponent cprComponent) - { - if (HasComp(target)) - { - _popupSystem.PopupEntity(Loc.GetString("cpr-target-rotting", ("entity", target)), performer, performer); - return; - } - - if (_inventory.TryGetSlotEntity(target, "outerClothing", out var outer)) - { - _popupSystem.PopupEntity(Loc.GetString("cpr-must-remove", ("clothing", outer)), performer, performer, PopupType.MediumCaution); - return; - } - - if (_inventory.TryGetSlotEntity(target, "mask", out var mask)) - { - _popupSystem.PopupEntity(Loc.GetString("cpr-must-remove", ("clothing", mask)), performer, performer, PopupType.MediumCaution); - return; - } - - if (_inventory.TryGetSlotEntity(performer, "mask", out var maskSelf)) - { - _popupSystem.PopupEntity(Loc.GetString("cpr-must-remove-own-mask", ("clothing", maskSelf)), performer, performer, PopupType.MediumCaution); - return; - } - - if (_net.IsServer) - { - _popupSystem.PopupEntity(Loc.GetString("cpr-start-second-person", ("target", target)), target, performer, PopupType.Medium); - _popupSystem.PopupEntity(Loc.GetString("cpr-start-second-person-patient", ("user", performer)), target, target, PopupType.Medium); - - var playingStream = _audio.PlayPvs(cprComponent.CPRSound, performer); - - if (playingStream == null) - return; - - cprComponent.CPRPlayingStream = _audio.PlayPvs(cprComponent.CPRSound, performer)!.Value.Entity; - } - - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, performer, cprComponent.DoAfterDuration, new CPRDoAfterEvent(), performer, target, performer) - { - BreakOnMove = true, - NeedHand = true, - BlockDuplicate = true - }); - } - - private void OnCPRDoAfter(EntityUid performer, CPRTrainingComponent component, CPRDoAfterEvent args) - { - component.CPRPlayingStream = _audio.Stop(component.CPRPlayingStream); - - if (args.Target == null) - return; - - if (HealsAirloss) - { - // There is PROBABLY a better way to do this, by all means let me know - var healing = new DamageSpecifier() - { - DamageDict = new() - { - { "Asphyxiation", -component.AirlossHeal * AirlossReductionMultiplier} - } - }; - _damageable.TryChangeDamage(args.Target, healing, true, origin: performer); - } - - if (ReducesRot) - _rottingSystem.ReduceAccumulator((EntityUid) args.Target, component.DoAfterDuration * RotReductionMultiplier); - - if (_robustRandom.Prob(ResuscitationChance) - && _mobThreshold.TryGetThresholdForState((EntityUid) args.Target, MobState.Dead, out var threshold) - && TryComp(args.Target, out var damageableComponent) - && TryComp(args.Target, out var state) - && damageableComponent.TotalDamage < threshold) - { - _mobStateSystem.ChangeMobState(args.Target.Value, MobState.Critical, state, performer); - } - } - } -} diff --git a/Content.Shared/Medical/CPRDoAfterEvent.cs b/Content.Shared/Medical/CPRDoAfterEvent.cs new file mode 100644 index 00000000000..378ab4f51aa --- /dev/null +++ b/Content.Shared/Medical/CPRDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Medical; + +[Serializable, NetSerializable] +public sealed partial class CPRDoAfterEvent : SimpleDoAfterEvent;