Skip to content

Commit

Permalink
[Feat] CPR Impovements (DeltaV-Station#1405)
Browse files Browse the repository at this point in the history
# Description

Moved the whole CPR code to the server. There was no need for it to be
on shared.
Now CPR automatically repeats itself until the person is alive.
CPR can now be performed without getting you mask off. You just need to
lower it down like when trying to eat/drink.
Fixed popups repeating itself x100 times.
Removed cvars and moved everything to CPRTraining component. Why would
anyone place them in cvars anyway?..

---

# Changelog

:cl:
- add: CPR now automatically repeats itself.
- add: CPR no longer requires you to take the mask off. You can now
simply lower it instead.
- fix: Fixed CPR repeating popups x100 times.

Signed-off-by: Remuchi <[email protected]>
  • Loading branch information
Remuchi authored Jan 2, 2025
1 parent db055a7 commit 075a443
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 197 deletions.
124 changes: 124 additions & 0 deletions Content.Server/Medical/CPR/CPRSystem.cs
Original file line number Diff line number Diff line change
@@ -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<CPRTrainingComponent, GetVerbsEvent<InnateVerb>>(AddCPRVerb);
SubscribeLocalEvent<CPRTrainingComponent, CPRDoAfterEvent>(OnCPRDoAfter);
}

private void AddCPRVerb(Entity<CPRTrainingComponent> performer, ref GetVerbsEvent<InnateVerb> args)
{
if (!args.CanInteract || !args.CanAccess || !TryComp<MobStateComponent>(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<CPRTrainingComponent> performer, EntityUid target)
{
if (HasComp<RottingComponent>(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<CPRTrainingComponent> 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<DamageableComponent>(args.Target, out var damageableComponent)
&& TryComp<MobStateComponent>(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);
}
}
30 changes: 30 additions & 0 deletions Content.Server/Medical/CPR/CPRTrainingComponent.cs
Original file line number Diff line number Diff line change
@@ -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;
}
33 changes: 0 additions & 33 deletions Content.Shared/Medical/CPR/Components/CPRTrainingComponent.cs

This file was deleted.

27 changes: 0 additions & 27 deletions Content.Shared/Medical/CPR/Systems/CPRSystem.CVars.cs

This file was deleted.

137 changes: 0 additions & 137 deletions Content.Shared/Medical/CPR/Systems/CPRSystem.cs

This file was deleted.

7 changes: 7 additions & 0 deletions Content.Shared/Medical/CPRDoAfterEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;

namespace Content.Shared.Medical;

[Serializable, NetSerializable]
public sealed partial class CPRDoAfterEvent : SimpleDoAfterEvent;

0 comments on commit 075a443

Please sign in to comment.