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:
365
Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs
Normal file
365
Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Execution;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Commands;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class SuicideCommandTests
|
||||
{
|
||||
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
id: SharpTestObject
|
||||
name: very sharp test object
|
||||
components:
|
||||
- type: Item
|
||||
- type: MeleeWeapon
|
||||
damage:
|
||||
types:
|
||||
Slash: 5
|
||||
- type: Execution
|
||||
|
||||
- type: entity
|
||||
id: MixedDamageTestObject
|
||||
name: mixed damage test object
|
||||
components:
|
||||
- type: Item
|
||||
- type: MeleeWeapon
|
||||
damage:
|
||||
types:
|
||||
Slash: 5
|
||||
Blunt: 5
|
||||
- type: Execution
|
||||
|
||||
- type: entity
|
||||
id: TestMaterialReclaimer
|
||||
name: test version of the material reclaimer
|
||||
components:
|
||||
- type: MaterialReclaimer";
|
||||
|
||||
/// <summary>
|
||||
/// Run the suicide command in the console
|
||||
/// Should successfully kill the player and ghost them
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestSuicide()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Connected = true,
|
||||
Dirty = true,
|
||||
DummyTicker = false
|
||||
});
|
||||
var server = pair.Server;
|
||||
var consoleHost = server.ResolveDependency<IConsoleHost>();
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var playerMan = server.ResolveDependency<IPlayerManager>();
|
||||
var mindSystem = entManager.System<SharedMindSystem>();
|
||||
var mobStateSystem = entManager.System<MobStateSystem>();
|
||||
|
||||
// We need to know the player and whether they can be hurt, killed, and whether they have a mind
|
||||
var player = playerMan.Sessions.First().AttachedEntity!.Value;
|
||||
var mind = mindSystem.GetMind(player);
|
||||
|
||||
MindComponent mindComponent = default;
|
||||
MobStateComponent mobStateComp = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
if (mind != null)
|
||||
mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
|
||||
|
||||
mobStateComp = entManager.GetComponent<MobStateComponent>(player);
|
||||
});
|
||||
|
||||
|
||||
// Check that running the suicide command kills the player
|
||||
// and properly ghosts them without them being able to return to their body
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(mobStateSystem.IsDead(player, mobStateComp));
|
||||
Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
|
||||
!ghostComp.CanReturnToBody);
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the suicide command while the player is already injured
|
||||
/// This should only deal as much damage as necessary to get to the dead threshold
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestSuicideWhileDamaged()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Connected = true,
|
||||
Dirty = true,
|
||||
DummyTicker = false
|
||||
});
|
||||
var server = pair.Server;
|
||||
var consoleHost = server.ResolveDependency<IConsoleHost>();
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var playerMan = server.ResolveDependency<IPlayerManager>();
|
||||
var protoMan = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
var damageableSystem = entManager.System<DamageableSystem>();
|
||||
var mindSystem = entManager.System<SharedMindSystem>();
|
||||
var mobStateSystem = entManager.System<MobStateSystem>();
|
||||
|
||||
// We need to know the player and whether they can be hurt, killed, and whether they have a mind
|
||||
var player = playerMan.Sessions.First().AttachedEntity!.Value;
|
||||
var mind = mindSystem.GetMind(player);
|
||||
|
||||
MindComponent mindComponent = default;
|
||||
MobStateComponent mobStateComp = default;
|
||||
MobThresholdsComponent mobThresholdsComp = default;
|
||||
DamageableComponent damageableComp = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
if (mind != null)
|
||||
mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
|
||||
|
||||
mobStateComp = entManager.GetComponent<MobStateComponent>(player);
|
||||
mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
|
||||
damageableComp = entManager.GetComponent<DamageableComponent>(player);
|
||||
});
|
||||
|
||||
if (protoMan.TryIndex<DamageTypePrototype>("Slash", out var slashProto))
|
||||
damageableSystem.TryChangeDamage(player, new DamageSpecifier(slashProto, FixedPoint2.New(46.5)));
|
||||
|
||||
// Check that running the suicide command kills the player
|
||||
// and properly ghosts them without them being able to return to their body
|
||||
// and that all the damage is concentrated in the Slash category
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
|
||||
var lethalDamageThreshold = mobThresholdsComp.Thresholds.Keys.Last();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(mobStateSystem.IsDead(player, mobStateComp));
|
||||
Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
|
||||
!ghostComp.CanReturnToBody);
|
||||
Assert.That(damageableComp.Damage.GetTotal(), Is.EqualTo(lethalDamageThreshold));
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the suicide command in the console
|
||||
/// Should only ghost the player but not kill them
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestSuicideWhenCannotSuicide()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Connected = true,
|
||||
Dirty = true,
|
||||
DummyTicker = false
|
||||
});
|
||||
var server = pair.Server;
|
||||
var consoleHost = server.ResolveDependency<IConsoleHost>();
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var playerMan = server.ResolveDependency<IPlayerManager>();
|
||||
var mindSystem = entManager.System<SharedMindSystem>();
|
||||
var mobStateSystem = entManager.System<MobStateSystem>();
|
||||
var tagSystem = entManager.System<TagSystem>();
|
||||
|
||||
// We need to know the player and whether they can be hurt, killed, and whether they have a mind
|
||||
var player = playerMan.Sessions.First().AttachedEntity!.Value;
|
||||
var mind = mindSystem.GetMind(player);
|
||||
MindComponent mindComponent = default;
|
||||
MobStateComponent mobStateComp = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
if (mind != null)
|
||||
mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
|
||||
mobStateComp = entManager.GetComponent<MobStateComponent>(player);
|
||||
});
|
||||
|
||||
tagSystem.AddTag(player, "CannotSuicide");
|
||||
|
||||
// Check that running the suicide command kills the player
|
||||
// and properly ghosts them without them being able to return to their body
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(mobStateSystem.IsAlive(player, mobStateComp));
|
||||
Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
|
||||
!ghostComp.CanReturnToBody);
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Run the suicide command while the player is holding an execution-capable weapon
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestSuicideByHeldItem()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Connected = true,
|
||||
Dirty = true,
|
||||
DummyTicker = false
|
||||
});
|
||||
var server = pair.Server;
|
||||
var consoleHost = server.ResolveDependency<IConsoleHost>();
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var playerMan = server.ResolveDependency<IPlayerManager>();
|
||||
|
||||
var handsSystem = entManager.System<SharedHandsSystem>();
|
||||
var mindSystem = entManager.System<SharedMindSystem>();
|
||||
var mobStateSystem = entManager.System<MobStateSystem>();
|
||||
var transformSystem = entManager.System<TransformSystem>();
|
||||
|
||||
// We need to know the player and whether they can be hurt, killed, and whether they have a mind
|
||||
var player = playerMan.Sessions.First().AttachedEntity!.Value;
|
||||
var mind = mindSystem.GetMind(player);
|
||||
|
||||
MindComponent mindComponent = default;
|
||||
MobStateComponent mobStateComp = default;
|
||||
MobThresholdsComponent mobThresholdsComp = default;
|
||||
DamageableComponent damageableComp = default;
|
||||
HandsComponent handsComponent = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
if (mind != null)
|
||||
mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
|
||||
|
||||
mobStateComp = entManager.GetComponent<MobStateComponent>(player);
|
||||
mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
|
||||
damageableComp = entManager.GetComponent<DamageableComponent>(player);
|
||||
handsComponent = entManager.GetComponent<HandsComponent>(player);
|
||||
});
|
||||
|
||||
// Spawn the weapon of choice and put it in the player's hands
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var item = entManager.SpawnEntity("SharpTestObject", transformSystem.GetMapCoordinates(player));
|
||||
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
|
||||
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
|
||||
Assert.That(executionComponent, Is.Not.EqualTo(null));
|
||||
});
|
||||
|
||||
// Check that running the suicide command kills the player
|
||||
// and properly ghosts them without them being able to return to their body
|
||||
// and that all the damage is concentrated in the Slash category
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
|
||||
var lethalDamageThreshold = mobThresholdsComp.Thresholds.Keys.Last();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(mobStateSystem.IsDead(player, mobStateComp));
|
||||
Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
|
||||
!ghostComp.CanReturnToBody);
|
||||
Assert.That(damageableComp.Damage.DamageDict["Slash"], Is.EqualTo(lethalDamageThreshold));
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the suicide command while the player is holding an execution-capable weapon
|
||||
/// with damage spread between slash and blunt
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestSuicideByHeldItemSpreadDamage()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Connected = true,
|
||||
Dirty = true,
|
||||
DummyTicker = false
|
||||
});
|
||||
var server = pair.Server;
|
||||
var consoleHost = server.ResolveDependency<IConsoleHost>();
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var playerMan = server.ResolveDependency<IPlayerManager>();
|
||||
|
||||
var handsSystem = entManager.System<SharedHandsSystem>();
|
||||
var mindSystem = entManager.System<SharedMindSystem>();
|
||||
var mobStateSystem = entManager.System<MobStateSystem>();
|
||||
var transformSystem = entManager.System<TransformSystem>();
|
||||
|
||||
// We need to know the player and whether they can be hurt, killed, and whether they have a mind
|
||||
var player = playerMan.Sessions.First().AttachedEntity!.Value;
|
||||
var mind = mindSystem.GetMind(player);
|
||||
|
||||
MindComponent mindComponent = default;
|
||||
MobStateComponent mobStateComp = default;
|
||||
MobThresholdsComponent mobThresholdsComp = default;
|
||||
DamageableComponent damageableComp = default;
|
||||
HandsComponent handsComponent = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
if (mind != null)
|
||||
mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
|
||||
|
||||
mobStateComp = entManager.GetComponent<MobStateComponent>(player);
|
||||
mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
|
||||
damageableComp = entManager.GetComponent<DamageableComponent>(player);
|
||||
handsComponent = entManager.GetComponent<HandsComponent>(player);
|
||||
});
|
||||
|
||||
// Spawn the weapon of choice and put it in the player's hands
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var item = entManager.SpawnEntity("MixedDamageTestObject", transformSystem.GetMapCoordinates(player));
|
||||
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
|
||||
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
|
||||
Assert.That(executionComponent, Is.Not.EqualTo(null));
|
||||
});
|
||||
|
||||
// Check that running the suicide command kills the player
|
||||
// and properly ghosts them without them being able to return to their body
|
||||
// and that slash damage is split in half
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
|
||||
var lethalDamageThreshold = mobThresholdsComp.Thresholds.Keys.Last();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(mobStateSystem.IsDead(player, mobStateComp));
|
||||
Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
|
||||
!ghostComp.CanReturnToBody);
|
||||
Assert.That(damageableComp.Damage.DamageDict["Slash"], Is.EqualTo(lethalDamageThreshold / 2));
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user