1
0

SuicideCommandTests.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. using System.Linq;
  2. using Content.Shared.Damage;
  3. using Content.Shared.Damage.Prototypes;
  4. using Content.Shared.Execution;
  5. using Content.Shared.FixedPoint;
  6. using Content.Shared.Ghost;
  7. using Content.Shared.Hands.Components;
  8. using Content.Shared.Hands.EntitySystems;
  9. using Content.Shared.Mind;
  10. using Content.Shared.Mobs.Components;
  11. using Content.Shared.Mobs.Systems;
  12. using Content.Shared.Tag;
  13. using Robust.Server.GameObjects;
  14. using Robust.Server.Player;
  15. using Robust.Shared.Console;
  16. using Robust.Shared.GameObjects;
  17. using Robust.Shared.Prototypes;
  18. namespace Content.IntegrationTests.Tests.Commands;
  19. [TestFixture]
  20. public sealed class SuicideCommandTests
  21. {
  22. [TestPrototypes]
  23. private const string Prototypes = @"
  24. - type: entity
  25. id: SharpTestObject
  26. name: very sharp test object
  27. components:
  28. - type: Item
  29. - type: MeleeWeapon
  30. damage:
  31. types:
  32. Slash: 5
  33. - type: Execution
  34. - type: entity
  35. id: MixedDamageTestObject
  36. name: mixed damage test object
  37. components:
  38. - type: Item
  39. - type: MeleeWeapon
  40. damage:
  41. types:
  42. Slash: 5
  43. Blunt: 5
  44. - type: Execution
  45. - type: entity
  46. id: TestMaterialReclaimer
  47. name: test version of the material reclaimer
  48. components:
  49. - type: MaterialReclaimer";
  50. /// <summary>
  51. /// Run the suicide command in the console
  52. /// Should successfully kill the player and ghost them
  53. /// </summary>
  54. [Test]
  55. public async Task TestSuicide()
  56. {
  57. await using var pair = await PoolManager.GetServerClient(new PoolSettings
  58. {
  59. Connected = true,
  60. Dirty = true,
  61. DummyTicker = false
  62. });
  63. var server = pair.Server;
  64. var consoleHost = server.ResolveDependency<IConsoleHost>();
  65. var entManager = server.ResolveDependency<IEntityManager>();
  66. var playerMan = server.ResolveDependency<IPlayerManager>();
  67. var mindSystem = entManager.System<SharedMindSystem>();
  68. var mobStateSystem = entManager.System<MobStateSystem>();
  69. // We need to know the player and whether they can be hurt, killed, and whether they have a mind
  70. var player = playerMan.Sessions.First().AttachedEntity!.Value;
  71. var mind = mindSystem.GetMind(player);
  72. MindComponent mindComponent = default;
  73. MobStateComponent mobStateComp = default;
  74. await server.WaitPost(() =>
  75. {
  76. if (mind != null)
  77. mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
  78. mobStateComp = entManager.GetComponent<MobStateComponent>(player);
  79. });
  80. // Check that running the suicide command kills the player
  81. // and properly ghosts them without them being able to return to their body
  82. await server.WaitAssertion(() =>
  83. {
  84. consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
  85. Assert.Multiple(() =>
  86. {
  87. Assert.That(mobStateSystem.IsDead(player, mobStateComp));
  88. Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
  89. !ghostComp.CanReturnToBody);
  90. });
  91. });
  92. await pair.CleanReturnAsync();
  93. }
  94. /// <summary>
  95. /// Run the suicide command while the player is already injured
  96. /// This should only deal as much damage as necessary to get to the dead threshold
  97. /// </summary>
  98. [Test]
  99. public async Task TestSuicideWhileDamaged()
  100. {
  101. await using var pair = await PoolManager.GetServerClient(new PoolSettings
  102. {
  103. Connected = true,
  104. Dirty = true,
  105. DummyTicker = false
  106. });
  107. var server = pair.Server;
  108. var consoleHost = server.ResolveDependency<IConsoleHost>();
  109. var entManager = server.ResolveDependency<IEntityManager>();
  110. var playerMan = server.ResolveDependency<IPlayerManager>();
  111. var protoMan = server.ResolveDependency<IPrototypeManager>();
  112. var damageableSystem = entManager.System<DamageableSystem>();
  113. var mindSystem = entManager.System<SharedMindSystem>();
  114. var mobStateSystem = entManager.System<MobStateSystem>();
  115. // We need to know the player and whether they can be hurt, killed, and whether they have a mind
  116. var player = playerMan.Sessions.First().AttachedEntity!.Value;
  117. var mind = mindSystem.GetMind(player);
  118. MindComponent mindComponent = default;
  119. MobStateComponent mobStateComp = default;
  120. MobThresholdsComponent mobThresholdsComp = default;
  121. DamageableComponent damageableComp = default;
  122. await server.WaitPost(() =>
  123. {
  124. if (mind != null)
  125. mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
  126. mobStateComp = entManager.GetComponent<MobStateComponent>(player);
  127. mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
  128. damageableComp = entManager.GetComponent<DamageableComponent>(player);
  129. if (protoMan.TryIndex<DamageTypePrototype>("Slash", out var slashProto))
  130. damageableSystem.TryChangeDamage(player, new DamageSpecifier(slashProto, FixedPoint2.New(46.5)));
  131. });
  132. // Check that running the suicide command kills the player
  133. // and properly ghosts them without them being able to return to their body
  134. // and that all the damage is concentrated in the Slash category
  135. await server.WaitAssertion(() =>
  136. {
  137. consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
  138. var lethalDamageThreshold = mobThresholdsComp.Thresholds.Keys.Last();
  139. Assert.Multiple(() =>
  140. {
  141. Assert.That(mobStateSystem.IsDead(player, mobStateComp));
  142. Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
  143. !ghostComp.CanReturnToBody);
  144. Assert.That(damageableComp.Damage.GetTotal(), Is.EqualTo(lethalDamageThreshold));
  145. });
  146. });
  147. await pair.CleanReturnAsync();
  148. }
  149. /// <summary>
  150. /// Run the suicide command in the console
  151. /// Should only ghost the player but not kill them
  152. /// </summary>
  153. [Test]
  154. public async Task TestSuicideWhenCannotSuicide()
  155. {
  156. await using var pair = await PoolManager.GetServerClient(new PoolSettings
  157. {
  158. Connected = true,
  159. Dirty = true,
  160. DummyTicker = false
  161. });
  162. var server = pair.Server;
  163. var consoleHost = server.ResolveDependency<IConsoleHost>();
  164. var entManager = server.ResolveDependency<IEntityManager>();
  165. var playerMan = server.ResolveDependency<IPlayerManager>();
  166. var mindSystem = entManager.System<SharedMindSystem>();
  167. var mobStateSystem = entManager.System<MobStateSystem>();
  168. var tagSystem = entManager.System<TagSystem>();
  169. // We need to know the player and whether they can be hurt, killed, and whether they have a mind
  170. var player = playerMan.Sessions.First().AttachedEntity!.Value;
  171. var mind = mindSystem.GetMind(player);
  172. MindComponent mindComponent = default;
  173. MobStateComponent mobStateComp = default;
  174. await server.WaitPost(() =>
  175. {
  176. if (mind != null)
  177. mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
  178. mobStateComp = entManager.GetComponent<MobStateComponent>(player);
  179. });
  180. tagSystem.AddTag(player, "CannotSuicide");
  181. // Check that running the suicide command kills the player
  182. // and properly ghosts them without them being able to return to their body
  183. await server.WaitAssertion(() =>
  184. {
  185. consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
  186. Assert.Multiple(() =>
  187. {
  188. Assert.That(mobStateSystem.IsAlive(player, mobStateComp));
  189. Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
  190. !ghostComp.CanReturnToBody);
  191. });
  192. });
  193. await pair.CleanReturnAsync();
  194. }
  195. /// <summary>
  196. /// Run the suicide command while the player is holding an execution-capable weapon
  197. /// </summary>
  198. [Test]
  199. public async Task TestSuicideByHeldItem()
  200. {
  201. await using var pair = await PoolManager.GetServerClient(new PoolSettings
  202. {
  203. Connected = true,
  204. Dirty = true,
  205. DummyTicker = false
  206. });
  207. var server = pair.Server;
  208. var consoleHost = server.ResolveDependency<IConsoleHost>();
  209. var entManager = server.ResolveDependency<IEntityManager>();
  210. var playerMan = server.ResolveDependency<IPlayerManager>();
  211. var handsSystem = entManager.System<SharedHandsSystem>();
  212. var mindSystem = entManager.System<SharedMindSystem>();
  213. var mobStateSystem = entManager.System<MobStateSystem>();
  214. var transformSystem = entManager.System<TransformSystem>();
  215. var damageableSystem = entManager.System<DamageableSystem>();
  216. // We need to know the player and whether they can be hurt, killed, and whether they have a mind
  217. var player = playerMan.Sessions.First().AttachedEntity!.Value;
  218. var mind = mindSystem.GetMind(player);
  219. MindComponent mindComponent = default;
  220. MobStateComponent mobStateComp = default;
  221. MobThresholdsComponent mobThresholdsComp = default;
  222. DamageableComponent damageableComp = default;
  223. HandsComponent handsComponent = default;
  224. await server.WaitPost(() =>
  225. {
  226. if (mind != null)
  227. mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
  228. mobStateComp = entManager.GetComponent<MobStateComponent>(player);
  229. mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
  230. damageableComp = entManager.GetComponent<DamageableComponent>(player);
  231. handsComponent = entManager.GetComponent<HandsComponent>(player);
  232. });
  233. // Spawn the weapon of choice and put it in the player's hands
  234. await server.WaitPost(() =>
  235. {
  236. var item = entManager.SpawnEntity("SharpTestObject", transformSystem.GetMapCoordinates(player));
  237. Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
  238. entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
  239. Assert.That(executionComponent, Is.Not.EqualTo(null));
  240. });
  241. // Check that running the suicide command kills the player
  242. // and properly ghosts them without them being able to return to their body
  243. // and that all the damage is concentrated in the Slash category
  244. await server.WaitAssertion(() =>
  245. {
  246. // Heal all damage first (possible low pressure damage taken)
  247. damageableSystem.SetAllDamage(player, damageableComp, 0);
  248. consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
  249. var lethalDamageThreshold = mobThresholdsComp.Thresholds.Keys.Last();
  250. Assert.Multiple(() =>
  251. {
  252. Assert.That(mobStateSystem.IsDead(player, mobStateComp));
  253. Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
  254. !ghostComp.CanReturnToBody);
  255. Assert.That(damageableComp.Damage.DamageDict["Slash"], Is.EqualTo(lethalDamageThreshold));
  256. });
  257. });
  258. await pair.CleanReturnAsync();
  259. }
  260. /// <summary>
  261. /// Run the suicide command while the player is holding an execution-capable weapon
  262. /// with damage spread between slash and blunt
  263. /// </summary>
  264. [Test]
  265. public async Task TestSuicideByHeldItemSpreadDamage()
  266. {
  267. await using var pair = await PoolManager.GetServerClient(new PoolSettings
  268. {
  269. Connected = true,
  270. Dirty = true,
  271. DummyTicker = false
  272. });
  273. var server = pair.Server;
  274. var consoleHost = server.ResolveDependency<IConsoleHost>();
  275. var entManager = server.ResolveDependency<IEntityManager>();
  276. var playerMan = server.ResolveDependency<IPlayerManager>();
  277. var handsSystem = entManager.System<SharedHandsSystem>();
  278. var mindSystem = entManager.System<SharedMindSystem>();
  279. var mobStateSystem = entManager.System<MobStateSystem>();
  280. var transformSystem = entManager.System<TransformSystem>();
  281. var damageableSystem = entManager.System<DamageableSystem>();
  282. // We need to know the player and whether they can be hurt, killed, and whether they have a mind
  283. var player = playerMan.Sessions.First().AttachedEntity!.Value;
  284. var mind = mindSystem.GetMind(player);
  285. MindComponent mindComponent = default;
  286. MobStateComponent mobStateComp = default;
  287. MobThresholdsComponent mobThresholdsComp = default;
  288. DamageableComponent damageableComp = default;
  289. HandsComponent handsComponent = default;
  290. await server.WaitPost(() =>
  291. {
  292. if (mind != null)
  293. mindComponent = entManager.GetComponent<MindComponent>(mind.Value);
  294. mobStateComp = entManager.GetComponent<MobStateComponent>(player);
  295. mobThresholdsComp = entManager.GetComponent<MobThresholdsComponent>(player);
  296. damageableComp = entManager.GetComponent<DamageableComponent>(player);
  297. handsComponent = entManager.GetComponent<HandsComponent>(player);
  298. });
  299. // Spawn the weapon of choice and put it in the player's hands
  300. await server.WaitPost(() =>
  301. {
  302. var item = entManager.SpawnEntity("MixedDamageTestObject", transformSystem.GetMapCoordinates(player));
  303. Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
  304. entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
  305. Assert.That(executionComponent, Is.Not.EqualTo(null));
  306. });
  307. // Check that running the suicide command kills the player
  308. // and properly ghosts them without them being able to return to their body
  309. // and that slash damage is split in half
  310. await server.WaitAssertion(() =>
  311. {
  312. // Heal all damage first (possible low pressure damage taken)
  313. damageableSystem.SetAllDamage(player, damageableComp, 0);
  314. consoleHost.GetSessionShell(playerMan.Sessions.First()).ExecuteCommand("suicide");
  315. var lethalDamageThreshold = mobThresholdsComp.Thresholds.Keys.Last();
  316. Assert.Multiple(() =>
  317. {
  318. Assert.That(mobStateSystem.IsDead(player, mobStateComp));
  319. Assert.That(entManager.TryGetComponent<GhostComponent>(mindComponent.CurrentEntity, out var ghostComp) &&
  320. !ghostComp.CanReturnToBody);
  321. Assert.That(damageableComp.Damage.DamageDict["Slash"], Is.EqualTo(lethalDamageThreshold / 2));
  322. });
  323. });
  324. await pair.CleanReturnAsync();
  325. }
  326. }