StationJobsTest.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Content.Server.Maps;
  4. using Content.Server.Station.Components;
  5. using Content.Server.Station.Systems;
  6. using Content.Shared.Preferences;
  7. using Content.Shared.Roles;
  8. using Robust.Shared.GameObjects;
  9. using Robust.Shared.Log;
  10. using Robust.Shared.Network;
  11. using Robust.Shared.Prototypes;
  12. using Robust.Shared.Timing;
  13. namespace Content.IntegrationTests.Tests.Station;
  14. [TestFixture]
  15. [TestOf(typeof(StationJobsSystem))]
  16. public sealed class StationJobsTest
  17. {
  18. [TestPrototypes]
  19. private const string Prototypes = @"
  20. - type: playTimeTracker
  21. id: PlayTimeDummyAssistant
  22. - type: playTimeTracker
  23. id: PlayTimeDummyMime
  24. - type: playTimeTracker
  25. id: PlayTimeDummyClown
  26. - type: playTimeTracker
  27. id: PlayTimeDummyCaptain
  28. - type: playTimeTracker
  29. id: PlayTimeDummyChaplain
  30. - type: gameMap
  31. id: FooStation
  32. minPlayers: 0
  33. mapName: FooStation
  34. mapPath: /Maps/civ/nomads_classic.yml
  35. stations:
  36. Station:
  37. mapNameTemplate: FooStation
  38. stationProto: StandardStationArena
  39. components:
  40. - type: StationJobs
  41. availableJobs:
  42. TMime: [0, -1]
  43. TAssistant: [-1, -1]
  44. TCaptain: [5, 5]
  45. TClown: [5, 6]
  46. - type: job
  47. id: TAssistant
  48. playTimeTracker: PlayTimeDummyAssistant
  49. - type: job
  50. id: TMime
  51. weight: 20
  52. playTimeTracker: PlayTimeDummyMime
  53. - type: job
  54. id: TClown
  55. weight: -10
  56. playTimeTracker: PlayTimeDummyClown
  57. - type: job
  58. id: TCaptain
  59. weight: 10
  60. playTimeTracker: PlayTimeDummyCaptain
  61. - type: job
  62. id: TChaplain
  63. playTimeTracker: PlayTimeDummyChaplain
  64. ";
  65. private const int StationCount = 100;
  66. private const int CaptainCount = StationCount;
  67. private const int PlayerCount = 2000;
  68. private const int TotalPlayers = PlayerCount + CaptainCount;
  69. [Test]
  70. public async Task AssignJobsTest()
  71. {
  72. await using var pair = await PoolManager.GetServerClient();
  73. var server = pair.Server;
  74. var prototypeManager = server.ResolveDependency<IPrototypeManager>();
  75. var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
  76. var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
  77. var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
  78. var stationSystem = entSysMan.GetEntitySystem<StationSystem>();
  79. var logmill = server.ResolveDependency<ILogManager>().RootSawmill;
  80. List<EntityUid> stations = new();
  81. await server.WaitPost(() =>
  82. {
  83. for (var i = 0; i < StationCount; i++)
  84. {
  85. stations.Add(stationSystem.InitializeNewStation(fooStationProto.Stations["Station"], null, $"Foo {StationCount}"));
  86. }
  87. });
  88. await server.WaitAssertion(() =>
  89. {
  90. var fakePlayers = new Dictionary<NetUserId, HumanoidCharacterProfile>()
  91. .AddJob("TAssistant", JobPriority.Medium, PlayerCount)
  92. .AddPreference("TClown", JobPriority.Low)
  93. .AddPreference("TMime", JobPriority.High)
  94. .WithPlayers(
  95. new Dictionary<NetUserId, HumanoidCharacterProfile>()
  96. .AddJob("TCaptain", JobPriority.High, CaptainCount)
  97. );
  98. Assert.That(fakePlayers, Is.Not.Empty);
  99. var start = new Stopwatch();
  100. start.Start();
  101. var assigned = stationJobs.AssignJobs(fakePlayers, stations);
  102. Assert.That(assigned, Is.Not.Empty);
  103. var time = start.Elapsed.TotalMilliseconds;
  104. logmill.Info($"Took {time} ms to distribute {TotalPlayers} players.");
  105. Assert.Multiple(() =>
  106. {
  107. foreach (var station in stations)
  108. {
  109. var assignedHere = assigned
  110. .Where(x => x.Value.Item2 == station)
  111. .ToDictionary(x => x.Key, x => x.Value);
  112. // Each station should have SOME players.
  113. Assert.That(assignedHere, Is.Not.Empty);
  114. // And it should have at least the minimum players to be considered a "fair" share, as they're all the same.
  115. Assert.That(assignedHere, Has.Count.GreaterThanOrEqualTo(TotalPlayers / stations.Count), "Station has too few players.");
  116. // And it shouldn't have ALL the players, either.
  117. Assert.That(assignedHere, Has.Count.LessThan(TotalPlayers), "Station has too many players.");
  118. // And there should be *A* captain, as there's one player with captain enabled per station.
  119. Assert.That(assignedHere.Where(x => x.Value.Item1 == "TCaptain").ToList(), Has.Count.EqualTo(1));
  120. }
  121. // All clown players have assistant as a higher priority.
  122. Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Not.Contain("TClown"));
  123. // Mime isn't an open job-slot at round-start.
  124. Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Not.Contain("TMime"));
  125. // All players have slots they can fill.
  126. Assert.That(assigned.Values, Has.Count.EqualTo(TotalPlayers), $"Expected {TotalPlayers} players.");
  127. // There must be assistants present.
  128. Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Contain("TAssistant"));
  129. // There must be captains present, too.
  130. Assert.That(assigned.Values.Select(x => x.Item1).ToList(), Does.Contain("TCaptain"));
  131. });
  132. });
  133. await pair.CleanReturnAsync();
  134. }
  135. [Test]
  136. public async Task AdjustJobsTest()
  137. {
  138. await using var pair = await PoolManager.GetServerClient();
  139. var server = pair.Server;
  140. var prototypeManager = server.ResolveDependency<IPrototypeManager>();
  141. var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
  142. var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
  143. var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
  144. var stationSystem = entSysMan.GetEntitySystem<StationSystem>();
  145. var station = EntityUid.Invalid;
  146. await server.WaitPost(() =>
  147. {
  148. station = stationSystem.InitializeNewStation(fooStationProto.Stations["Station"], null, $"Foo Station");
  149. });
  150. await server.WaitRunTicks(1);
  151. await server.WaitAssertion(() =>
  152. {
  153. // Verify jobs are/are not unlimited.
  154. Assert.Multiple(() =>
  155. {
  156. Assert.That(stationJobs.IsJobUnlimited(station, "TAssistant"), "TAssistant is expected to be unlimited.");
  157. Assert.That(stationJobs.IsJobUnlimited(station, "TMime"), "TMime is expected to be unlimited.");
  158. Assert.That(!stationJobs.IsJobUnlimited(station, "TCaptain"), "TCaptain is expected to not be unlimited.");
  159. Assert.That(!stationJobs.IsJobUnlimited(station, "TClown"), "TClown is expected to not be unlimited.");
  160. });
  161. Assert.Multiple(() =>
  162. {
  163. Assert.That(stationJobs.TrySetJobSlot(station, "TClown", 0), "Could not set TClown to have zero slots.");
  164. Assert.That(stationJobs.TryGetJobSlot(station, "TClown", out var clownSlots), "Could not get the number of TClown slots.");
  165. Assert.That(clownSlots, Is.EqualTo(0));
  166. Assert.That(!stationJobs.TryAdjustJobSlot(station, "TCaptain", -9999), "Was able to adjust TCaptain by -9999 without clamping.");
  167. Assert.That(stationJobs.TryAdjustJobSlot(station, "TCaptain", -9999, false, true), "Could not adjust TCaptain by -9999.");
  168. Assert.That(stationJobs.TryGetJobSlot(station, "TCaptain", out var captainSlots), "Could not get the number of TCaptain slots.");
  169. Assert.That(captainSlots, Is.EqualTo(0));
  170. });
  171. Assert.Multiple(() =>
  172. {
  173. Assert.That(stationJobs.TrySetJobSlot(station, "TChaplain", 10, true), "Could not create 10 TChaplain slots.");
  174. stationJobs.MakeJobUnlimited(station, "TChaplain");
  175. Assert.That(stationJobs.IsJobUnlimited(station, "TChaplain"), "Could not make TChaplain unlimited.");
  176. });
  177. });
  178. await pair.CleanReturnAsync();
  179. }
  180. [Test]
  181. public async Task InvalidRoundstartJobsTest()
  182. {
  183. await using var pair = await PoolManager.GetServerClient();
  184. var server = pair.Server;
  185. var prototypeManager = server.ResolveDependency<IPrototypeManager>();
  186. var compFact = server.ResolveDependency<IComponentFactory>();
  187. var name = compFact.GetComponentName<StationJobsComponent>();
  188. await server.WaitAssertion(() =>
  189. {
  190. // invalidJobs contains all the jobs which can't be set for preference:
  191. // i.e. all the jobs that shouldn't be available round-start.
  192. var invalidJobs = new HashSet<string>();
  193. foreach (var job in prototypeManager.EnumeratePrototypes<JobPrototype>())
  194. {
  195. if (!job.SetPreference)
  196. invalidJobs.Add(job.ID);
  197. }
  198. Assert.Multiple(() =>
  199. {
  200. foreach (var gameMap in prototypeManager.EnumeratePrototypes<GameMapPrototype>())
  201. {
  202. foreach (var (stationId, station) in gameMap.Stations)
  203. {
  204. if (!station.StationComponentOverrides.TryGetComponent(name, out var comp))
  205. continue;
  206. foreach (var (job, array) in ((StationJobsComponent) comp).SetupAvailableJobs)
  207. {
  208. Assert.That(array.Length, Is.EqualTo(2));
  209. Assert.That(array[0] is -1 or >= 0);
  210. Assert.That(array[1] is -1 or >= 0);
  211. Assert.That(invalidJobs, Does.Not.Contain(job), $"Station {stationId} contains job prototype {job} which cannot be present roundstart.");
  212. }
  213. }
  214. }
  215. });
  216. });
  217. await pair.CleanReturnAsync();
  218. }
  219. }
  220. internal static class JobExtensions
  221. {
  222. public static Dictionary<NetUserId, HumanoidCharacterProfile> AddJob(
  223. this Dictionary<NetUserId, HumanoidCharacterProfile> inp, string jobId, JobPriority prio = JobPriority.Medium,
  224. int amount = 1)
  225. {
  226. for (var i = 0; i < amount; i++)
  227. {
  228. inp.Add(new NetUserId(Guid.NewGuid()), HumanoidCharacterProfile.Random().WithJobPriority(jobId, prio));
  229. }
  230. return inp;
  231. }
  232. public static Dictionary<NetUserId, HumanoidCharacterProfile> AddPreference(
  233. this Dictionary<NetUserId, HumanoidCharacterProfile> inp, string jobId, JobPriority prio = JobPriority.Medium)
  234. {
  235. return inp.ToDictionary(x => x.Key, x => x.Value.WithJobPriority(jobId, prio));
  236. }
  237. public static Dictionary<NetUserId, HumanoidCharacterProfile> WithPlayers(
  238. this Dictionary<NetUserId, HumanoidCharacterProfile> inp,
  239. Dictionary<NetUserId, HumanoidCharacterProfile> second)
  240. {
  241. return new[] { inp, second }.SelectMany(x => x).ToDictionary(x => x.Key, x => x.Value);
  242. }
  243. }