Files
tbd-station-14/Content.Shared/Execution/SharedExecutionSystem.cs
Scribbles0 220aff21eb Melee Executions (#30104)
* melee executions

* fix damage bug

* cleanup

* address reviews hopefully

* resistance bypass mechanic

* component changes

* self executions (not finished yet)

* self execs part two

* ok i fixed things (still not finished)

* finish everything

* review stuff

* nuke if (kind = special)

* more review stuffs

* Make suicide system much less hardcoded and make much more use of events

* Fix a dumb bug I introduced

* self execution popups

* Integration tests

* Why did they even take 0.5 blunt damage?

* More consistent integration tests

* Destructive equals true

* Allow it to dirty-dispose

* IS THIS WHAT YOU WANT?

* FRESH AND CLEAN

* modifier to multiplier

* don't jinx the integration tests

* no file-scoped namespace

* Move the rest of execution to shared, create SuicideGhostEvent

* handled

* Get rid of unused code and add a comment

* ghost before suicide

* stop cat suicides

* popup fix + small suicide change

* make it a bit better

---------

Co-authored-by: Plykiya <58439124+Plykiya@users.noreply.github.com>
2024-08-11 13:05:54 +10:00

235 lines
8.4 KiB
C#

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;
/// <summary>
/// Verb for violently murdering cuffed creatures.
/// </summary>
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!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ExecutionComponent, GetVerbsEvent<UtilityVerb>>(OnGetInteractionsVerbs);
SubscribeLocalEvent<ExecutionComponent, GetMeleeDamageEvent>(OnGetMeleeDamage);
SubscribeLocalEvent<ExecutionComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
SubscribeLocalEvent<ExecutionComponent, ExecutionDoAfterEvent>(OnExecutionDoAfter);
}
private void OnGetInteractionsVerbs(EntityUid uid, ExecutionComponent comp, GetVerbsEvent<UtilityVerb> 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<DamageableComponent>(victim))
return false;
// You can't execute something that cannot die
if (!TryComp<MobStateComponent>(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<ExecutionComponent> entity, ref GetMeleeDamageEvent args)
{
if (!TryComp<MeleeWeaponComponent>(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<ExecutionComponent> entity, ref SuicideByEnvironmentEvent args)
{
if (!TryComp<MeleeWeaponComponent>(entity, out var melee))
return;
string? internalMsg = entity.Comp.CompleteInternalSelfExecutionMessage;
string? externalMsg = entity.Comp.CompleteExternalSelfExecutionMessage;
if (!TryComp<DamageableComponent>(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<ExecutionComponent> entity, ref ExecutionDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Used == null || args.Target == null)
return;
if (!TryComp<MeleeWeaponComponent>(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);
}
}
}