DefibrillatorSystem.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using Content.Server.Atmos.Rotting;
  2. using Content.Server.Chat.Systems;
  3. using Content.Server.DoAfter;
  4. using Content.Server.Electrocution;
  5. using Content.Server.EUI;
  6. using Content.Server.Ghost;
  7. using Content.Server.Popups;
  8. using Content.Server.PowerCell;
  9. using Content.Shared.Traits.Assorted;
  10. using Content.Shared.Damage;
  11. using Content.Shared.DoAfter;
  12. using Content.Shared.Interaction;
  13. using Content.Shared.Interaction.Components;
  14. using Content.Shared.Interaction.Events;
  15. using Content.Shared.Item.ItemToggle;
  16. using Content.Shared.Medical;
  17. using Content.Shared.Mind;
  18. using Content.Shared.Mobs;
  19. using Content.Shared.Mobs.Components;
  20. using Content.Shared.Mobs.Systems;
  21. using Content.Shared.PowerCell;
  22. using Content.Shared.Timing;
  23. using Content.Shared.Toggleable;
  24. using Robust.Shared.Audio.Systems;
  25. using Robust.Shared.Player;
  26. namespace Content.Server.Medical;
  27. /// <summary>
  28. /// This handles interactions and logic relating to <see cref="DefibrillatorComponent"/>
  29. /// </summary>
  30. public sealed class DefibrillatorSystem : EntitySystem
  31. {
  32. [Dependency] private readonly ChatSystem _chatManager = default!;
  33. [Dependency] private readonly DamageableSystem _damageable = default!;
  34. [Dependency] private readonly DoAfterSystem _doAfter = default!;
  35. [Dependency] private readonly ElectrocutionSystem _electrocution = default!;
  36. [Dependency] private readonly EuiManager _euiManager = default!;
  37. [Dependency] private readonly ItemToggleSystem _toggle = default!;
  38. [Dependency] private readonly RottingSystem _rotting = default!;
  39. [Dependency] private readonly MobStateSystem _mobState = default!;
  40. [Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
  41. [Dependency] private readonly PopupSystem _popup = default!;
  42. [Dependency] private readonly PowerCellSystem _powerCell = default!;
  43. [Dependency] private readonly SharedAudioSystem _audio = default!;
  44. [Dependency] private readonly SharedMindSystem _mind = default!;
  45. [Dependency] private readonly UseDelaySystem _useDelay = default!;
  46. /// <inheritdoc/>
  47. public override void Initialize()
  48. {
  49. SubscribeLocalEvent<DefibrillatorComponent, AfterInteractEvent>(OnAfterInteract);
  50. SubscribeLocalEvent<DefibrillatorComponent, DefibrillatorZapDoAfterEvent>(OnDoAfter);
  51. }
  52. private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, AfterInteractEvent args)
  53. {
  54. if (args.Handled || args.Target is not { } target)
  55. return;
  56. args.Handled = TryStartZap(uid, target, args.User, component);
  57. }
  58. private void OnDoAfter(EntityUid uid, DefibrillatorComponent component, DefibrillatorZapDoAfterEvent args)
  59. {
  60. if (args.Handled || args.Cancelled)
  61. return;
  62. if (args.Target is not { } target)
  63. return;
  64. if (!CanZap(uid, target, args.User, component))
  65. return;
  66. args.Handled = true;
  67. Zap(uid, target, args.User, component);
  68. }
  69. /// <summary>
  70. /// Checks if you can actually defib a target.
  71. /// </summary>
  72. /// <param name="uid">Uid of the defib</param>
  73. /// <param name="target">Uid of the target getting defibbed</param>
  74. /// <param name="user">Uid of the entity using the defibrillator</param>
  75. /// <param name="component">Defib component</param>
  76. /// <param name="targetCanBeAlive">
  77. /// 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.
  78. /// </param>
  79. /// <returns>
  80. /// Returns true if the target is valid to be defibed, false otherwise.
  81. /// </returns>
  82. public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null, bool targetCanBeAlive = false)
  83. {
  84. if (!Resolve(uid, ref component))
  85. return false;
  86. if (!_toggle.IsActivated(uid))
  87. {
  88. if (user != null)
  89. _popup.PopupEntity(Loc.GetString("defibrillator-not-on"), uid, user.Value);
  90. return false;
  91. }
  92. if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay), component.DelayId))
  93. return false;
  94. if (!TryComp<MobStateComponent>(target, out var mobState))
  95. return false;
  96. if (!_powerCell.HasActivatableCharge(uid, user: user))
  97. return false;
  98. if (!targetCanBeAlive && _mobState.IsAlive(target, mobState))
  99. return false;
  100. if (!targetCanBeAlive && !component.CanDefibCrit && _mobState.IsCritical(target, mobState))
  101. return false;
  102. return true;
  103. }
  104. /// <summary>
  105. /// Tries to start defibrillating the target. If the target is valid, will start the defib do-after.
  106. /// </summary>
  107. /// <param name="uid">Uid of the defib</param>
  108. /// <param name="target">Uid of the target getting defibbed</param>
  109. /// <param name="user">Uid of the entity using the defibrillator</param>
  110. /// <param name="component">Defib component</param>
  111. /// <returns>
  112. /// Returns true if the defibrillation do-after started, otherwise false.
  113. /// </returns>
  114. public bool TryStartZap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
  115. {
  116. if (!Resolve(uid, ref component))
  117. return false;
  118. if (!CanZap(uid, target, user, component))
  119. return false;
  120. _audio.PlayPvs(component.ChargeSound, uid);
  121. return _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.DoAfterDuration, new DefibrillatorZapDoAfterEvent(),
  122. uid, target, uid)
  123. {
  124. NeedHand = true,
  125. BreakOnMove = !component.AllowDoAfterMovement
  126. });
  127. }
  128. /// <summary>
  129. /// Tries to defibrillate the target with the given defibrillator.
  130. /// </summary>
  131. public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
  132. {
  133. if (!Resolve(uid, ref component))
  134. return;
  135. if (!_powerCell.TryUseActivatableCharge(uid, user: user))
  136. return;
  137. var selfEvent = new SelfBeforeDefibrillatorZapsEvent(user, uid, target);
  138. RaiseLocalEvent(user, selfEvent);
  139. target = selfEvent.DefibTarget;
  140. // Ensure thet new target is still valid.
  141. if (selfEvent.Cancelled || !CanZap(uid, target, user, component, true))
  142. return;
  143. var targetEvent = new TargetBeforeDefibrillatorZapsEvent(user, uid, target);
  144. RaiseLocalEvent(target, targetEvent);
  145. target = targetEvent.DefibTarget;
  146. if (targetEvent.Cancelled || !CanZap(uid, target, user, component, true))
  147. return;
  148. if (!TryComp<MobStateComponent>(target, out var mob) ||
  149. !TryComp<MobThresholdsComponent>(target, out var thresholds))
  150. return;
  151. _audio.PlayPvs(component.ZapSound, uid);
  152. _electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true);
  153. if (!TryComp<UseDelayComponent>(uid, out var useDelay))
  154. return;
  155. _useDelay.SetLength((uid, useDelay), component.ZapDelay, component.DelayId);
  156. _useDelay.TryResetDelay((uid, useDelay), id: component.DelayId);
  157. ICommonSession? session = null;
  158. var dead = true;
  159. if (_rotting.IsRotten(target))
  160. {
  161. _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"),
  162. InGameICChatType.Speak, true);
  163. }
  164. else if (TryComp<UnrevivableComponent>(target, out var unrevivable))
  165. {
  166. _chatManager.TrySendInGameICMessage(uid, Loc.GetString(unrevivable.ReasonMessage),
  167. InGameICChatType.Speak, true);
  168. }
  169. else
  170. {
  171. if (_mobState.IsDead(target, mob))
  172. _damageable.TryChangeDamage(target, component.ZapHeal, true, origin: uid);
  173. if (_mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var threshold) &&
  174. TryComp<DamageableComponent>(target, out var damageableComponent) &&
  175. damageableComponent.TotalDamage < threshold)
  176. {
  177. _mobState.ChangeMobState(target, MobState.Critical, mob, uid);
  178. dead = false;
  179. }
  180. if (_mind.TryGetMind(target, out _, out var mind) &&
  181. mind.Session is { } playerSession)
  182. {
  183. session = playerSession;
  184. // notify them they're being revived.
  185. if (mind.CurrentEntity != target)
  186. {
  187. _euiManager.OpenEui(new ReturnToBodyEui(mind, _mind), session);
  188. }
  189. }
  190. else
  191. {
  192. _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-no-mind"),
  193. InGameICChatType.Speak, true);
  194. }
  195. }
  196. var sound = dead || session == null
  197. ? component.FailureSound
  198. : component.SuccessSound;
  199. _audio.PlayPvs(sound, uid);
  200. // if we don't have enough power left for another shot, turn it off
  201. if (!_powerCell.HasActivatableCharge(uid))
  202. _toggle.TryDeactivate(uid);
  203. // TODO clean up this clown show above
  204. var ev = new TargetDefibrillatedEvent(user, (uid, component));
  205. RaiseLocalEvent(target, ref ev);
  206. }
  207. }