InteractionTest.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. #nullable enable
  2. using System.Linq;
  3. using System.Numerics;
  4. using Content.Client.Construction;
  5. using Content.Client.Examine;
  6. using Content.Client.Gameplay;
  7. using Content.IntegrationTests.Pair;
  8. using Content.Server.Body.Systems;
  9. using Content.Server.Hands.Systems;
  10. using Content.Server.Stack;
  11. using Content.Server.Tools;
  12. using Content.Shared.Body.Part;
  13. using Content.Shared.DoAfter;
  14. using Content.Shared.Hands.Components;
  15. using Content.Shared.Interaction;
  16. using Content.Shared.Mind;
  17. using Content.Shared.Players;
  18. using Robust.Client.Input;
  19. using Robust.Client.UserInterface;
  20. using Robust.Shared.GameObjects;
  21. using Robust.Shared.Log;
  22. using Robust.Shared.Map;
  23. using Robust.Shared.Player;
  24. using Robust.Shared.Prototypes;
  25. using Robust.Shared.Timing;
  26. using Robust.UnitTesting;
  27. using Content.Shared.Item.ItemToggle;
  28. using Robust.Client.State;
  29. namespace Content.IntegrationTests.Tests.Interaction;
  30. /// <summary>
  31. /// This is a base class designed to make it easier to test various interactions like construction & DoAfters.
  32. ///
  33. /// For construction tests, the interactions are intentionally hard-coded and not pulled automatically from the
  34. /// construction graph, even though this may be a pain to maintain. This is because otherwise these tests could not
  35. /// detect errors in the graph pathfinding (e.g., infinite loops, missing steps, etc).
  36. /// </summary>
  37. [TestFixture]
  38. [FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
  39. public abstract partial class InteractionTest
  40. {
  41. protected virtual string PlayerPrototype => "InteractionTestMob";
  42. protected TestPair Pair = default!;
  43. protected TestMapData MapData => Pair.TestMap!;
  44. protected RobustIntegrationTest.ServerIntegrationInstance Server => Pair.Server;
  45. protected RobustIntegrationTest.ClientIntegrationInstance Client => Pair.Client;
  46. protected MapId MapId => MapData.MapId;
  47. /// <summary>
  48. /// Target coordinates. Note that this does not necessarily correspond to the position of the <see cref="Target"/>
  49. /// entity.
  50. /// </summary>
  51. protected NetCoordinates TargetCoords;
  52. /// <summary>
  53. /// Initial player coordinates. Note that this does not necessarily correspond to the position of the
  54. /// <see cref="Player"/> entity.
  55. /// </summary>
  56. protected NetCoordinates PlayerCoords;
  57. /// <summary>
  58. /// The player entity that performs all these interactions. Defaults to an admin-observer with 1 hand.
  59. /// </summary>
  60. protected NetEntity Player;
  61. protected EntityUid SPlayer;
  62. protected EntityUid CPlayer;
  63. protected ICommonSession ClientSession = default!;
  64. protected ICommonSession ServerSession = default!;
  65. /// <summary>
  66. /// The current target entity. This is the default entity for various helper functions.
  67. /// </summary>
  68. /// <remarks>
  69. /// Note that this target may be automatically modified by various interactions, in particular construction
  70. /// interactions often swap out entities, and there are helper methods that attempt to automatically upddate
  71. /// the target entity. See <see cref="CheckTargetChange"/>
  72. /// </remarks>
  73. protected NetEntity? Target;
  74. protected EntityUid? STarget => ToServer(Target);
  75. protected EntityUid? CTarget => ToClient(Target);
  76. /// <summary>
  77. /// When attempting to start construction, this is the client-side ID of the construction ghost.
  78. /// </summary>
  79. protected int ConstructionGhostId;
  80. // SERVER dependencies
  81. protected IEntityManager SEntMan = default!;
  82. protected ITileDefinitionManager TileMan = default!;
  83. protected IMapManager MapMan = default!;
  84. protected IPrototypeManager ProtoMan = default!;
  85. protected IGameTiming STiming = default!;
  86. protected IComponentFactory Factory = default!;
  87. protected HandsSystem HandSys = default!;
  88. protected StackSystem Stack = default!;
  89. protected SharedInteractionSystem InteractSys = default!;
  90. protected Content.Server.Construction.ConstructionSystem SConstruction = default!;
  91. protected SharedDoAfterSystem DoAfterSys = default!;
  92. protected ToolSystem ToolSys = default!;
  93. protected ItemToggleSystem ItemToggleSys = default!;
  94. protected InteractionTestSystem STestSystem = default!;
  95. protected SharedTransformSystem Transform = default!;
  96. protected SharedMapSystem MapSystem = default!;
  97. protected ISawmill SLogger = default!;
  98. protected SharedUserInterfaceSystem SUiSys = default!;
  99. // CLIENT dependencies
  100. protected IEntityManager CEntMan = default!;
  101. protected IGameTiming CTiming = default!;
  102. protected IUserInterfaceManager UiMan = default!;
  103. protected IInputManager InputManager = default!;
  104. protected Robust.Client.GameObjects.InputSystem InputSystem = default!;
  105. protected ConstructionSystem CConSys = default!;
  106. protected ExamineSystem ExamineSys = default!;
  107. protected InteractionTestSystem CTestSystem = default!;
  108. protected ISawmill CLogger = default!;
  109. protected SharedUserInterfaceSystem CUiSys = default!;
  110. // player components
  111. protected HandsComponent Hands = default!;
  112. protected DoAfterComponent DoAfters = default!;
  113. public float TickPeriod => (float) STiming.TickPeriod.TotalSeconds;
  114. // Simple mob that has one hand and can perform misc interactions.
  115. [TestPrototypes]
  116. private const string TestPrototypes = @"
  117. - type: entity
  118. id: InteractionTestMob
  119. components:
  120. - type: Body
  121. prototype: Aghost
  122. - type: DoAfter
  123. - type: Hands
  124. - type: ComplexInteraction
  125. - type: MindContainer
  126. - type: Stripping
  127. - type: Puller
  128. - type: Physics
  129. - type: Tag
  130. tags:
  131. - CanPilot
  132. - type: UserInterface
  133. ";
  134. [SetUp]
  135. public virtual async Task Setup()
  136. {
  137. Pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, Dirty = true });
  138. // server dependencies
  139. SEntMan = Server.ResolveDependency<IEntityManager>();
  140. TileMan = Server.ResolveDependency<ITileDefinitionManager>();
  141. MapMan = Server.ResolveDependency<IMapManager>();
  142. ProtoMan = Server.ResolveDependency<IPrototypeManager>();
  143. Factory = Server.ResolveDependency<IComponentFactory>();
  144. STiming = Server.ResolveDependency<IGameTiming>();
  145. HandSys = SEntMan.System<HandsSystem>();
  146. InteractSys = SEntMan.System<SharedInteractionSystem>();
  147. ToolSys = SEntMan.System<ToolSystem>();
  148. ItemToggleSys = SEntMan.System<ItemToggleSystem>();
  149. DoAfterSys = SEntMan.System<SharedDoAfterSystem>();
  150. Transform = SEntMan.System<SharedTransformSystem>();
  151. MapSystem = SEntMan.System<SharedMapSystem>();
  152. SConstruction = SEntMan.System<Server.Construction.ConstructionSystem>();
  153. STestSystem = SEntMan.System<InteractionTestSystem>();
  154. Stack = SEntMan.System<StackSystem>();
  155. SLogger = Server.ResolveDependency<ILogManager>().RootSawmill;
  156. SUiSys = Client.System<SharedUserInterfaceSystem>();
  157. // client dependencies
  158. CEntMan = Client.ResolveDependency<IEntityManager>();
  159. UiMan = Client.ResolveDependency<IUserInterfaceManager>();
  160. CTiming = Client.ResolveDependency<IGameTiming>();
  161. InputManager = Client.ResolveDependency<IInputManager>();
  162. InputSystem = CEntMan.System<Robust.Client.GameObjects.InputSystem>();
  163. CTestSystem = CEntMan.System<InteractionTestSystem>();
  164. CConSys = CEntMan.System<ConstructionSystem>();
  165. ExamineSys = CEntMan.System<ExamineSystem>();
  166. CLogger = Client.ResolveDependency<ILogManager>().RootSawmill;
  167. CUiSys = Client.System<SharedUserInterfaceSystem>();
  168. // Setup map.
  169. await Pair.CreateTestMap();
  170. PlayerCoords = SEntMan.GetNetCoordinates(Transform.WithEntityId(MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)), MapData.MapUid));
  171. TargetCoords = SEntMan.GetNetCoordinates(Transform.WithEntityId(MapData.GridCoords.Offset(new Vector2(1.5f, 0.5f)), MapData.MapUid));
  172. await SetTile(Plating, grid: MapData.Grid);
  173. // Get player data
  174. var sPlayerMan = Server.ResolveDependency<Robust.Server.Player.IPlayerManager>();
  175. var cPlayerMan = Client.ResolveDependency<Robust.Client.Player.IPlayerManager>();
  176. if (Client.Session == null)
  177. Assert.Fail("No player");
  178. ClientSession = Client.Session!;
  179. ServerSession = sPlayerMan.GetSessionById(ClientSession.UserId);
  180. // Spawn player entity & attach
  181. EntityUid? old = default;
  182. await Server.WaitPost(() =>
  183. {
  184. // Fuck you mind system I want an hour of my life back
  185. // Mind system is a time vampire
  186. SEntMan.System<SharedMindSystem>().WipeMind(ServerSession.ContentData()?.Mind);
  187. old = cPlayerMan.LocalEntity;
  188. SPlayer = SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords));
  189. Player = SEntMan.GetNetEntity(SPlayer);
  190. Server.PlayerMan.SetAttachedEntity(ServerSession, SPlayer);
  191. Hands = SEntMan.GetComponent<HandsComponent>(SPlayer);
  192. DoAfters = SEntMan.GetComponent<DoAfterComponent>(SPlayer);
  193. });
  194. // Check player got attached.
  195. await RunTicks(5);
  196. CPlayer = ToClient(Player);
  197. Assert.That(cPlayerMan.LocalEntity, Is.EqualTo(CPlayer));
  198. // Delete old player entity.
  199. await Server.WaitPost(() =>
  200. {
  201. if (old != null)
  202. SEntMan.DeleteEntity(old.Value);
  203. });
  204. // Ensure that the player only has one hand, so that they do not accidentally pick up deconstruction products
  205. await Server.WaitPost(() =>
  206. {
  207. // I lost an hour of my life trying to track down how the hell interaction tests were breaking
  208. // so greatz to this. Just make your own body prototype!
  209. var bodySystem = SEntMan.System<BodySystem>();
  210. var hands = bodySystem.GetBodyChildrenOfType(SEntMan.GetEntity(Player), BodyPartType.Hand).ToArray();
  211. for (var i = 1; i < hands.Length; i++)
  212. {
  213. SEntMan.DeleteEntity(hands[i].Id);
  214. }
  215. });
  216. // Change UI state to in-game.
  217. var state = Client.ResolveDependency<IStateManager>();
  218. await Client.WaitPost(() => state.RequestStateChange<GameplayState>());
  219. // Final player asserts/checks.
  220. await Pair.ReallyBeIdle(5);
  221. Assert.Multiple(() =>
  222. {
  223. Assert.That(CEntMan.GetNetEntity(cPlayerMan.LocalEntity), Is.EqualTo(Player));
  224. Assert.That(sPlayerMan.GetSessionById(ClientSession.UserId).AttachedEntity, Is.EqualTo(SEntMan.GetEntity(Player)));
  225. });
  226. }
  227. [TearDown]
  228. public async Task TearDownInternal()
  229. {
  230. await Server.WaitPost(() => MapSystem.DeleteMap(MapId));
  231. await Pair.CleanReturnAsync();
  232. await TearDown();
  233. }
  234. protected virtual Task TearDown()
  235. {
  236. return Task.CompletedTask;
  237. }
  238. }