using Content.Shared.Weapons.Ranged.Systems;
using Content.Shared.ActionBlocker;
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.Weapons.Ranged.Components;
using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Content.Shared.Execution;
///
/// Verb for violently murdering cuffed creatures.
///
public sealed class ExecutionSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SharedGunSystem _gunSystem = default!;
[Dependency] private readonly SharedCombatModeSystem _combatSystem = default!;
[Dependency] private readonly SharedMeleeWeaponSystem _meleeSystem = default!;
// TODO: Still needs more cleaning up.
private const string DefaultInternalMeleeExecutionMessage = "execution-popup-melee-initial-internal";
private const string DefaultExternalMeleeExecutionMessage = "execution-popup-melee-initial-external";
private const string DefaultCompleteInternalMeleeExecutionMessage = "execution-popup-melee-complete-internal";
private const string DefaultCompleteExternalMeleeExecutionMessage = "execution-popup-melee-complete-external";
private const string DefaultInternalGunExecutionMessage = "execution-popup-gun-initial-internal";
private const string DefaultExternalGunExecutionMessage = "execution-popup-gun-initial-external";
private const string DefaultCompleteInternalGunExecutionMessage = "execution-popup-gun-complete-internal";
private const string DefaultCompleteExternalGunExecutionMessage = "execution-popup-gun-complete-external";
///
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent>(OnGetInteractionsVerbs);
SubscribeLocalEvent(OnExecutionDoAfter);
SubscribeLocalEvent(OnGetMeleeDamage);
}
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 (!CanExecuteWithAny(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 (!CanExecuteWithAny(victim, attacker))
return;
// TODO: This should just be on the weapons as a single execution message.
var defaultExecutionInternal = DefaultInternalMeleeExecutionMessage;
var defaultExecutionExternal = DefaultExternalMeleeExecutionMessage;
if (HasComp(weapon))
{
defaultExecutionExternal = DefaultInternalGunExecutionMessage;
defaultExecutionInternal = DefaultExternalGunExecutionMessage;
}
var internalMsg = defaultExecutionInternal;
var externalMsg = defaultExecutionExternal;
ShowExecutionInternalPopup(internalMsg, attacker, victim, weapon);
ShowExecutionExternalPopup(externalMsg, attacker, victim, weapon);
var doAfter =
new DoAfterArgs(EntityManager, attacker, comp.DoAfterDuration, new ExecutionDoAfterEvent(), weapon, target: victim, used: weapon)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
NeedHand = true
};
_doAfterSystem.TryStartDoAfter(doAfter);
}
private bool CanExecuteWithAny(EntityUid victim, EntityUid attacker)
{
// Use suicide.
if (victim == attacker)
return false;
// No point executing someone if they can't take damage
if (!TryComp(victim, out _))
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 (_mobStateSystem.IsDead(victim, mobState))
return false;
// You must be able to attack people to execute
if (!_actionBlockerSystem.CanAttack(attacker, victim))
return false;
// The victim must be incapacitated to be executed
if (victim != attacker && _actionBlockerSystem.CanInteract(victim, null))
return false;
// All checks passed
return true;
}
private void OnExecutionDoAfter(EntityUid uid, ExecutionComponent component, ExecutionDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Used == null || args.Target == null)
return;
var attacker = args.User;
var victim = args.Target.Value;
var weapon = args.Used.Value;
if (!CanExecuteWithAny(victim, attacker))
return;
// This is needed so the melee system does not stop it.
var prev = _combatSystem.IsInCombatMode(attacker);
_combatSystem.SetInCombatMode(attacker, true);
component.Executing = true;
string? internalMsg = null;
string? externalMsg = null;
if (TryComp(uid, out MeleeWeaponComponent? melee))
{
_meleeSystem.AttemptLightAttack(attacker, weapon, melee, victim);
internalMsg = DefaultCompleteInternalMeleeExecutionMessage;
externalMsg = DefaultCompleteExternalMeleeExecutionMessage;
}
else if (TryComp(uid, out GunComponent? gun))
{
var clumsyShot = false;
// TODO: This should just be an event or something instead to get this.
// TODO: Handle clumsy.
if (!_gunSystem.AttemptDirectShoot(args.User, uid, args.Target.Value, gun))
{
internalMsg = null;
externalMsg = null;
}
else
{
internalMsg = DefaultCompleteInternalGunExecutionMessage;
externalMsg = DefaultCompleteExternalGunExecutionMessage;
}
args.Handled = true;
}
_combatSystem.SetInCombatMode(attacker, prev);
component.Executing = false;
args.Handled = true;
if (internalMsg != null && externalMsg != null)
{
ShowExecutionInternalPopup(internalMsg, attacker, victim, uid);
ShowExecutionExternalPopup(externalMsg, attacker, victim, uid);
}
}
private void OnGetMeleeDamage(EntityUid uid, ExecutionComponent comp, ref GetMeleeDamageEvent args)
{
if (!TryComp(uid, out var melee) ||
!TryComp(uid, out var execComp) ||
!execComp.Executing)
{
return;
}
var bonus = melee.Damage * execComp.DamageModifier - melee.Damage;
args.Damage += bonus;
}
private void ShowExecutionInternalPopup(string locString,
EntityUid attacker, EntityUid victim, EntityUid weapon, bool predict = true)
{
if (predict)
{
_popupSystem.PopupClient(
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
attacker,
attacker,
PopupType.Medium
);
}
else
{
_popupSystem.PopupEntity(
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
attacker,
Filter.Entities(attacker),
true,
PopupType.Medium
);
}
}
private void ShowExecutionExternalPopup(string locString, EntityUid attacker, EntityUid victim, EntityUid weapon)
{
_popupSystem.PopupEntity(
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
attacker,
Filter.PvsExcept(attacker),
true,
PopupType.MediumCaution
);
}
}