SharedExecutionSystem.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. using Content.Shared.ActionBlocker;
  2. using Content.Shared.Chat;
  3. using Content.Shared.CombatMode;
  4. using Content.Shared.Damage;
  5. using Content.Shared.Database;
  6. using Content.Shared.DoAfter;
  7. using Content.Shared.IdentityManagement;
  8. using Content.Shared.Mobs.Components;
  9. using Content.Shared.Mobs.Systems;
  10. using Content.Shared.Popups;
  11. using Content.Shared.Verbs;
  12. using Content.Shared.Weapons.Melee;
  13. using Content.Shared.Weapons.Melee.Events;
  14. using Content.Shared.Interaction.Events;
  15. using Content.Shared.Mind;
  16. using Robust.Shared.Player;
  17. using Robust.Shared.Audio.Systems;
  18. namespace Content.Shared.Execution;
  19. /// <summary>
  20. /// Verb for violently murdering cuffed creatures.
  21. /// </summary>
  22. public sealed class SharedExecutionSystem : EntitySystem
  23. {
  24. [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
  25. [Dependency] private readonly SharedAudioSystem _audio = default!;
  26. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  27. [Dependency] private readonly MobStateSystem _mobState = default!;
  28. [Dependency] private readonly SharedPopupSystem _popup = default!;
  29. [Dependency] private readonly SharedSuicideSystem _suicide = default!;
  30. [Dependency] private readonly SharedCombatModeSystem _combat = default!;
  31. [Dependency] private readonly SharedExecutionSystem _execution = default!;
  32. [Dependency] private readonly SharedMeleeWeaponSystem _melee = default!;
  33. /// <inheritdoc/>
  34. public override void Initialize()
  35. {
  36. base.Initialize();
  37. SubscribeLocalEvent<ExecutionComponent, GetVerbsEvent<UtilityVerb>>(OnGetInteractionsVerbs);
  38. SubscribeLocalEvent<ExecutionComponent, GetMeleeDamageEvent>(OnGetMeleeDamage);
  39. SubscribeLocalEvent<ExecutionComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
  40. SubscribeLocalEvent<ExecutionComponent, ExecutionDoAfterEvent>(OnExecutionDoAfter);
  41. }
  42. private void OnGetInteractionsVerbs(EntityUid uid, ExecutionComponent comp, GetVerbsEvent<UtilityVerb> args)
  43. {
  44. if (args.Hands == null || args.Using == null || !args.CanAccess || !args.CanInteract)
  45. return;
  46. var attacker = args.User;
  47. var weapon = args.Using.Value;
  48. var victim = args.Target;
  49. if (!CanBeExecuted(victim, attacker))
  50. return;
  51. UtilityVerb verb = new()
  52. {
  53. Act = () => TryStartExecutionDoAfter(weapon, victim, attacker, comp),
  54. Impact = LogImpact.High,
  55. Text = Loc.GetString("execution-verb-name"),
  56. Message = Loc.GetString("execution-verb-message"),
  57. };
  58. args.Verbs.Add(verb);
  59. }
  60. private void TryStartExecutionDoAfter(EntityUid weapon, EntityUid victim, EntityUid attacker, ExecutionComponent comp)
  61. {
  62. if (!CanBeExecuted(victim, attacker))
  63. return;
  64. if (attacker == victim)
  65. {
  66. ShowExecutionInternalPopup(comp.InternalSelfExecutionMessage, attacker, victim, weapon);
  67. ShowExecutionExternalPopup(comp.ExternalSelfExecutionMessage, attacker, victim, weapon);
  68. }
  69. else
  70. {
  71. ShowExecutionInternalPopup(comp.InternalMeleeExecutionMessage, attacker, victim, weapon);
  72. ShowExecutionExternalPopup(comp.ExternalMeleeExecutionMessage, attacker, victim, weapon);
  73. }
  74. var doAfter =
  75. new DoAfterArgs(EntityManager, attacker, comp.DoAfterDuration, new ExecutionDoAfterEvent(), weapon, target: victim, used: weapon)
  76. {
  77. BreakOnMove = true,
  78. BreakOnDamage = true,
  79. NeedHand = true
  80. };
  81. _doAfter.TryStartDoAfter(doAfter);
  82. }
  83. public bool CanBeExecuted(EntityUid victim, EntityUid attacker)
  84. {
  85. // No point executing someone if they can't take damage
  86. if (!HasComp<DamageableComponent>(victim))
  87. return false;
  88. // You can't execute something that cannot die
  89. if (!TryComp<MobStateComponent>(victim, out var mobState))
  90. return false;
  91. // You're not allowed to execute dead people (no fun allowed)
  92. if (_mobState.IsDead(victim, mobState))
  93. return false;
  94. // You must be able to attack people to execute
  95. if (!_actionBlocker.CanAttack(attacker, victim))
  96. return false;
  97. // The victim must be incapacitated to be executed
  98. if (victim != attacker && _actionBlocker.CanInteract(victim, null))
  99. return false;
  100. // All checks passed
  101. return true;
  102. }
  103. private void OnGetMeleeDamage(Entity<ExecutionComponent> entity, ref GetMeleeDamageEvent args)
  104. {
  105. if (!TryComp<MeleeWeaponComponent>(entity, out var melee) || !entity.Comp.Executing)
  106. {
  107. return;
  108. }
  109. var bonus = melee.Damage * entity.Comp.DamageMultiplier - melee.Damage;
  110. args.Damage += bonus;
  111. args.ResistanceBypass = true;
  112. }
  113. private void OnSuicideByEnvironment(Entity<ExecutionComponent> entity, ref SuicideByEnvironmentEvent args)
  114. {
  115. if (!TryComp<MeleeWeaponComponent>(entity, out var melee))
  116. return;
  117. string? internalMsg = entity.Comp.CompleteInternalSelfExecutionMessage;
  118. string? externalMsg = entity.Comp.CompleteExternalSelfExecutionMessage;
  119. if (!TryComp<DamageableComponent>(args.Victim, out var damageableComponent))
  120. return;
  121. ShowExecutionInternalPopup(internalMsg, args.Victim, args.Victim, entity, false);
  122. ShowExecutionExternalPopup(externalMsg, args.Victim, args.Victim, entity);
  123. _audio.PlayPredicted(melee.HitSound, args.Victim, args.Victim);
  124. _suicide.ApplyLethalDamage((args.Victim, damageableComponent), melee.Damage);
  125. args.Handled = true;
  126. }
  127. private void ShowExecutionInternalPopup(string locString, EntityUid attacker, EntityUid victim, EntityUid weapon, bool predict = true)
  128. {
  129. if (predict)
  130. {
  131. _popup.PopupClient(
  132. Loc.GetString(locString, ("attacker", Identity.Entity(attacker, EntityManager)), ("victim", Identity.Entity(victim, EntityManager)), ("weapon", weapon)),
  133. attacker,
  134. attacker,
  135. PopupType.MediumCaution
  136. );
  137. }
  138. else
  139. {
  140. _popup.PopupEntity(
  141. Loc.GetString(locString, ("attacker", Identity.Entity(attacker, EntityManager)), ("victim", Identity.Entity(victim, EntityManager)), ("weapon", weapon)),
  142. attacker,
  143. attacker,
  144. PopupType.MediumCaution
  145. );
  146. }
  147. }
  148. private void ShowExecutionExternalPopup(string locString, EntityUid attacker, EntityUid victim, EntityUid weapon)
  149. {
  150. _popup.PopupEntity(
  151. Loc.GetString(locString, ("attacker", Identity.Entity(attacker, EntityManager)), ("victim", Identity.Entity(victim, EntityManager)), ("weapon", weapon)),
  152. attacker,
  153. Filter.PvsExcept(attacker),
  154. true,
  155. PopupType.MediumCaution
  156. );
  157. }
  158. private void OnExecutionDoAfter(Entity<ExecutionComponent> entity, ref ExecutionDoAfterEvent args)
  159. {
  160. if (args.Handled || args.Cancelled || args.Used == null || args.Target == null)
  161. return;
  162. if (!TryComp<MeleeWeaponComponent>(entity, out var meleeWeaponComp))
  163. return;
  164. var attacker = args.User;
  165. var victim = args.Target.Value;
  166. var weapon = args.Used.Value;
  167. if (!_execution.CanBeExecuted(victim, attacker))
  168. return;
  169. // This is needed so the melee system does not stop it.
  170. var prev = _combat.IsInCombatMode(attacker);
  171. _combat.SetInCombatMode(attacker, true);
  172. entity.Comp.Executing = true;
  173. var internalMsg = entity.Comp.CompleteInternalMeleeExecutionMessage;
  174. var externalMsg = entity.Comp.CompleteExternalMeleeExecutionMessage;
  175. if (attacker == victim)
  176. {
  177. var suicideEvent = new SuicideEvent(victim);
  178. RaiseLocalEvent(victim, suicideEvent);
  179. var suicideGhostEvent = new SuicideGhostEvent(victim);
  180. RaiseLocalEvent(victim, suicideGhostEvent);
  181. }
  182. else
  183. {
  184. _melee.AttemptLightAttack(attacker, weapon, meleeWeaponComp, victim);
  185. }
  186. _combat.SetInCombatMode(attacker, prev);
  187. entity.Comp.Executing = false;
  188. args.Handled = true;
  189. if (attacker != victim)
  190. {
  191. _execution.ShowExecutionInternalPopup(internalMsg, attacker, victim, entity);
  192. _execution.ShowExecutionExternalPopup(externalMsg, attacker, victim, entity);
  193. }
  194. }
  195. }