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>
This commit is contained in:
@@ -1,141 +1,154 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Mind.Components;
|
||||
|
||||
namespace Content.Server.Chat
|
||||
namespace Content.Server.Chat;
|
||||
|
||||
public sealed class SuicideSystem : EntitySystem
|
||||
{
|
||||
public sealed class SuicideSystem : EntitySystem
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly SharedSuicideSystem _suicide = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
base.Initialize();
|
||||
|
||||
public bool Suicide(EntityUid victim)
|
||||
{
|
||||
// Checks to see if the CannotSuicide tag exits, ghosts instead.
|
||||
if (_tagSystem.HasTag(victim, "CannotSuicide"))
|
||||
return false;
|
||||
|
||||
// Checks to see if the player is dead.
|
||||
if (!TryComp<MobStateComponent>(victim, out var mobState) || _mobState.IsDead(victim, mobState))
|
||||
return false;
|
||||
|
||||
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(victim):player} is attempting to suicide");
|
||||
|
||||
var suicideEvent = new SuicideEvent(victim);
|
||||
|
||||
//Check to see if there were any systems blocking this suicide
|
||||
if (SuicideAttemptBlocked(victim, suicideEvent))
|
||||
return false;
|
||||
|
||||
bool environmentSuicide = false;
|
||||
// If you are critical, you wouldn't be able to use your surroundings to suicide, so you do the default suicide
|
||||
if (!_mobState.IsCritical(victim, mobState))
|
||||
{
|
||||
environmentSuicide = EnvironmentSuicideHandler(victim, suicideEvent);
|
||||
}
|
||||
|
||||
if (suicideEvent.AttemptBlocked)
|
||||
return false;
|
||||
|
||||
DefaultSuicideHandler(victim, suicideEvent);
|
||||
|
||||
ApplyDeath(victim, suicideEvent.Kind!.Value);
|
||||
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(victim):player} suicided{(environmentSuicide ? " (environment)" : "")}");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If not handled, does the default suicide, which is biting your own tongue
|
||||
/// </summary>
|
||||
private void DefaultSuicideHandler(EntityUid victim, SuicideEvent suicideEvent)
|
||||
{
|
||||
if (suicideEvent.Handled)
|
||||
return;
|
||||
|
||||
var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", victim));
|
||||
_popup.PopupEntity(othersMessage, victim, Filter.PvsExcept(victim), true);
|
||||
|
||||
var selfMessage = Loc.GetString("suicide-command-default-text-self");
|
||||
_popup.PopupEntity(selfMessage, victim, victim);
|
||||
suicideEvent.SetHandled(SuicideKind.Bloodloss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if there are any other systems that prevent suicide
|
||||
/// </summary>
|
||||
/// <returns>Returns true if there was a blocked attempt</returns>
|
||||
private bool SuicideAttemptBlocked(EntityUid victim, SuicideEvent suicideEvent)
|
||||
{
|
||||
RaiseLocalEvent(victim, suicideEvent, true);
|
||||
|
||||
if (suicideEvent.AttemptBlocked)
|
||||
return true;
|
||||
SubscribeLocalEvent<DamageableComponent, SuicideEvent>(OnDamageableSuicide);
|
||||
SubscribeLocalEvent<MobStateComponent, SuicideEvent>(OnEnvironmentalSuicide);
|
||||
SubscribeLocalEvent<MindContainerComponent, SuicideGhostEvent>(OnSuicideGhost);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calling this function will attempt to kill the user by suiciding on objects in the surrounding area
|
||||
/// or by applying a lethal amount of damage to the user with the default method.
|
||||
/// Used when writing /suicide
|
||||
/// </summary>
|
||||
public bool Suicide(EntityUid victim)
|
||||
{
|
||||
// Can't suicide if we're already dead
|
||||
if (!TryComp<MobStateComponent>(victim, out var mobState) || _mobState.IsDead(victim, mobState))
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raise event to attempt to use held item, or surrounding entities to attempt to commit suicide
|
||||
/// </summary>
|
||||
private bool EnvironmentSuicideHandler(EntityUid victim, SuicideEvent suicideEvent)
|
||||
{
|
||||
var itemQuery = GetEntityQuery<ItemComponent>();
|
||||
|
||||
// Suicide by held item
|
||||
if (EntityManager.TryGetComponent(victim, out HandsComponent? handsComponent)
|
||||
&& handsComponent.ActiveHandEntity is { } item)
|
||||
{
|
||||
RaiseLocalEvent(item, suicideEvent, false);
|
||||
|
||||
if (suicideEvent.Handled)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Suicide by nearby entity (ex: Microwave)
|
||||
foreach (var entity in _entityLookupSystem.GetEntitiesInRange(victim, 1, LookupFlags.Approximate | LookupFlags.Static))
|
||||
{
|
||||
// Skip any nearby items that can be picked up, we already checked the active held item above
|
||||
if (itemQuery.HasComponent(entity))
|
||||
continue;
|
||||
|
||||
RaiseLocalEvent(entity, suicideEvent);
|
||||
|
||||
if (suicideEvent.Handled)
|
||||
return true;
|
||||
}
|
||||
var suicideGhostEvent = new SuicideGhostEvent(victim);
|
||||
RaiseLocalEvent(victim, suicideGhostEvent);
|
||||
|
||||
// Suicide is considered a fail if the user wasn't able to ghost
|
||||
// Suiciding with the CannotSuicide tag will ghost the player but not kill the body
|
||||
if (!suicideGhostEvent.Handled || _tagSystem.HasTag(victim, "CannotSuicide"))
|
||||
return false;
|
||||
|
||||
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(victim):player} is attempting to suicide");
|
||||
var suicideEvent = new SuicideEvent(victim);
|
||||
RaiseLocalEvent(victim, suicideEvent);
|
||||
|
||||
_adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(victim):player} suicided.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event subscription created to handle the ghosting aspect relating to suicides
|
||||
/// Mainly useful when you can raise an event in Shared and can't call Suicide() directly
|
||||
/// </summary>
|
||||
private void OnSuicideGhost(Entity<MindContainerComponent> victim, ref SuicideGhostEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (victim.Comp.Mind == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<MindComponent>(victim.Comp.Mind, out var mindComponent))
|
||||
return;
|
||||
|
||||
// CannotSuicide tag will allow the user to ghost, but also return to their mind
|
||||
// This is kind of weird, not sure what it applies to?
|
||||
if (_tagSystem.HasTag(victim, "CannotSuicide"))
|
||||
args.CanReturnToBody = true;
|
||||
|
||||
if (_gameTicker.OnGhostAttempt(victim.Comp.Mind.Value, args.CanReturnToBody, mind: mindComponent))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raise event to attempt to use held item, or surrounding entities to attempt to commit suicide
|
||||
/// </summary>
|
||||
private void OnEnvironmentalSuicide(Entity<MobStateComponent> victim, ref SuicideEvent args)
|
||||
{
|
||||
if (args.Handled || _mobState.IsCritical(victim))
|
||||
return;
|
||||
|
||||
var suicideByEnvironmentEvent = new SuicideByEnvironmentEvent(victim);
|
||||
|
||||
// Try to suicide by raising an event on the held item
|
||||
if (EntityManager.TryGetComponent(victim, out HandsComponent? handsComponent)
|
||||
&& handsComponent.ActiveHandEntity is { } item)
|
||||
{
|
||||
RaiseLocalEvent(item, suicideByEnvironmentEvent);
|
||||
if (suicideByEnvironmentEvent.Handled)
|
||||
{
|
||||
args.Handled = suicideByEnvironmentEvent.Handled;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDeath(EntityUid target, SuicideKind kind)
|
||||
// Try to suicide by nearby entities, like Microwaves or Crematoriums, by raising an event on it
|
||||
// Returns upon being handled by any entity
|
||||
var itemQuery = GetEntityQuery<ItemComponent>();
|
||||
foreach (var entity in _entityLookupSystem.GetEntitiesInRange(victim, 1, LookupFlags.Approximate | LookupFlags.Static))
|
||||
{
|
||||
if (kind == SuicideKind.Special)
|
||||
return;
|
||||
// Skip any nearby items that can be picked up, we already checked the active held item above
|
||||
if (itemQuery.HasComponent(entity))
|
||||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex<DamageTypePrototype>(kind.ToString(), out var damagePrototype))
|
||||
{
|
||||
const SuicideKind fallback = SuicideKind.Blunt;
|
||||
Log.Error($"{nameof(SuicideSystem)} could not find the damage type prototype associated with {kind}. Falling back to {fallback}");
|
||||
damagePrototype = _prototypeManager.Index<DamageTypePrototype>(fallback.ToString());
|
||||
}
|
||||
const int lethalAmountOfDamage = 200; // TODO: Would be nice to get this number from somewhere else
|
||||
_damageableSystem.TryChangeDamage(target, new(damagePrototype, lethalAmountOfDamage), true, origin: target);
|
||||
RaiseLocalEvent(entity, suicideByEnvironmentEvent);
|
||||
if (!suicideByEnvironmentEvent.Handled)
|
||||
continue;
|
||||
|
||||
args.Handled = suicideByEnvironmentEvent.Handled;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default suicide behavior for any kind of entity that can take damage
|
||||
/// </summary>
|
||||
private void OnDamageableSuicide(Entity<DamageableComponent> victim, ref SuicideEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", victim));
|
||||
_popup.PopupEntity(othersMessage, victim, Filter.PvsExcept(victim), true);
|
||||
|
||||
var selfMessage = Loc.GetString("suicide-command-default-text-self");
|
||||
_popup.PopupEntity(selfMessage, victim, victim);
|
||||
|
||||
if (args.DamageSpecifier != null)
|
||||
{
|
||||
_suicide.ApplyLethalDamage(victim, args.DamageSpecifier);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
args.DamageType ??= "Bloodloss";
|
||||
_suicide.ApplyLethalDamage(victim, args.DamageType);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user