using Content.Shared.ActionBlocker; using Content.Shared.Chat; using Content.Shared.CombatMode; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.DoAfter; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Content.Shared.Verbs; using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Interaction.Events; using Content.Shared.Mind; using Robust.Shared.Player; using Robust.Shared.Audio.Systems; namespace Content.Shared.Execution; /// /// Verb for violently murdering cuffed creatures. /// public sealed class SharedExecutionSystem : EntitySystem { [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedSuicideSystem _suicide = default!; [Dependency] private readonly SharedCombatModeSystem _combat = default!; [Dependency] private readonly SharedExecutionSystem _execution = default!; [Dependency] private readonly SharedMeleeWeaponSystem _melee = default!; /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent>(OnGetInteractionsVerbs); SubscribeLocalEvent(OnGetMeleeDamage); SubscribeLocalEvent(OnSuicideByEnvironment); SubscribeLocalEvent(OnExecutionDoAfter); } private void OnGetInteractionsVerbs(EntityUid uid, ExecutionComponent comp, GetVerbsEvent args) { if (args.Hands == null || args.Using == null || !args.CanAccess || !args.CanInteract) return; var attacker = args.User; var weapon = args.Using.Value; var victim = args.Target; if (!CanBeExecuted(victim, attacker)) return; UtilityVerb verb = new() { Act = () => TryStartExecutionDoAfter(weapon, victim, attacker, comp), Impact = LogImpact.High, Text = Loc.GetString("execution-verb-name"), Message = Loc.GetString("execution-verb-message"), }; args.Verbs.Add(verb); } private void TryStartExecutionDoAfter(EntityUid weapon, EntityUid victim, EntityUid attacker, ExecutionComponent comp) { if (!CanBeExecuted(victim, attacker)) return; if (attacker == victim) { ShowExecutionInternalPopup(comp.InternalSelfExecutionMessage, attacker, victim, weapon); ShowExecutionExternalPopup(comp.ExternalSelfExecutionMessage, attacker, victim, weapon); } else { ShowExecutionInternalPopup(comp.InternalMeleeExecutionMessage, attacker, victim, weapon); ShowExecutionExternalPopup(comp.ExternalMeleeExecutionMessage, attacker, victim, weapon); } var doAfter = new DoAfterArgs(EntityManager, attacker, comp.DoAfterDuration, new ExecutionDoAfterEvent(), weapon, target: victim, used: weapon) { BreakOnMove = true, BreakOnDamage = true, NeedHand = true }; _doAfter.TryStartDoAfter(doAfter); } public bool CanBeExecuted(EntityUid victim, EntityUid attacker) { // No point executing someone if they can't take damage if (!HasComp(victim)) return false; // You can't execute something that cannot die if (!TryComp(victim, out var mobState)) return false; // You're not allowed to execute dead people (no fun allowed) if (_mobState.IsDead(victim, mobState)) return false; // You must be able to attack people to execute if (!_actionBlocker.CanAttack(attacker, victim)) return false; // The victim must be incapacitated to be executed if (victim != attacker && _actionBlocker.CanInteract(victim, null)) return false; // All checks passed return true; } private void OnGetMeleeDamage(Entity entity, ref GetMeleeDamageEvent args) { if (!TryComp(entity, out var melee) || !entity.Comp.Executing) { return; } var bonus = melee.Damage * entity.Comp.DamageMultiplier - melee.Damage; args.Damage += bonus; args.ResistanceBypass = true; } private void OnSuicideByEnvironment(Entity entity, ref SuicideByEnvironmentEvent args) { if (!TryComp(entity, out var melee)) return; string? internalMsg = entity.Comp.CompleteInternalSelfExecutionMessage; string? externalMsg = entity.Comp.CompleteExternalSelfExecutionMessage; if (!TryComp(args.Victim, out var damageableComponent)) return; ShowExecutionInternalPopup(internalMsg, args.Victim, args.Victim, entity, false); ShowExecutionExternalPopup(externalMsg, args.Victim, args.Victim, entity); _audio.PlayPredicted(melee.HitSound, args.Victim, args.Victim); _suicide.ApplyLethalDamage((args.Victim, damageableComponent), melee.Damage); args.Handled = true; } private void ShowExecutionInternalPopup(string locString, EntityUid attacker, EntityUid victim, EntityUid weapon, bool predict = true) { if (predict) { _popup.PopupClient( Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)), attacker, attacker, PopupType.MediumCaution ); } else { _popup.PopupEntity( Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)), attacker, attacker, PopupType.MediumCaution ); } } private void ShowExecutionExternalPopup(string locString, EntityUid attacker, EntityUid victim, EntityUid weapon) { _popup.PopupEntity( Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)), attacker, Filter.PvsExcept(attacker), true, PopupType.MediumCaution ); } private void OnExecutionDoAfter(Entity entity, ref ExecutionDoAfterEvent args) { if (args.Handled || args.Cancelled || args.Used == null || args.Target == null) return; if (!TryComp(entity, out var meleeWeaponComp)) return; var attacker = args.User; var victim = args.Target.Value; var weapon = args.Used.Value; if (!_execution.CanBeExecuted(victim, attacker)) return; // This is needed so the melee system does not stop it. var prev = _combat.IsInCombatMode(attacker); _combat.SetInCombatMode(attacker, true); entity.Comp.Executing = true; var internalMsg = entity.Comp.CompleteInternalMeleeExecutionMessage; var externalMsg = entity.Comp.CompleteExternalMeleeExecutionMessage; if (attacker == victim) { var suicideEvent = new SuicideEvent(victim); RaiseLocalEvent(victim, suicideEvent); var suicideGhostEvent = new SuicideGhostEvent(victim); RaiseLocalEvent(victim, suicideGhostEvent); } else { _melee.AttemptLightAttack(attacker, weapon, meleeWeaponComp, victim); } _combat.SetInCombatMode(attacker, prev); entity.Comp.Executing = false; args.Handled = true; if (attacker != victim) { _execution.ShowExecutionInternalPopup(internalMsg, attacker, victim, entity); _execution.ShowExecutionExternalPopup(externalMsg, attacker, victim, entity); } } }