1
0

PowerTest.cs 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339
  1. #nullable enable
  2. using Content.Server.NodeContainer;
  3. using Content.Server.NodeContainer.EntitySystems;
  4. using Content.Server.NodeContainer.Nodes;
  5. using Content.Server.Power.Components;
  6. using Content.Server.Power.EntitySystems;
  7. using Content.Server.Power.Nodes;
  8. using Content.Shared.Coordinates;
  9. using Robust.Shared.GameObjects;
  10. using Robust.Shared.Map;
  11. using Robust.Shared.Maths;
  12. using Robust.Shared.Timing;
  13. namespace Content.IntegrationTests.Tests.Power
  14. {
  15. [TestFixture]
  16. public sealed class PowerTest
  17. {
  18. [TestPrototypes]
  19. private const string Prototypes = @"
  20. - type: entity
  21. id: GeneratorDummy
  22. components:
  23. - type: NodeContainer
  24. nodes:
  25. output:
  26. !type:CableDeviceNode
  27. nodeGroupID: HVPower
  28. - type: PowerSupplier
  29. - type: Transform
  30. anchored: true
  31. - type: entity
  32. id: ConsumerDummy
  33. components:
  34. - type: Transform
  35. anchored: true
  36. - type: NodeContainer
  37. nodes:
  38. input:
  39. !type:CableDeviceNode
  40. nodeGroupID: HVPower
  41. - type: PowerConsumer
  42. - type: entity
  43. id: ChargingBatteryDummy
  44. components:
  45. - type: Transform
  46. anchored: true
  47. - type: NodeContainer
  48. nodes:
  49. output:
  50. !type:CableDeviceNode
  51. nodeGroupID: HVPower
  52. - type: PowerNetworkBattery
  53. - type: Battery
  54. - type: BatteryCharger
  55. - type: entity
  56. id: DischargingBatteryDummy
  57. components:
  58. - type: Transform
  59. anchored: true
  60. - type: NodeContainer
  61. nodes:
  62. output:
  63. !type:CableDeviceNode
  64. nodeGroupID: HVPower
  65. - type: PowerNetworkBattery
  66. - type: Battery
  67. - type: BatteryDischarger
  68. - type: entity
  69. id: FullBatteryDummy
  70. components:
  71. - type: Transform
  72. anchored: true
  73. - type: NodeContainer
  74. nodes:
  75. output:
  76. !type:CableDeviceNode
  77. nodeGroupID: HVPower
  78. input:
  79. !type:CableTerminalPortNode
  80. nodeGroupID: HVPower
  81. - type: PowerNetworkBattery
  82. - type: Battery
  83. - type: BatteryDischarger
  84. node: output
  85. - type: BatteryCharger
  86. node: input
  87. - type: entity
  88. id: SubstationDummy
  89. components:
  90. - type: NodeContainer
  91. nodes:
  92. input:
  93. !type:CableDeviceNode
  94. nodeGroupID: HVPower
  95. output:
  96. !type:CableDeviceNode
  97. nodeGroupID: MVPower
  98. - type: BatteryCharger
  99. voltage: High
  100. - type: BatteryDischarger
  101. voltage: Medium
  102. - type: PowerNetworkBattery
  103. maxChargeRate: 1000
  104. maxSupply: 1000
  105. supplyRampTolerance: 1000
  106. - type: Battery
  107. maxCharge: 1000
  108. startingCharge: 1000
  109. - type: Transform
  110. anchored: true
  111. - type: entity
  112. id: ApcDummy
  113. components:
  114. - type: Battery
  115. maxCharge: 10000
  116. startingCharge: 10000
  117. - type: PowerNetworkBattery
  118. maxChargeRate: 1000
  119. maxSupply: 1000
  120. supplyRampTolerance: 1000
  121. - type: BatteryCharger
  122. voltage: Medium
  123. - type: BatteryDischarger
  124. voltage: Apc
  125. - type: Apc
  126. voltage: Apc
  127. - type: NodeContainer
  128. nodes:
  129. input:
  130. !type:CableDeviceNode
  131. nodeGroupID: MVPower
  132. output:
  133. !type:CableDeviceNode
  134. nodeGroupID: Apc
  135. - type: Transform
  136. anchored: true
  137. - type: UserInterface
  138. interfaces:
  139. enum.ApcUiKey.Key:
  140. type: ApcBoundUserInterface
  141. - type: AccessReader
  142. access: [['Engineering']]
  143. - type: entity
  144. id: ApcPowerReceiverDummy
  145. components:
  146. - type: ApcPowerReceiver
  147. - type: ExtensionCableReceiver
  148. - type: Transform
  149. anchored: true
  150. ";
  151. /// <summary>
  152. /// Test small power net with a simple surplus of power over the loads.
  153. /// </summary>
  154. [Test]
  155. public async Task TestSimpleSurplus()
  156. {
  157. await using var pair = await PoolManager.GetServerClient();
  158. var server = pair.Server;
  159. var mapManager = server.ResolveDependency<IMapManager>();
  160. var entityManager = server.ResolveDependency<IEntityManager>();
  161. var mapSys = entityManager.System<SharedMapSystem>();
  162. const float loadPower = 200;
  163. PowerSupplierComponent supplier = default!;
  164. PowerConsumerComponent consumer1 = default!;
  165. PowerConsumerComponent consumer2 = default!;
  166. await server.WaitAssertion(() =>
  167. {
  168. var map = mapSys.CreateMap(out var mapId);
  169. var grid = mapManager.CreateGridEntity(mapId);
  170. // Power only works when anchored
  171. for (var i = 0; i < 3; i++)
  172. {
  173. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  174. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  175. }
  176. var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates());
  177. var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 1));
  178. var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2));
  179. supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
  180. consumer1 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt1);
  181. consumer2 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt2);
  182. // Plenty of surplus and tolerance
  183. supplier.MaxSupply = loadPower * 4;
  184. supplier.SupplyRampTolerance = loadPower * 4;
  185. consumer1.DrawRate = loadPower;
  186. consumer2.DrawRate = loadPower;
  187. });
  188. server.RunTicks(1); //let run a tick for PowerNet to process power
  189. await server.WaitAssertion(() =>
  190. {
  191. Assert.Multiple(() =>
  192. {
  193. // Assert both consumers fully powered
  194. Assert.That(consumer1.ReceivedPower, Is.EqualTo(consumer1.DrawRate).Within(0.1));
  195. Assert.That(consumer2.ReceivedPower, Is.EqualTo(consumer2.DrawRate).Within(0.1));
  196. // Assert that load adds up on supply.
  197. Assert.That(supplier.CurrentSupply, Is.EqualTo(loadPower * 2).Within(0.1));
  198. });
  199. });
  200. await pair.CleanReturnAsync();
  201. }
  202. /// <summary>
  203. /// Test small power net with a simple deficit of power over the loads.
  204. /// </summary>
  205. [Test]
  206. public async Task TestSimpleDeficit()
  207. {
  208. await using var pair = await PoolManager.GetServerClient();
  209. var server = pair.Server;
  210. var mapManager = server.ResolveDependency<IMapManager>();
  211. var entityManager = server.ResolveDependency<IEntityManager>();
  212. var mapSys = entityManager.System<SharedMapSystem>();
  213. const float loadPower = 200;
  214. PowerSupplierComponent supplier = default!;
  215. PowerConsumerComponent consumer1 = default!;
  216. PowerConsumerComponent consumer2 = default!;
  217. await server.WaitAssertion(() =>
  218. {
  219. var map = mapSys.CreateMap(out var mapId);
  220. var grid = mapManager.CreateGridEntity(mapId);
  221. // Power only works when anchored
  222. for (var i = 0; i < 3; i++)
  223. {
  224. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  225. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  226. }
  227. var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates());
  228. var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 1));
  229. var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2));
  230. supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
  231. consumer1 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt1);
  232. consumer2 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt2);
  233. // Too little supply, both consumers should get 33% power.
  234. supplier.MaxSupply = loadPower;
  235. supplier.SupplyRampTolerance = loadPower;
  236. consumer1.DrawRate = loadPower;
  237. consumer2.DrawRate = loadPower * 2;
  238. });
  239. server.RunTicks(1); //let run a tick for PowerNet to process power
  240. await server.WaitAssertion(() =>
  241. {
  242. Assert.Multiple(() =>
  243. {
  244. // Assert both consumers get 33% power.
  245. Assert.That(consumer1.ReceivedPower, Is.EqualTo(consumer1.DrawRate / 3).Within(0.1));
  246. Assert.That(consumer2.ReceivedPower, Is.EqualTo(consumer2.DrawRate / 3).Within(0.1));
  247. // Supply should be maxed out
  248. Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.1));
  249. });
  250. });
  251. await pair.CleanReturnAsync();
  252. }
  253. [Test]
  254. public async Task TestSupplyRamp()
  255. {
  256. await using var pair = await PoolManager.GetServerClient();
  257. var server = pair.Server;
  258. var mapManager = server.ResolveDependency<IMapManager>();
  259. var entityManager = server.ResolveDependency<IEntityManager>();
  260. var mapSys = entityManager.System<SharedMapSystem>();
  261. var gameTiming = server.ResolveDependency<IGameTiming>();
  262. PowerSupplierComponent supplier = default!;
  263. PowerConsumerComponent consumer = default!;
  264. await server.WaitAssertion(() =>
  265. {
  266. var map = mapSys.CreateMap(out var mapId);
  267. var grid = mapManager.CreateGridEntity(mapId);
  268. // Power only works when anchored
  269. for (var i = 0; i < 3; i++)
  270. {
  271. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  272. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  273. }
  274. var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates());
  275. var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2));
  276. supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
  277. consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
  278. // Supply has enough total power but needs to ramp up to match.
  279. supplier.MaxSupply = 400;
  280. supplier.SupplyRampRate = 400;
  281. supplier.SupplyRampTolerance = 100;
  282. consumer.DrawRate = 400;
  283. });
  284. // Exact values can/will be off by a tick, add tolerance for that.
  285. var tickPeriod = (float) gameTiming.TickPeriod.TotalSeconds;
  286. var tickDev = 400 * tickPeriod * 1.1f;
  287. server.RunTicks(1);
  288. await server.WaitAssertion(() =>
  289. {
  290. Assert.Multiple(() =>
  291. {
  292. // First tick, supply should be delivering 100 W (max tolerance) and start ramping up.
  293. Assert.That(supplier.CurrentSupply, Is.EqualTo(100).Within(0.1));
  294. Assert.That(consumer.ReceivedPower, Is.EqualTo(100).Within(0.1));
  295. });
  296. });
  297. // run for 0.25 seconds (minus the previous tick)
  298. var ticks = (int) Math.Round(0.25 * gameTiming.TickRate) - 1;
  299. server.RunTicks(ticks);
  300. await server.WaitAssertion(() =>
  301. {
  302. Assert.Multiple(() =>
  303. {
  304. // After 15 ticks (0.25 seconds), supply ramp pos should be at 100 W and supply at 100, approx.
  305. Assert.That(supplier.CurrentSupply, Is.EqualTo(200).Within(tickDev));
  306. Assert.That(supplier.SupplyRampPosition, Is.EqualTo(100).Within(tickDev));
  307. Assert.That(consumer.ReceivedPower, Is.EqualTo(200).Within(tickDev));
  308. });
  309. });
  310. // run for 0.75 seconds
  311. ticks = (int) Math.Round(0.75 * gameTiming.TickRate);
  312. server.RunTicks(ticks);
  313. await server.WaitAssertion(() =>
  314. {
  315. Assert.Multiple(() =>
  316. {
  317. // After 1 second total, ramp should be at 400 and supply should be at 400, everybody happy.
  318. Assert.That(supplier.CurrentSupply, Is.EqualTo(400).Within(tickDev));
  319. Assert.That(supplier.SupplyRampPosition, Is.EqualTo(400).Within(tickDev));
  320. Assert.That(consumer.ReceivedPower, Is.EqualTo(400).Within(tickDev));
  321. });
  322. });
  323. await pair.CleanReturnAsync();
  324. }
  325. [Test]
  326. public async Task TestBatteryRamp()
  327. {
  328. await using var pair = await PoolManager.GetServerClient();
  329. var server = pair.Server;
  330. var mapManager = server.ResolveDependency<IMapManager>();
  331. var entityManager = server.ResolveDependency<IEntityManager>();
  332. var gameTiming = server.ResolveDependency<IGameTiming>();
  333. var batterySys = entityManager.System<BatterySystem>();
  334. var mapSys = entityManager.System<SharedMapSystem>();
  335. const float startingCharge = 100_000;
  336. PowerNetworkBatteryComponent netBattery = default!;
  337. BatteryComponent battery = default!;
  338. PowerConsumerComponent consumer = default!;
  339. await server.WaitAssertion(() =>
  340. {
  341. var map = mapSys.CreateMap(out var mapId);
  342. var grid = mapManager.CreateGridEntity(mapId);
  343. // Power only works when anchored
  344. for (var i = 0; i < 3; i++)
  345. {
  346. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  347. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  348. }
  349. var generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.Owner.ToCoordinates());
  350. var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2));
  351. netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(generatorEnt);
  352. battery = entityManager.GetComponent<BatteryComponent>(generatorEnt);
  353. consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
  354. batterySys.SetMaxCharge(generatorEnt, startingCharge, battery);
  355. batterySys.SetCharge(generatorEnt, startingCharge, battery);
  356. netBattery.MaxSupply = 400;
  357. netBattery.SupplyRampRate = 400;
  358. netBattery.SupplyRampTolerance = 100;
  359. consumer.DrawRate = 400;
  360. });
  361. // Exact values can/will be off by a tick, add tolerance for that.
  362. var tickPeriod = (float) gameTiming.TickPeriod.TotalSeconds;
  363. var tickDev = 400 * tickPeriod * 1.1f;
  364. server.RunTicks(1);
  365. await server.WaitAssertion(() =>
  366. {
  367. Assert.Multiple(() =>
  368. {
  369. // First tick, supply should be delivering 100 W (max tolerance) and start ramping up.
  370. Assert.That(netBattery.CurrentSupply, Is.EqualTo(100).Within(0.1));
  371. Assert.That(consumer.ReceivedPower, Is.EqualTo(100).Within(0.1));
  372. });
  373. });
  374. // run for 0.25 seconds (minus the previous tick)
  375. var ticks = (int) Math.Round(0.25 * gameTiming.TickRate) - 1;
  376. server.RunTicks(ticks);
  377. await server.WaitAssertion(() =>
  378. {
  379. Assert.Multiple(() =>
  380. {
  381. // After 15 ticks (0.25 seconds), supply ramp pos should be at 100 W and supply at 100, approx.
  382. Assert.That(netBattery.CurrentSupply, Is.EqualTo(200).Within(tickDev));
  383. Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(100).Within(tickDev));
  384. Assert.That(consumer.ReceivedPower, Is.EqualTo(200).Within(tickDev));
  385. // Trivial integral to calculate expected power spent.
  386. const double spentExpected = (200 + 100) / 2.0 * 0.25;
  387. Assert.That(battery.CurrentCharge, Is.EqualTo(startingCharge - spentExpected).Within(tickDev));
  388. });
  389. });
  390. // run for 0.75 seconds
  391. ticks = (int) Math.Round(0.75 * gameTiming.TickRate);
  392. server.RunTicks(ticks);
  393. await server.WaitAssertion(() =>
  394. {
  395. Assert.Multiple(() =>
  396. {
  397. // After 1 second total, ramp should be at 400 and supply should be at 400, everybody happy.
  398. Assert.That(netBattery.CurrentSupply, Is.EqualTo(400).Within(tickDev));
  399. Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(400).Within(tickDev));
  400. Assert.That(consumer.ReceivedPower, Is.EqualTo(400).Within(tickDev));
  401. // Trivial integral to calculate expected power spent.
  402. const double spentExpected = (400 + 100) / 2.0 * 0.75 + 400 * 0.25;
  403. Assert.That(battery.CurrentCharge, Is.EqualTo(startingCharge - spentExpected).Within(tickDev));
  404. });
  405. });
  406. await pair.CleanReturnAsync();
  407. }
  408. [Test]
  409. public async Task TestNoDemandRampdown()
  410. {
  411. // checks that batteries and supplies properly ramp down if the load is disconnected/disabled.
  412. await using var pair = await PoolManager.GetServerClient();
  413. var server = pair.Server;
  414. var mapManager = server.ResolveDependency<IMapManager>();
  415. var entityManager = server.ResolveDependency<IEntityManager>();
  416. var batterySys = entityManager.System<BatterySystem>();
  417. var mapSys = entityManager.System<SharedMapSystem>();
  418. PowerSupplierComponent supplier = default!;
  419. PowerNetworkBatteryComponent netBattery = default!;
  420. BatteryComponent battery = default!;
  421. PowerConsumerComponent consumer = default!;
  422. var rampRate = 500;
  423. var rampTol = 100;
  424. var draw = 1000;
  425. await server.WaitAssertion(() =>
  426. {
  427. var map = mapSys.CreateMap(out var mapId);
  428. var grid = mapManager.CreateGridEntity(mapId);
  429. // Power only works when anchored
  430. for (var i = 0; i < 3; i++)
  431. {
  432. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  433. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  434. }
  435. var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates());
  436. var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 1));
  437. var batteryEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.Owner.ToCoordinates(0, 2));
  438. netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
  439. battery = entityManager.GetComponent<BatteryComponent>(batteryEnt);
  440. supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
  441. consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
  442. consumer.DrawRate = draw;
  443. supplier.MaxSupply = draw / 2;
  444. supplier.SupplyRampRate = rampRate;
  445. supplier.SupplyRampTolerance = rampTol;
  446. batterySys.SetMaxCharge(batteryEnt, 100_000, battery);
  447. batterySys.SetCharge(batteryEnt, 100_000, battery);
  448. netBattery.MaxSupply = draw / 2;
  449. netBattery.SupplyRampRate = rampRate;
  450. netBattery.SupplyRampTolerance = rampTol;
  451. });
  452. server.RunTicks(1);
  453. await server.WaitAssertion(() =>
  454. {
  455. Assert.Multiple(() =>
  456. {
  457. Assert.That(supplier.CurrentSupply, Is.EqualTo(rampTol).Within(0.1));
  458. Assert.That(netBattery.CurrentSupply, Is.EqualTo(rampTol).Within(0.1));
  459. Assert.That(consumer.ReceivedPower, Is.EqualTo(rampTol * 2).Within(0.1));
  460. });
  461. });
  462. server.RunTicks(60);
  463. await server.WaitAssertion(() =>
  464. {
  465. Assert.Multiple(() =>
  466. {
  467. Assert.That(supplier.CurrentSupply, Is.EqualTo(draw / 2).Within(0.1));
  468. Assert.That(supplier.SupplyRampPosition, Is.EqualTo(draw / 2).Within(0.1));
  469. Assert.That(netBattery.CurrentSupply, Is.EqualTo(draw / 2).Within(0.1));
  470. Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(draw / 2).Within(0.1));
  471. Assert.That(consumer.ReceivedPower, Is.EqualTo(draw).Within(0.1));
  472. });
  473. });
  474. // now we disconnect the load;
  475. consumer.NetworkLoad.Enabled = false;
  476. server.RunTicks(60);
  477. await server.WaitAssertion(() =>
  478. {
  479. Assert.Multiple(() =>
  480. {
  481. Assert.That(supplier.CurrentSupply, Is.EqualTo(0).Within(0.1));
  482. Assert.That(supplier.SupplyRampPosition, Is.EqualTo(0).Within(0.1));
  483. Assert.That(netBattery.CurrentSupply, Is.EqualTo(0).Within(0.1));
  484. Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(0).Within(0.1));
  485. Assert.That(consumer.ReceivedPower, Is.EqualTo(0).Within(0.1));
  486. });
  487. });
  488. await pair.CleanReturnAsync();
  489. }
  490. [Test]
  491. public async Task TestSimpleBatteryChargeDeficit()
  492. {
  493. await using var pair = await PoolManager.GetServerClient();
  494. var server = pair.Server;
  495. var mapManager = server.ResolveDependency<IMapManager>();
  496. var gameTiming = server.ResolveDependency<IGameTiming>();
  497. var entityManager = server.ResolveDependency<IEntityManager>();
  498. var batterySys = entityManager.System<BatterySystem>();
  499. var mapSys = entityManager.System<SharedMapSystem>();
  500. PowerSupplierComponent supplier = default!;
  501. BatteryComponent battery = default!;
  502. await server.WaitAssertion(() =>
  503. {
  504. var map = mapSys.CreateMap(out var mapId);
  505. var grid = mapManager.CreateGridEntity(mapId);
  506. // Power only works when anchored
  507. for (var i = 0; i < 3; i++)
  508. {
  509. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  510. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  511. }
  512. var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates());
  513. var batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", grid.Owner.ToCoordinates(0, 2));
  514. supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
  515. var netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
  516. battery = entityManager.GetComponent<BatteryComponent>(batteryEnt);
  517. supplier.MaxSupply = 500;
  518. supplier.SupplyRampTolerance = 500;
  519. batterySys.SetMaxCharge(batteryEnt, 100_000, battery);
  520. netBattery.MaxChargeRate = 1_000;
  521. netBattery.Efficiency = 0.5f;
  522. });
  523. // run for 0.5 seconds
  524. var ticks = (int) Math.Round(0.5 * gameTiming.TickRate);
  525. server.RunTicks(ticks);
  526. await server.WaitAssertion(() =>
  527. {
  528. Assert.Multiple(() =>
  529. {
  530. // half a second @ 500 W = 250
  531. // 50% efficiency, so 125 J stored total.
  532. Assert.That(battery.CurrentCharge, Is.EqualTo(125).Within(0.1));
  533. Assert.That(supplier.CurrentSupply, Is.EqualTo(500).Within(0.1));
  534. });
  535. });
  536. await pair.CleanReturnAsync();
  537. }
  538. [Test]
  539. public async Task TestFullBattery()
  540. {
  541. await using var pair = await PoolManager.GetServerClient();
  542. var server = pair.Server;
  543. var mapManager = server.ResolveDependency<IMapManager>();
  544. var entityManager = server.ResolveDependency<IEntityManager>();
  545. var gameTiming = server.ResolveDependency<IGameTiming>();
  546. var batterySys = entityManager.System<BatterySystem>();
  547. var mapSys = entityManager.System<SharedMapSystem>();
  548. PowerConsumerComponent consumer = default!;
  549. PowerSupplierComponent supplier = default!;
  550. PowerNetworkBatteryComponent netBattery = default!;
  551. BatteryComponent battery = default!;
  552. await server.WaitAssertion(() =>
  553. {
  554. var map = mapSys.CreateMap(out var mapId);
  555. var grid = mapManager.CreateGridEntity(mapId);
  556. // Power only works when anchored
  557. for (var i = 0; i < 4; i++)
  558. {
  559. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  560. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  561. }
  562. var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1));
  563. entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
  564. var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2));
  565. var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0));
  566. var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3));
  567. consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
  568. supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
  569. netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
  570. battery = entityManager.GetComponent<BatteryComponent>(batteryEnt);
  571. // Consumer needs 1000 W, supplier can only provide 800, battery fills in the remaining 200.
  572. consumer.DrawRate = 1000;
  573. supplier.MaxSupply = 800;
  574. supplier.SupplyRampTolerance = 800;
  575. netBattery.MaxSupply = 400;
  576. netBattery.SupplyRampTolerance = 400;
  577. netBattery.SupplyRampRate = 100_000;
  578. batterySys.SetMaxCharge(batteryEnt, 100_000, battery);
  579. batterySys.SetCharge(batteryEnt, 100_000, battery);
  580. });
  581. // Run some ticks so everything is stable.
  582. server.RunTicks(gameTiming.TickRate);
  583. // Exact values can/will be off by a tick, add tolerance for that.
  584. var tickPeriod = (float) gameTiming.TickPeriod.TotalSeconds;
  585. var tickDev = 400 * tickPeriod * 1.1f;
  586. await server.WaitAssertion(() =>
  587. {
  588. Assert.Multiple(() =>
  589. {
  590. Assert.That(consumer.ReceivedPower, Is.EqualTo(consumer.DrawRate).Within(0.1));
  591. Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.1));
  592. // Battery's current supply includes passed-through power from the supply.
  593. // Assert ramp position is correct to make sure it's only supplying 200 W for real.
  594. Assert.That(netBattery.CurrentSupply, Is.EqualTo(1000).Within(0.1));
  595. Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(200).Within(0.1));
  596. const int expectedSpent = 200;
  597. Assert.That(battery.CurrentCharge, Is.EqualTo(battery.MaxCharge - expectedSpent).Within(tickDev));
  598. });
  599. });
  600. await pair.CleanReturnAsync();
  601. }
  602. [Test]
  603. public async Task TestFullBatteryEfficiencyPassThrough()
  604. {
  605. await using var pair = await PoolManager.GetServerClient();
  606. var server = pair.Server;
  607. var mapManager = server.ResolveDependency<IMapManager>();
  608. var entityManager = server.ResolveDependency<IEntityManager>();
  609. var gameTiming = server.ResolveDependency<IGameTiming>();
  610. var batterySys = entityManager.System<BatterySystem>();
  611. var mapSys = entityManager.System<SharedMapSystem>();
  612. PowerConsumerComponent consumer = default!;
  613. PowerSupplierComponent supplier = default!;
  614. PowerNetworkBatteryComponent netBattery = default!;
  615. BatteryComponent battery = default!;
  616. await server.WaitAssertion(() =>
  617. {
  618. var map = mapSys.CreateMap(out var mapId);
  619. var grid = mapManager.CreateGridEntity(mapId);
  620. // Power only works when anchored
  621. for (var i = 0; i < 4; i++)
  622. {
  623. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  624. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  625. }
  626. var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1));
  627. entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
  628. var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2));
  629. var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0));
  630. var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3));
  631. consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
  632. supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
  633. netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
  634. battery = entityManager.GetComponent<BatteryComponent>(batteryEnt);
  635. // Consumer needs 1000 W, supply and battery can only provide 400 each.
  636. // BUT the battery has 50% input efficiency, so 50% of the power of the supply gets lost.
  637. consumer.DrawRate = 1000;
  638. supplier.MaxSupply = 400;
  639. supplier.SupplyRampTolerance = 400;
  640. netBattery.MaxSupply = 400;
  641. netBattery.SupplyRampTolerance = 400;
  642. netBattery.SupplyRampRate = 100_000;
  643. netBattery.Efficiency = 0.5f;
  644. batterySys.SetMaxCharge(batteryEnt, 1_000_000, battery);
  645. batterySys.SetCharge(batteryEnt, 1_000_000, battery);
  646. });
  647. // Run some ticks so everything is stable.
  648. server.RunTicks(gameTiming.TickRate);
  649. // Exact values can/will be off by a tick, add tolerance for that.
  650. var tickPeriod = (float) gameTiming.TickPeriod.TotalSeconds;
  651. var tickDev = 400 * tickPeriod * 1.1f;
  652. await server.WaitAssertion(() =>
  653. {
  654. Assert.Multiple(() =>
  655. {
  656. Assert.That(consumer.ReceivedPower, Is.EqualTo(600).Within(0.1));
  657. Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.1));
  658. Assert.That(netBattery.CurrentSupply, Is.EqualTo(600).Within(0.1));
  659. Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(400).Within(0.1));
  660. const int expectedSpent = 400;
  661. Assert.That(battery.CurrentCharge, Is.EqualTo(battery.MaxCharge - expectedSpent).Within(tickDev));
  662. });
  663. });
  664. await pair.CleanReturnAsync();
  665. }
  666. [Test]
  667. public async Task TestFullBatteryEfficiencyDemandPassThrough()
  668. {
  669. await using var pair = await PoolManager.GetServerClient();
  670. var server = pair.Server;
  671. var mapManager = server.ResolveDependency<IMapManager>();
  672. var entityManager = server.ResolveDependency<IEntityManager>();
  673. var batterySys = entityManager.System<BatterySystem>();
  674. var mapSys = entityManager.System<SharedMapSystem>();
  675. PowerConsumerComponent consumer1 = default!;
  676. PowerConsumerComponent consumer2 = default!;
  677. PowerSupplierComponent supplier = default!;
  678. await server.WaitAssertion(() =>
  679. {
  680. var map = mapSys.CreateMap(out var mapId);
  681. var grid = mapManager.CreateGridEntity(mapId);
  682. // Map layout here is
  683. // C - consumer
  684. // B - battery
  685. // G - generator
  686. // B - battery
  687. // C - consumer
  688. // Connected in the only way that makes sense.
  689. // Power only works when anchored
  690. for (var i = 0; i < 5; i++)
  691. {
  692. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  693. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  694. }
  695. entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 2));
  696. var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 2));
  697. entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
  698. var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 1));
  699. var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 3));
  700. var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 2));
  701. var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 0));
  702. var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 4));
  703. consumer1 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt1);
  704. consumer2 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt2);
  705. supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
  706. var netBattery1 = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt1);
  707. var netBattery2 = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt2);
  708. var battery1 = entityManager.GetComponent<BatteryComponent>(batteryEnt1);
  709. var battery2 = entityManager.GetComponent<BatteryComponent>(batteryEnt2);
  710. // There are two loads, 500 W and 1000 W respectively.
  711. // The 500 W load is behind a 50% efficient battery,
  712. // so *effectively* it needs 2x as much power from the supply to run.
  713. // Assert that both are getting 50% power.
  714. // Batteries are empty and only a bridge.
  715. consumer1.DrawRate = 500;
  716. consumer2.DrawRate = 1000;
  717. supplier.MaxSupply = 1000;
  718. supplier.SupplyRampTolerance = 1000;
  719. batterySys.SetMaxCharge(batteryEnt1, 1_000_000, battery1);
  720. batterySys.SetMaxCharge(batteryEnt2, 1_000_000, battery2);
  721. netBattery1.MaxChargeRate = 1_000;
  722. netBattery2.MaxChargeRate = 1_000;
  723. netBattery1.Efficiency = 0.5f;
  724. netBattery1.MaxSupply = 1_000_000;
  725. netBattery2.MaxSupply = 1_000_000;
  726. netBattery1.SupplyRampTolerance = 1_000_000;
  727. netBattery2.SupplyRampTolerance = 1_000_000;
  728. });
  729. // Run some ticks so everything is stable.
  730. server.RunTicks(10);
  731. await server.WaitAssertion(() =>
  732. {
  733. Assert.Multiple(() =>
  734. {
  735. Assert.That(consumer1.ReceivedPower, Is.EqualTo(250).Within(0.1));
  736. Assert.That(consumer2.ReceivedPower, Is.EqualTo(500).Within(0.1));
  737. Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.1));
  738. });
  739. });
  740. await pair.CleanReturnAsync();
  741. }
  742. /// <summary>
  743. /// Checks that if there is insufficient supply to meet demand, generators will run at full power instead of
  744. /// having generators and batteries sharing the load.
  745. /// </summary>
  746. [Test]
  747. public async Task TestSupplyPrioritized()
  748. {
  749. await using var pair = await PoolManager.GetServerClient();
  750. var server = pair.Server;
  751. var mapManager = server.ResolveDependency<IMapManager>();
  752. var entityManager = server.ResolveDependency<IEntityManager>();
  753. var gameTiming = server.ResolveDependency<IGameTiming>();
  754. var batterySys = entityManager.System<BatterySystem>();
  755. var mapSys = entityManager.System<SharedMapSystem>();
  756. PowerConsumerComponent consumer = default!;
  757. PowerSupplierComponent supplier1 = default!;
  758. PowerSupplierComponent supplier2 = default!;
  759. PowerNetworkBatteryComponent netBattery1 = default!;
  760. PowerNetworkBatteryComponent netBattery2 = default!;
  761. BatteryComponent battery1 = default!;
  762. BatteryComponent battery2 = default!;
  763. await server.WaitAssertion(() =>
  764. {
  765. var map = mapSys.CreateMap(out var mapId);
  766. var grid = mapManager.CreateGridEntity(mapId);
  767. // Layout is two generators, two batteries, and one load. As to why two: because previously this test
  768. // would fail ONLY if there were more than two batteries present, because each of them tries to supply
  769. // the unmet load, leading to a double-battery supply attempt and ramping down of power generation from
  770. // supplies.
  771. // Actual layout is Battery Supply, Load, Supply, Battery
  772. // Place cables
  773. for (var i = -2; i <= 2; i++)
  774. {
  775. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  776. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  777. }
  778. var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2));
  779. var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, -2));
  780. var supplyEnt1 = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 1));
  781. var supplyEnt2 = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, -1));
  782. var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 0));
  783. consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
  784. supplier1 = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt1);
  785. supplier2 = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt2);
  786. netBattery1 = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt1);
  787. netBattery2 = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt2);
  788. battery1 = entityManager.GetComponent<BatteryComponent>(batteryEnt1);
  789. battery2 = entityManager.GetComponent<BatteryComponent>(batteryEnt2);
  790. // Consumer wants 2k, supplies can only provide 1k (500 each). Expectation is that batteries will only provide the necessary remaining 1k (500 each).
  791. // Previously this failed with a 2x 333 w supplies and 2x 666 w batteries.
  792. consumer.DrawRate = 2000;
  793. supplier1.MaxSupply = 500;
  794. supplier2.MaxSupply = 500;
  795. supplier1.SupplyRampTolerance = 500;
  796. supplier2.SupplyRampTolerance = 500;
  797. netBattery1.MaxSupply = 1000;
  798. netBattery2.MaxSupply = 1000;
  799. netBattery1.SupplyRampTolerance = 1000;
  800. netBattery2.SupplyRampTolerance = 1000;
  801. netBattery1.SupplyRampRate = 100_000;
  802. netBattery2.SupplyRampRate = 100_000;
  803. batterySys.SetMaxCharge(batteryEnt1, 100_000, battery1);
  804. batterySys.SetMaxCharge(batteryEnt2, 100_000, battery2);
  805. batterySys.SetCharge(batteryEnt1, 100_000, battery1);
  806. batterySys.SetCharge(batteryEnt2, 100_000, battery2);
  807. });
  808. // Run some ticks so everything is stable.
  809. server.RunTicks(60);
  810. await server.WaitAssertion(() =>
  811. {
  812. Assert.Multiple(() =>
  813. {
  814. Assert.That(consumer.ReceivedPower, Is.EqualTo(consumer.DrawRate).Within(0.1));
  815. Assert.That(supplier1.CurrentSupply, Is.EqualTo(supplier1.MaxSupply).Within(0.1));
  816. Assert.That(supplier2.CurrentSupply, Is.EqualTo(supplier2.MaxSupply).Within(0.1));
  817. Assert.That(netBattery1.CurrentSupply, Is.EqualTo(500).Within(0.1));
  818. Assert.That(netBattery2.CurrentSupply, Is.EqualTo(500).Within(0.1));
  819. Assert.That(netBattery2.SupplyRampPosition, Is.EqualTo(500).Within(0.1));
  820. Assert.That(netBattery2.SupplyRampPosition, Is.EqualTo(500).Within(0.1));
  821. });
  822. });
  823. await pair.CleanReturnAsync();
  824. }
  825. /// <summary>
  826. /// Test that power is distributed proportionally, even through batteries.
  827. /// </summary>
  828. [Test]
  829. public async Task TestBatteriesProportional()
  830. {
  831. await using var pair = await PoolManager.GetServerClient();
  832. var server = pair.Server;
  833. var mapManager = server.ResolveDependency<IMapManager>();
  834. var entityManager = server.ResolveDependency<IEntityManager>();
  835. var batterySys = entityManager.System<BatterySystem>();
  836. var mapSys = entityManager.System<SharedMapSystem>();
  837. PowerConsumerComponent consumer1 = default!;
  838. PowerConsumerComponent consumer2 = default!;
  839. PowerSupplierComponent supplier = default!;
  840. await server.WaitAssertion(() =>
  841. {
  842. var map = mapSys.CreateMap(out var mapId);
  843. var grid = mapManager.CreateGridEntity(mapId);
  844. // Map layout here is
  845. // C - consumer
  846. // B - battery
  847. // G - generator
  848. // B - battery
  849. // C - consumer
  850. // Connected in the only way that makes sense.
  851. // Power only works when anchored
  852. for (var i = 0; i < 5; i++)
  853. {
  854. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  855. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  856. }
  857. entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 2));
  858. var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 2));
  859. entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
  860. var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 1));
  861. var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 3));
  862. var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 2));
  863. var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 0));
  864. var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 4));
  865. consumer1 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt1);
  866. consumer2 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt2);
  867. supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
  868. var netBattery1 = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt1);
  869. var netBattery2 = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt2);
  870. var battery1 = entityManager.GetComponent<BatteryComponent>(batteryEnt1);
  871. var battery2 = entityManager.GetComponent<BatteryComponent>(batteryEnt2);
  872. consumer1.DrawRate = 500;
  873. consumer2.DrawRate = 1000;
  874. supplier.MaxSupply = 1000;
  875. supplier.SupplyRampTolerance = 1000;
  876. batterySys.SetMaxCharge(batteryEnt1, 1_000_000, battery1);
  877. batterySys.SetMaxCharge(batteryEnt2, 1_000_000, battery2);
  878. netBattery1.MaxChargeRate = 20;
  879. netBattery2.MaxChargeRate = 20;
  880. netBattery1.MaxSupply = 1_000_000;
  881. netBattery2.MaxSupply = 1_000_000;
  882. netBattery1.SupplyRampTolerance = 1_000_000;
  883. netBattery2.SupplyRampTolerance = 1_000_000;
  884. });
  885. // Run some ticks so everything is stable.
  886. server.RunTicks(60);
  887. await server.WaitAssertion(() =>
  888. {
  889. Assert.Multiple(() =>
  890. {
  891. // NOTE: MaxChargeRate on batteries actually skews the demand.
  892. // So that's why the tolerance is so high, the charge rate is so *low*,
  893. // and we run so many ticks to stabilize.
  894. Assert.That(consumer1.ReceivedPower, Is.EqualTo(333.333).Within(10));
  895. Assert.That(consumer2.ReceivedPower, Is.EqualTo(666.666).Within(10));
  896. Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.1));
  897. });
  898. });
  899. await pair.CleanReturnAsync();
  900. }
  901. [Test]
  902. public async Task TestBatteryEngineCut()
  903. {
  904. await using var pair = await PoolManager.GetServerClient();
  905. var server = pair.Server;
  906. var mapManager = server.ResolveDependency<IMapManager>();
  907. var entityManager = server.ResolveDependency<IEntityManager>();
  908. var batterySys = entityManager.System<BatterySystem>();
  909. var mapSys = entityManager.System<SharedMapSystem>();
  910. PowerConsumerComponent consumer = default!;
  911. PowerSupplierComponent supplier = default!;
  912. PowerNetworkBatteryComponent netBattery = default!;
  913. await server.WaitPost(() =>
  914. {
  915. var map = mapSys.CreateMap(out var mapId);
  916. var grid = mapManager.CreateGridEntity(mapId);
  917. // Power only works when anchored
  918. for (var i = 0; i < 4; i++)
  919. {
  920. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  921. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
  922. }
  923. var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1));
  924. entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
  925. var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2));
  926. var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0));
  927. var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3));
  928. consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
  929. supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
  930. netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
  931. var battery = entityManager.GetComponent<BatteryComponent>(batteryEnt);
  932. consumer.DrawRate = 1000;
  933. supplier.MaxSupply = 1000;
  934. supplier.SupplyRampTolerance = 1000;
  935. netBattery.MaxSupply = 1000;
  936. netBattery.SupplyRampTolerance = 200;
  937. netBattery.SupplyRampRate = 10;
  938. batterySys.SetMaxCharge(batteryEnt, 100_000, battery);
  939. batterySys.SetCharge(batteryEnt, 100_000, battery);
  940. });
  941. // Run some ticks so everything is stable.
  942. server.RunTicks(5);
  943. await server.WaitAssertion(() =>
  944. {
  945. Assert.Multiple(() =>
  946. {
  947. // Supply and consumer are fully loaded/supplied.
  948. Assert.That(consumer.ReceivedPower, Is.EqualTo(consumer.DrawRate).Within(0.5));
  949. Assert.That(supplier.CurrentSupply, Is.EqualTo(supplier.MaxSupply).Within(0.5));
  950. });
  951. // Cut off the supplier
  952. supplier.Enabled = false;
  953. // Remove tolerance on battery too.
  954. netBattery.SupplyRampTolerance = 5;
  955. });
  956. server.RunTicks(3);
  957. await server.WaitAssertion(() =>
  958. {
  959. Assert.Multiple(() =>
  960. {
  961. // Assert that network drops to 0 power and starts ramping up
  962. Assert.That(consumer.ReceivedPower, Is.LessThan(50).And.GreaterThan(0));
  963. Assert.That(netBattery.CurrentReceiving, Is.EqualTo(0));
  964. Assert.That(netBattery.CurrentSupply, Is.GreaterThan(0));
  965. });
  966. });
  967. await pair.CleanReturnAsync();
  968. }
  969. /// <summary>
  970. /// Test that <see cref="CableTerminalNode"/> correctly isolates two networks.
  971. /// </summary>
  972. [Test]
  973. public async Task TestTerminalNodeGroups()
  974. {
  975. await using var pair = await PoolManager.GetServerClient();
  976. var server = pair.Server;
  977. var mapManager = server.ResolveDependency<IMapManager>();
  978. var entityManager = server.ResolveDependency<IEntityManager>();
  979. var nodeContainer = entityManager.System<NodeContainerSystem>();
  980. var mapSys = entityManager.System<SharedMapSystem>();
  981. CableNode leftNode = default!;
  982. CableNode rightNode = default!;
  983. Node batteryInput = default!;
  984. Node batteryOutput = default!;
  985. await server.WaitAssertion(() =>
  986. {
  987. var map = mapSys.CreateMap(out var mapId);
  988. var grid = mapManager.CreateGridEntity(mapId);
  989. // Power only works when anchored
  990. for (var i = 0; i < 4; i++)
  991. {
  992. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  993. }
  994. var leftEnt = entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 0));
  995. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 1));
  996. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 2));
  997. var rightEnt = entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 3));
  998. var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1));
  999. entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
  1000. var battery = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2));
  1001. var batteryNodeContainer = entityManager.GetComponent<NodeContainerComponent>(battery);
  1002. if (nodeContainer.TryGetNode<CableNode>(entityManager.GetComponent<NodeContainerComponent>(leftEnt),
  1003. "power", out var leftN))
  1004. leftNode = leftN;
  1005. if (nodeContainer.TryGetNode<CableNode>(entityManager.GetComponent<NodeContainerComponent>(rightEnt),
  1006. "power", out var rightN))
  1007. rightNode = rightN;
  1008. if (nodeContainer.TryGetNode<Node>(batteryNodeContainer, "input", out var nInput))
  1009. batteryInput = nInput;
  1010. if (nodeContainer.TryGetNode<Node>(batteryNodeContainer, "output", out var nOutput))
  1011. batteryOutput = nOutput;
  1012. });
  1013. // Run ticks to allow node groups to update.
  1014. server.RunTicks(1);
  1015. await server.WaitAssertion(() =>
  1016. {
  1017. Assert.Multiple(() =>
  1018. {
  1019. Assert.That(batteryInput.NodeGroup, Is.EqualTo(leftNode.NodeGroup));
  1020. Assert.That(batteryOutput.NodeGroup, Is.EqualTo(rightNode.NodeGroup));
  1021. Assert.That(leftNode.NodeGroup, Is.Not.EqualTo(rightNode.NodeGroup));
  1022. });
  1023. });
  1024. await pair.CleanReturnAsync();
  1025. }
  1026. [Test]
  1027. public async Task ApcChargingTest()
  1028. {
  1029. await using var pair = await PoolManager.GetServerClient();
  1030. var server = pair.Server;
  1031. var mapManager = server.ResolveDependency<IMapManager>();
  1032. var entityManager = server.ResolveDependency<IEntityManager>();
  1033. var batterySys = entityManager.System<BatterySystem>();
  1034. var mapSys = entityManager.System<SharedMapSystem>();
  1035. PowerNetworkBatteryComponent substationNetBattery = default!;
  1036. BatteryComponent apcBattery = default!;
  1037. await server.WaitAssertion(() =>
  1038. {
  1039. var map = mapSys.CreateMap(out var mapId);
  1040. var grid = mapManager.CreateGridEntity(mapId);
  1041. // Power only works when anchored
  1042. for (var i = 0; i < 3; i++)
  1043. {
  1044. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  1045. }
  1046. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 0));
  1047. entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, 1));
  1048. entityManager.SpawnEntity("CableMV", grid.Owner.ToCoordinates(0, 1));
  1049. entityManager.SpawnEntity("CableMV", grid.Owner.ToCoordinates(0, 2));
  1050. var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0));
  1051. var substationEnt = entityManager.SpawnEntity("SubstationDummy", grid.Owner.ToCoordinates(0, 1));
  1052. var apcEnt = entityManager.SpawnEntity("ApcDummy", grid.Owner.ToCoordinates(0, 2));
  1053. var generatorSupplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
  1054. substationNetBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(substationEnt);
  1055. apcBattery = entityManager.GetComponent<BatteryComponent>(apcEnt);
  1056. generatorSupplier.MaxSupply = 1000;
  1057. generatorSupplier.SupplyRampTolerance = 1000;
  1058. batterySys.SetCharge(apcEnt, 0, apcBattery);
  1059. });
  1060. server.RunTicks(5); //let run a few ticks for PowerNets to reevaluate and start charging apc
  1061. await server.WaitAssertion(() =>
  1062. {
  1063. Assert.Multiple(() =>
  1064. {
  1065. Assert.That(substationNetBattery.CurrentSupply, Is.GreaterThan(0)); //substation should be providing power
  1066. Assert.That(apcBattery.CurrentCharge, Is.GreaterThan(0)); //apc battery should have gained charge
  1067. });
  1068. });
  1069. await pair.CleanReturnAsync();
  1070. }
  1071. [Test]
  1072. public async Task ApcNetTest()
  1073. {
  1074. await using var pair = await PoolManager.GetServerClient();
  1075. var server = pair.Server;
  1076. var mapManager = server.ResolveDependency<IMapManager>();
  1077. var entityManager = server.ResolveDependency<IEntityManager>();
  1078. var batterySys = entityManager.System<BatterySystem>();
  1079. var extensionCableSystem = entityManager.System<ExtensionCableSystem>();
  1080. var mapSys = entityManager.System<SharedMapSystem>();
  1081. PowerNetworkBatteryComponent apcNetBattery = default!;
  1082. ApcPowerReceiverComponent receiver = default!;
  1083. ApcPowerReceiverComponent unpoweredReceiver = default!;
  1084. await server.WaitAssertion(() =>
  1085. {
  1086. var map = mapSys.CreateMap(out var mapId);
  1087. var grid = mapManager.CreateGridEntity(mapId);
  1088. const int range = 5;
  1089. // Power only works when anchored
  1090. for (var i = 0; i < range; i++)
  1091. {
  1092. mapSys.SetTile(grid, new Vector2i(0, i), new Tile(1));
  1093. }
  1094. var apcEnt = entityManager.SpawnEntity("ApcDummy", grid.Owner.ToCoordinates(0, 0));
  1095. var apcExtensionEnt = entityManager.SpawnEntity("CableApcExtension", grid.Owner.ToCoordinates(0, 0));
  1096. // Create a powered receiver in range (range is 0 indexed)
  1097. var powerReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", grid.Owner.ToCoordinates(0, range - 1));
  1098. receiver = entityManager.GetComponent<ApcPowerReceiverComponent>(powerReceiverEnt);
  1099. // Create an unpowered receiver outside range
  1100. var unpoweredReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", grid.Owner.ToCoordinates(0, range));
  1101. unpoweredReceiver = entityManager.GetComponent<ApcPowerReceiverComponent>(unpoweredReceiverEnt);
  1102. var battery = entityManager.GetComponent<BatteryComponent>(apcEnt);
  1103. apcNetBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(apcEnt);
  1104. extensionCableSystem.SetProviderTransferRange(apcExtensionEnt, range);
  1105. extensionCableSystem.SetReceiverReceptionRange(powerReceiverEnt, range);
  1106. batterySys.SetMaxCharge(apcEnt, 10000, battery); //arbitrary nonzero amount of charge
  1107. batterySys.SetCharge(apcEnt, battery.MaxCharge, battery); //fill battery
  1108. receiver.Load = 1; //arbitrary small amount of power
  1109. });
  1110. server.RunTicks(1); //let run a tick for ApcNet to process power
  1111. await server.WaitAssertion(() =>
  1112. {
  1113. Assert.Multiple(() =>
  1114. {
  1115. Assert.That(receiver.Powered, "Receiver in range should be powered");
  1116. Assert.That(!unpoweredReceiver.Powered, "Out of range receiver should not be powered");
  1117. Assert.That(apcNetBattery.CurrentSupply, Is.EqualTo(1).Within(0.1));
  1118. });
  1119. });
  1120. await pair.CleanReturnAsync();
  1121. }
  1122. }
  1123. }