| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- using Content.Server.Atmos.Rotting;
- using Content.Server.Chat.Systems;
- using Content.Server.DoAfter;
- using Content.Server.Electrocution;
- using Content.Server.EUI;
- using Content.Server.Ghost;
- using Content.Server.Popups;
- using Content.Server.PowerCell;
- using Content.Shared.Traits.Assorted;
- using Content.Shared.Damage;
- using Content.Shared.DoAfter;
- using Content.Shared.Interaction;
- using Content.Shared.Interaction.Components;
- using Content.Shared.Interaction.Events;
- using Content.Shared.Item.ItemToggle;
- using Content.Shared.Medical;
- using Content.Shared.Mind;
- using Content.Shared.Mobs;
- using Content.Shared.Mobs.Components;
- using Content.Shared.Mobs.Systems;
- using Content.Shared.PowerCell;
- using Content.Shared.Timing;
- using Content.Shared.Toggleable;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Player;
- namespace Content.Server.Medical;
- /// <summary>
- /// This handles interactions and logic relating to <see cref="DefibrillatorComponent"/>
- /// </summary>
- public sealed class DefibrillatorSystem : EntitySystem
- {
- [Dependency] private readonly ChatSystem _chatManager = default!;
- [Dependency] private readonly DamageableSystem _damageable = default!;
- [Dependency] private readonly DoAfterSystem _doAfter = default!;
- [Dependency] private readonly ElectrocutionSystem _electrocution = default!;
- [Dependency] private readonly EuiManager _euiManager = default!;
- [Dependency] private readonly ItemToggleSystem _toggle = default!;
- [Dependency] private readonly RottingSystem _rotting = default!;
- [Dependency] private readonly MobStateSystem _mobState = default!;
- [Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly PowerCellSystem _powerCell = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedMindSystem _mind = default!;
- [Dependency] private readonly UseDelaySystem _useDelay = default!;
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<DefibrillatorComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<DefibrillatorComponent, DefibrillatorZapDoAfterEvent>(OnDoAfter);
- }
- private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, AfterInteractEvent args)
- {
- if (args.Handled || args.Target is not { } target)
- return;
- args.Handled = TryStartZap(uid, target, args.User, component);
- }
- private void OnDoAfter(EntityUid uid, DefibrillatorComponent component, DefibrillatorZapDoAfterEvent args)
- {
- if (args.Handled || args.Cancelled)
- return;
- if (args.Target is not { } target)
- return;
- if (!CanZap(uid, target, args.User, component))
- return;
- args.Handled = true;
- Zap(uid, target, args.User, component);
- }
- /// <summary>
- /// Checks if you can actually defib a target.
- /// </summary>
- /// <param name="uid">Uid of the defib</param>
- /// <param name="target">Uid of the target getting defibbed</param>
- /// <param name="user">Uid of the entity using the defibrillator</param>
- /// <param name="component">Defib component</param>
- /// <param name="targetCanBeAlive">
- /// If true, the target can be alive. If false, the function will check if the target is alive and will return false if they are.
- /// </param>
- /// <returns>
- /// Returns true if the target is valid to be defibed, false otherwise.
- /// </returns>
- public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null, bool targetCanBeAlive = false)
- {
- if (!Resolve(uid, ref component))
- return false;
- if (!_toggle.IsActivated(uid))
- {
- if (user != null)
- _popup.PopupEntity(Loc.GetString("defibrillator-not-on"), uid, user.Value);
- return false;
- }
- if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay), component.DelayId))
- return false;
- if (!TryComp<MobStateComponent>(target, out var mobState))
- return false;
- if (!_powerCell.HasActivatableCharge(uid, user: user))
- return false;
- if (!targetCanBeAlive && _mobState.IsAlive(target, mobState))
- return false;
- if (!targetCanBeAlive && !component.CanDefibCrit && _mobState.IsCritical(target, mobState))
- return false;
- return true;
- }
- /// <summary>
- /// Tries to start defibrillating the target. If the target is valid, will start the defib do-after.
- /// </summary>
- /// <param name="uid">Uid of the defib</param>
- /// <param name="target">Uid of the target getting defibbed</param>
- /// <param name="user">Uid of the entity using the defibrillator</param>
- /// <param name="component">Defib component</param>
- /// <returns>
- /// Returns true if the defibrillation do-after started, otherwise false.
- /// </returns>
- public bool TryStartZap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return false;
- if (!CanZap(uid, target, user, component))
- return false;
- _audio.PlayPvs(component.ChargeSound, uid);
- return _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.DoAfterDuration, new DefibrillatorZapDoAfterEvent(),
- uid, target, uid)
- {
- NeedHand = true,
- BreakOnMove = !component.AllowDoAfterMovement
- });
- }
- /// <summary>
- /// Tries to defibrillate the target with the given defibrillator.
- /// </summary>
- public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- if (!_powerCell.TryUseActivatableCharge(uid, user: user))
- return;
- var selfEvent = new SelfBeforeDefibrillatorZapsEvent(user, uid, target);
- RaiseLocalEvent(user, selfEvent);
- target = selfEvent.DefibTarget;
- // Ensure thet new target is still valid.
- if (selfEvent.Cancelled || !CanZap(uid, target, user, component, true))
- return;
- var targetEvent = new TargetBeforeDefibrillatorZapsEvent(user, uid, target);
- RaiseLocalEvent(target, targetEvent);
- target = targetEvent.DefibTarget;
- if (targetEvent.Cancelled || !CanZap(uid, target, user, component, true))
- return;
- if (!TryComp<MobStateComponent>(target, out var mob) ||
- !TryComp<MobThresholdsComponent>(target, out var thresholds))
- return;
- _audio.PlayPvs(component.ZapSound, uid);
- _electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true);
- if (!TryComp<UseDelayComponent>(uid, out var useDelay))
- return;
- _useDelay.SetLength((uid, useDelay), component.ZapDelay, component.DelayId);
- _useDelay.TryResetDelay((uid, useDelay), id: component.DelayId);
- ICommonSession? session = null;
- var dead = true;
- if (_rotting.IsRotten(target))
- {
- _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"),
- InGameICChatType.Speak, true);
- }
- else if (TryComp<UnrevivableComponent>(target, out var unrevivable))
- {
- _chatManager.TrySendInGameICMessage(uid, Loc.GetString(unrevivable.ReasonMessage),
- InGameICChatType.Speak, true);
- }
- else
- {
- if (_mobState.IsDead(target, mob))
- _damageable.TryChangeDamage(target, component.ZapHeal, true, origin: uid);
- if (_mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var threshold) &&
- TryComp<DamageableComponent>(target, out var damageableComponent) &&
- damageableComponent.TotalDamage < threshold)
- {
- _mobState.ChangeMobState(target, MobState.Critical, mob, uid);
- dead = false;
- }
- if (_mind.TryGetMind(target, out _, out var mind) &&
- mind.Session is { } playerSession)
- {
- session = playerSession;
- // notify them they're being revived.
- if (mind.CurrentEntity != target)
- {
- _euiManager.OpenEui(new ReturnToBodyEui(mind, _mind), session);
- }
- }
- else
- {
- _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-no-mind"),
- InGameICChatType.Speak, true);
- }
- }
- var sound = dead || session == null
- ? component.FailureSound
- : component.SuccessSound;
- _audio.PlayPvs(sound, uid);
- // if we don't have enough power left for another shot, turn it off
- if (!_powerCell.HasActivatableCharge(uid))
- _toggle.TryDeactivate(uid);
- // TODO clean up this clown show above
- var ev = new TargetDefibrillatedEvent(user, (uid, component));
- RaiseLocalEvent(target, ref ev);
- }
- }
|