using System.Linq; using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Systems; 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"; private static readonly ProtoId CannotSuicideTag = "CannotSuicide"; private static readonly ProtoId DamageType = "Slash"; /// /// Run the suicide command in the console /// Should successfully kill the player and ghost them /// [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(); var entManager = server.ResolveDependency(); var playerMan = server.ResolveDependency(); var mindSystem = entManager.System(); var mobStateSystem = entManager.System(); // 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(mind.Value); mobStateComp = entManager.GetComponent(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(mindComponent.CurrentEntity, out var ghostComp) && !ghostComp.CanReturnToBody); }); }); await pair.CleanReturnAsync(); } /// /// 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 /// [Test] public async Task TestSuicideWhileDamaged() { return; // Offbrand await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, Dirty = true, DummyTicker = false }); var server = pair.Server; var consoleHost = server.ResolveDependency(); var entManager = server.ResolveDependency(); var playerMan = server.ResolveDependency(); var protoMan = server.ResolveDependency(); var damageableSystem = entManager.System(); var mindSystem = entManager.System(); var mobStateSystem = entManager.System(); // 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(mind.Value); mobStateComp = entManager.GetComponent(player); mobThresholdsComp = entManager.GetComponent(player); damageableComp = entManager.GetComponent(player); var slashProto = protoMan.Index(DamageType); 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(mindComponent.CurrentEntity, out var ghostComp) && !ghostComp.CanReturnToBody); Assert.That(damageableComp.Damage.GetTotal(), Is.EqualTo(lethalDamageThreshold)); }); }); await pair.CleanReturnAsync(); } /// /// Run the suicide command in the console /// Should only ghost the player but not kill them /// [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(); var entManager = server.ResolveDependency(); var playerMan = server.ResolveDependency(); var mindSystem = entManager.System(); var mobStateSystem = entManager.System(); var tagSystem = entManager.System(); // 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(mind.Value); mobStateComp = entManager.GetComponent(player); }); tagSystem.AddTag(player, CannotSuicideTag); // 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(mindComponent.CurrentEntity, out var ghostComp) && !ghostComp.CanReturnToBody); }); }); await pair.CleanReturnAsync(); } /// /// Run the suicide command while the player is holding an execution-capable weapon /// [Test] public async Task TestSuicideByHeldItem() { return; // Offbrand await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, Dirty = true, DummyTicker = false }); var server = pair.Server; var consoleHost = server.ResolveDependency(); var entManager = server.ResolveDependency(); var playerMan = server.ResolveDependency(); var handsSystem = entManager.System(); var mindSystem = entManager.System(); var mobStateSystem = entManager.System(); var transformSystem = entManager.System(); var damageableSystem = entManager.System(); // 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(mind.Value); mobStateComp = entManager.GetComponent(player); mobThresholdsComp = entManager.GetComponent(player); damageableComp = entManager.GetComponent(player); handsComponent = entManager.GetComponent(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.ActiveHandId!)); entManager.TryGetComponent(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(() => { // Heal all damage first (possible low pressure damage taken) damageableSystem.ClearAllDamage((player, damageableComp)); 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(mindComponent.CurrentEntity, out var ghostComp) && !ghostComp.CanReturnToBody); Assert.That(damageableComp.Damage.DamageDict["Slash"], Is.EqualTo(lethalDamageThreshold)); }); }); await pair.CleanReturnAsync(); } /// /// Run the suicide command while the player is holding an execution-capable weapon /// with damage spread between slash and blunt /// [Test] public async Task TestSuicideByHeldItemSpreadDamage() { return; // Offbrand await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, Dirty = true, DummyTicker = false }); var server = pair.Server; var consoleHost = server.ResolveDependency(); var entManager = server.ResolveDependency(); var playerMan = server.ResolveDependency(); var handsSystem = entManager.System(); var mindSystem = entManager.System(); var mobStateSystem = entManager.System(); var transformSystem = entManager.System(); var damageableSystem = entManager.System(); // 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(mind.Value); mobStateComp = entManager.GetComponent(player); mobThresholdsComp = entManager.GetComponent(player); damageableComp = entManager.GetComponent(player); handsComponent = entManager.GetComponent(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.ActiveHandId!)); entManager.TryGetComponent(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(() => { // Heal all damage first (possible low pressure damage taken) damageableSystem.ClearAllDamage((player, damageableComp)); 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(mindComponent.CurrentEntity, out var ghostComp) && !ghostComp.CanReturnToBody); Assert.That(damageableComp.Damage.DamageDict["Slash"], Is.EqualTo(lethalDamageThreshold / 2)); }); }); await pair.CleanReturnAsync(); } }