PowerMonitoringConsoleSystem.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. using Content.Server.NodeContainer;
  2. using Content.Server.NodeContainer.EntitySystems;
  3. using Content.Server.NodeContainer.Nodes;
  4. using Content.Server.Power.Components;
  5. using Content.Server.Power.Nodes;
  6. using Content.Server.Power.NodeGroups;
  7. using Content.Server.StationEvents.Components;
  8. using Content.Shared.GameTicking.Components;
  9. using Content.Shared.Pinpointer;
  10. using Content.Shared.Station.Components;
  11. using Content.Shared.Power;
  12. using JetBrains.Annotations;
  13. using Robust.Server.GameObjects;
  14. using Robust.Shared.Map.Components;
  15. using Robust.Shared.Utility;
  16. using System.Linq;
  17. namespace Content.Server.Power.EntitySystems;
  18. [UsedImplicitly]
  19. internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitoringConsoleSystem
  20. {
  21. [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
  22. [Dependency] private readonly SharedMapSystem _sharedMapSystem = default!;
  23. // Note: this data does not need to be saved
  24. private Dictionary<EntityUid, Dictionary<Vector2i, PowerCableChunk>> _gridPowerCableChunks = new();
  25. private float _updateTimer = 1.0f;
  26. private const float UpdateTime = 1.0f;
  27. private const float RoguePowerConsumerThreshold = 100000;
  28. public override void Initialize()
  29. {
  30. base.Initialize();
  31. // Console events
  32. SubscribeLocalEvent<PowerMonitoringConsoleComponent, ComponentInit>(OnConsoleInit);
  33. SubscribeLocalEvent<PowerMonitoringConsoleComponent, EntParentChangedMessage>(OnConsoleParentChanged);
  34. SubscribeLocalEvent<PowerMonitoringCableNetworksComponent, ComponentInit>(OnCableNetworksInit);
  35. SubscribeLocalEvent<PowerMonitoringCableNetworksComponent, EntParentChangedMessage>(OnCableNetworksParentChanged);
  36. // UI events
  37. SubscribeLocalEvent<PowerMonitoringConsoleComponent, PowerMonitoringConsoleMessage>(OnPowerMonitoringConsoleMessage);
  38. SubscribeLocalEvent<PowerMonitoringConsoleComponent, BoundUIOpenedEvent>(OnBoundUIOpened);
  39. // Grid events
  40. SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
  41. SubscribeLocalEvent<CableComponent, CableAnchorStateChangedEvent>(OnCableAnchorStateChanged);
  42. SubscribeLocalEvent<PowerMonitoringDeviceComponent, AnchorStateChangedEvent>(OnDeviceAnchoringChanged);
  43. SubscribeLocalEvent<PowerMonitoringDeviceComponent, NodeGroupsRebuilt>(OnNodeGroupRebuilt);
  44. // Game rule events
  45. SubscribeLocalEvent<GameRuleStartedEvent>(OnPowerGridCheckStarted);
  46. SubscribeLocalEvent<GameRuleEndedEvent>(OnPowerGridCheckEnded);
  47. }
  48. #region EventHandling
  49. private void OnConsoleInit(EntityUid uid, PowerMonitoringConsoleComponent component, ComponentInit args)
  50. {
  51. RefreshPowerMonitoringConsole(uid, component);
  52. }
  53. private void OnConsoleParentChanged(EntityUid uid, PowerMonitoringConsoleComponent component, EntParentChangedMessage args)
  54. {
  55. RefreshPowerMonitoringConsole(uid, component);
  56. }
  57. private void OnCableNetworksInit(EntityUid uid, PowerMonitoringCableNetworksComponent component, ComponentInit args)
  58. {
  59. RefreshPowerMonitoringCableNetworks(uid, component);
  60. }
  61. private void OnCableNetworksParentChanged(EntityUid uid, PowerMonitoringCableNetworksComponent component, EntParentChangedMessage args)
  62. {
  63. RefreshPowerMonitoringCableNetworks(uid, component);
  64. }
  65. private void OnPowerMonitoringConsoleMessage(EntityUid uid, PowerMonitoringConsoleComponent component, PowerMonitoringConsoleMessage args)
  66. {
  67. var focus = EntityManager.GetEntity(args.FocusDevice);
  68. var group = args.FocusGroup;
  69. // Update this if the focus device has changed
  70. if (component.Focus != focus)
  71. {
  72. component.Focus = focus;
  73. if (TryComp<PowerMonitoringCableNetworksComponent>(uid, out var cableNetworks))
  74. {
  75. cableNetworks.FocusChunks.Clear(); // Component will be dirtied when these chunks are rebuilt, unless the focus is null
  76. if (focus == null)
  77. Dirty(uid, cableNetworks);
  78. }
  79. }
  80. // Update this if the focus group has changed
  81. if (component.FocusGroup != group)
  82. {
  83. component.FocusGroup = args.FocusGroup;
  84. Dirty(uid, component);
  85. }
  86. }
  87. private void OnBoundUIOpened(EntityUid uid, PowerMonitoringConsoleComponent component, BoundUIOpenedEvent args)
  88. {
  89. component.Focus = null;
  90. component.FocusGroup = PowerMonitoringConsoleGroup.Generator;
  91. if (TryComp<PowerMonitoringCableNetworksComponent>(uid, out var cableNetworks))
  92. {
  93. cableNetworks.FocusChunks.Clear();
  94. Dirty(uid, cableNetworks);
  95. }
  96. }
  97. private void OnGridSplit(ref GridSplitEvent args)
  98. {
  99. // Collect grids
  100. var allGrids = args.NewGrids.ToList();
  101. if (!allGrids.Contains(args.Grid))
  102. allGrids.Add(args.Grid);
  103. // Refresh affected power cable grids
  104. foreach (var grid in allGrids)
  105. {
  106. if (!TryComp<MapGridComponent>(grid, out var map))
  107. continue;
  108. RefreshPowerCableGrid(grid, map);
  109. }
  110. // Update power monitoring consoles that stand upon an updated grid
  111. var query = AllEntityQuery<PowerMonitoringConsoleComponent, PowerMonitoringCableNetworksComponent, TransformComponent>();
  112. while (query.MoveNext(out var ent, out var entConsole, out var entCableNetworks, out var entXform))
  113. {
  114. if (entXform.GridUid == null)
  115. continue;
  116. if (!allGrids.Contains(entXform.GridUid.Value))
  117. continue;
  118. RefreshPowerMonitoringConsole(ent, entConsole);
  119. RefreshPowerMonitoringCableNetworks(ent, entCableNetworks);
  120. }
  121. }
  122. public void OnCableAnchorStateChanged(EntityUid uid, CableComponent component, CableAnchorStateChangedEvent args)
  123. {
  124. var xform = args.Transform;
  125. if (xform.GridUid == null || !TryComp<MapGridComponent>(xform.GridUid, out var grid))
  126. return;
  127. if (!_gridPowerCableChunks.TryGetValue(xform.GridUid.Value, out var allChunks))
  128. allChunks = new();
  129. var tile = _sharedMapSystem.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates);
  130. var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
  131. if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
  132. {
  133. chunk = new PowerCableChunk(chunkOrigin);
  134. allChunks[chunkOrigin] = chunk;
  135. }
  136. var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
  137. var flag = GetFlag(relative);
  138. if (args.Anchored)
  139. chunk.PowerCableData[(int) component.CableType] |= flag;
  140. else
  141. chunk.PowerCableData[(int) component.CableType] &= ~flag;
  142. var query = AllEntityQuery<PowerMonitoringCableNetworksComponent, TransformComponent>();
  143. while (query.MoveNext(out var ent, out var entCableNetworks, out var entXform))
  144. {
  145. if (entXform.GridUid != xform.GridUid)
  146. continue;
  147. entCableNetworks.AllChunks = allChunks;
  148. Dirty(ent, entCableNetworks);
  149. }
  150. }
  151. private void OnDeviceAnchoringChanged(EntityUid uid, PowerMonitoringDeviceComponent component, AnchorStateChangedEvent args)
  152. {
  153. var xform = Transform(uid);
  154. var gridUid = xform.GridUid;
  155. if (gridUid == null)
  156. return;
  157. if (component.IsCollectionMasterOrChild)
  158. AssignEntityAsCollectionMaster(uid, component, xform);
  159. var query = AllEntityQuery<PowerMonitoringConsoleComponent, TransformComponent>();
  160. while (query.MoveNext(out var ent, out var entConsole, out var entXform))
  161. {
  162. if (gridUid != entXform.GridUid)
  163. continue;
  164. if (!args.Anchored)
  165. {
  166. entConsole.PowerMonitoringDeviceMetaData.Remove(EntityManager.GetNetEntity(uid));
  167. Dirty(ent, entConsole);
  168. continue;
  169. }
  170. var name = MetaData(uid).EntityName;
  171. var coords = EntityManager.GetNetCoordinates(xform.Coordinates);
  172. var metaData = new PowerMonitoringDeviceMetaData(name, coords, component.Group, component.SpritePath, component.SpriteState);
  173. entConsole.PowerMonitoringDeviceMetaData.TryAdd(EntityManager.GetNetEntity(uid), metaData);
  174. Dirty(ent, entConsole);
  175. }
  176. }
  177. public void OnNodeGroupRebuilt(EntityUid uid, PowerMonitoringDeviceComponent component, NodeGroupsRebuilt args)
  178. {
  179. if (component.IsCollectionMasterOrChild)
  180. AssignEntityAsCollectionMaster(uid, component);
  181. var query = AllEntityQuery<PowerMonitoringConsoleComponent, PowerMonitoringCableNetworksComponent>();
  182. while (query.MoveNext(out var _, out var entConsole, out var entCableNetworks))
  183. {
  184. if (entConsole.Focus == uid)
  185. entCableNetworks.FocusChunks.Clear(); // Component is dirtied when these chunks are rebuilt
  186. }
  187. }
  188. private void OnPowerGridCheckStarted(ref GameRuleStartedEvent ev)
  189. {
  190. if (!TryComp<PowerGridCheckRuleComponent>(ev.RuleEntity, out var rule))
  191. return;
  192. var query = AllEntityQuery<PowerMonitoringConsoleComponent, TransformComponent>();
  193. while (query.MoveNext(out var uid, out var console, out var xform))
  194. {
  195. if (CompOrNull<StationMemberComponent>(xform.GridUid)?.Station == rule.AffectedStation)
  196. {
  197. console.Flags |= PowerMonitoringFlags.PowerNetAbnormalities;
  198. Dirty(uid, console);
  199. }
  200. }
  201. }
  202. private void OnPowerGridCheckEnded(ref GameRuleEndedEvent ev)
  203. {
  204. if (!TryComp<PowerGridCheckRuleComponent>(ev.RuleEntity, out var rule))
  205. return;
  206. var query = AllEntityQuery<PowerMonitoringConsoleComponent, TransformComponent>();
  207. while (query.MoveNext(out var uid, out var console, out var xform))
  208. {
  209. if (CompOrNull<StationMemberComponent>(xform.GridUid)?.Station == rule.AffectedStation)
  210. {
  211. console.Flags &= ~PowerMonitoringFlags.PowerNetAbnormalities;
  212. Dirty(uid, console);
  213. }
  214. }
  215. }
  216. #endregion
  217. public override void Update(float frameTime)
  218. {
  219. base.Update(frameTime);
  220. _updateTimer += frameTime;
  221. if (_updateTimer >= UpdateTime)
  222. {
  223. _updateTimer -= UpdateTime;
  224. var query = AllEntityQuery<PowerMonitoringConsoleComponent>();
  225. while (query.MoveNext(out var ent, out var console))
  226. {
  227. if (!_userInterfaceSystem.IsUiOpen(ent, PowerMonitoringConsoleUiKey.Key))
  228. continue;
  229. UpdateUIState(ent, console);
  230. }
  231. }
  232. }
  233. private void UpdateUIState(EntityUid uid, PowerMonitoringConsoleComponent component)
  234. {
  235. var consoleXform = Transform(uid);
  236. if (consoleXform?.GridUid == null)
  237. return;
  238. var gridUid = consoleXform.GridUid.Value;
  239. if (!TryComp<MapGridComponent>(gridUid, out var mapGrid))
  240. return;
  241. // The grid must have a NavMapComponent to visualize the map in the UI
  242. EnsureComp<NavMapComponent>(gridUid);
  243. // Initializing data to be send to the client
  244. var totalSources = 0d;
  245. var totalBatteryUsage = 0d;
  246. var totalLoads = 0d;
  247. var allEntries = new List<PowerMonitoringConsoleEntry>();
  248. var sourcesForFocus = new List<PowerMonitoringConsoleEntry>();
  249. var loadsForFocus = new List<PowerMonitoringConsoleEntry>();
  250. var flags = component.Flags;
  251. // Reset RoguePowerConsumer flag
  252. component.Flags &= ~PowerMonitoringFlags.RoguePowerConsumer;
  253. // Record the load value of all non-tracked power consumers on the same grid as the console
  254. var powerConsumerQuery = AllEntityQuery<PowerConsumerComponent, TransformComponent>();
  255. while (powerConsumerQuery.MoveNext(out var ent, out var powerConsumer, out var xform))
  256. {
  257. if (xform.Anchored == false || xform.GridUid != gridUid)
  258. continue;
  259. if (TryComp<PowerMonitoringDeviceComponent>(ent, out var device))
  260. continue;
  261. // Flag an alert if power consumption is ridiculous
  262. if (powerConsumer.ReceivedPower >= RoguePowerConsumerThreshold)
  263. component.Flags |= PowerMonitoringFlags.RoguePowerConsumer;
  264. totalLoads += powerConsumer.DrawRate;
  265. }
  266. if (component.Flags != flags)
  267. Dirty(uid, component);
  268. // Loop over all tracked devices
  269. var powerMonitoringDeviceQuery = AllEntityQuery<PowerMonitoringDeviceComponent, TransformComponent>();
  270. while (powerMonitoringDeviceQuery.MoveNext(out var ent, out var device, out var xform))
  271. {
  272. // Ignore joint, non-master entities
  273. if (device.IsCollectionMasterOrChild && !device.IsCollectionMaster)
  274. continue;
  275. if (xform.Anchored == false || xform.GridUid != gridUid)
  276. continue;
  277. // Get the device power stats
  278. var powerStats = GetPowerStats(ent, device);
  279. //, out var powerSupplied, out var powerUsage, out var batteryUsage);
  280. // Update all running totals
  281. totalSources += powerStats.PowerSupplied;
  282. totalLoads += powerStats.PowerUsage;
  283. totalBatteryUsage += powerStats.BatteryUsage;
  284. // Continue on if the device is not in the current focus group
  285. if (device.Group != component.FocusGroup)
  286. continue;
  287. // Generate a new console entry with which to populate the UI
  288. var entry = new PowerMonitoringConsoleEntry(EntityManager.GetNetEntity(ent), device.Group, powerStats.PowerValue, powerStats.BatteryLevel);
  289. allEntries.Add(entry);
  290. }
  291. // Update the UI focus data (if applicable)
  292. if (component.Focus != null)
  293. {
  294. if (TryComp<NodeContainerComponent>(component.Focus, out var nodeContainer) &&
  295. TryComp<PowerMonitoringDeviceComponent>(component.Focus, out var device))
  296. {
  297. // Record the tracked sources powering the device
  298. if (nodeContainer.Nodes.TryGetValue(device.SourceNode, out var sourceNode))
  299. GetSourcesForNode(component.Focus.Value, sourceNode, out sourcesForFocus);
  300. // Search for the enabled load node (required for portable generators)
  301. var loadNodeName = device.LoadNode;
  302. if (device.LoadNodes != null)
  303. {
  304. var foundNode = nodeContainer.Nodes.FirstOrNull(x => x.Value is CableDeviceNode && (x.Value as CableDeviceNode)?.Enabled == true);
  305. if (foundNode != null)
  306. loadNodeName = foundNode.Value.Key;
  307. }
  308. // Record the tracked loads on the device
  309. if (nodeContainer.Nodes.TryGetValue(loadNodeName, out var loadNode))
  310. GetLoadsForNode(component.Focus.Value, loadNode, out loadsForFocus);
  311. // If the UI focus changed, update the highlighted power network
  312. if (TryComp<PowerMonitoringCableNetworksComponent>(uid, out var cableNetworks) &&
  313. cableNetworks.FocusChunks.Count == 0)
  314. {
  315. var reachableEntities = new List<EntityUid>();
  316. if (sourceNode?.NodeGroup != null)
  317. {
  318. foreach (var node in sourceNode.NodeGroup.Nodes)
  319. reachableEntities.Add(node.Owner);
  320. }
  321. if (loadNode?.NodeGroup != null)
  322. {
  323. foreach (var node in loadNode.NodeGroup.Nodes)
  324. reachableEntities.Add(node.Owner);
  325. }
  326. UpdateFocusNetwork(uid, cableNetworks, gridUid, mapGrid, reachableEntities);
  327. }
  328. }
  329. }
  330. // Set the UI state
  331. _userInterfaceSystem.SetUiState(uid,
  332. PowerMonitoringConsoleUiKey.Key,
  333. new PowerMonitoringConsoleBoundInterfaceState
  334. (totalSources,
  335. totalBatteryUsage,
  336. totalLoads,
  337. allEntries.ToArray(),
  338. sourcesForFocus.ToArray(),
  339. loadsForFocus.ToArray()));
  340. }
  341. private PowerStats GetPowerStats(EntityUid uid, PowerMonitoringDeviceComponent device)
  342. {
  343. var stats = new PowerStats();
  344. if (device.Group == PowerMonitoringConsoleGroup.Generator)
  345. {
  346. // This covers most power sources
  347. if (TryComp<PowerSupplierComponent>(uid, out var supplier))
  348. {
  349. stats.PowerValue = supplier.CurrentSupply;
  350. stats.PowerSupplied += stats.PowerValue;
  351. }
  352. // Edge case: radiation collectors
  353. else if (TryComp<BatteryDischargerComponent>(uid, out var _) &&
  354. TryComp<PowerNetworkBatteryComponent>(uid, out var battery))
  355. {
  356. stats.PowerValue = battery.NetworkBattery.CurrentSupply;
  357. stats.PowerSupplied += stats.PowerValue;
  358. stats.BatteryLevel = GetBatteryLevel(uid);
  359. }
  360. }
  361. else if (device.Group == PowerMonitoringConsoleGroup.SMES ||
  362. device.Group == PowerMonitoringConsoleGroup.Substation ||
  363. device.Group == PowerMonitoringConsoleGroup.APC)
  364. {
  365. if (TryComp<PowerNetworkBatteryComponent>(uid, out var battery))
  366. {
  367. stats.BatteryLevel = GetBatteryLevel(uid);
  368. stats.PowerValue = battery.CurrentSupply;
  369. // Load due to network battery recharging
  370. stats.PowerUsage += Math.Max(battery.CurrentReceiving - battery.CurrentSupply, 0d);
  371. // Track battery usage
  372. stats.BatteryUsage += Math.Max(battery.CurrentSupply - battery.CurrentReceiving, 0d);
  373. // Records loads attached to APCs
  374. if (device.Group == PowerMonitoringConsoleGroup.APC && battery.Enabled)
  375. {
  376. stats.PowerUsage += battery.NetworkBattery.LoadingNetworkDemand;
  377. }
  378. }
  379. }
  380. // Master devices add the power values from all entities they represent (if applicable)
  381. if (device.IsCollectionMasterOrChild && device.IsCollectionMaster)
  382. {
  383. foreach ((var child, var childDevice) in device.ChildDevices)
  384. {
  385. if (child == uid)
  386. continue;
  387. // Safeguard to prevent infinite loops
  388. if (childDevice.IsCollectionMaster && childDevice.ChildDevices.ContainsKey(uid))
  389. continue;
  390. var childResult = GetPowerStats(child, childDevice);
  391. stats.PowerValue += childResult.PowerValue;
  392. stats.PowerSupplied += childResult.PowerSupplied;
  393. stats.PowerUsage += childResult.PowerUsage;
  394. stats.BatteryUsage += childResult.BatteryUsage;
  395. }
  396. }
  397. return stats;
  398. }
  399. private float? GetBatteryLevel(EntityUid uid)
  400. {
  401. if (!TryComp<BatteryComponent>(uid, out var battery))
  402. return null;
  403. var effectiveMax = battery.MaxCharge;
  404. if (effectiveMax == 0)
  405. effectiveMax = 1;
  406. return battery.CurrentCharge / effectiveMax;
  407. }
  408. private void GetSourcesForNode(EntityUid uid, Node node, out List<PowerMonitoringConsoleEntry> sources)
  409. {
  410. sources = new List<PowerMonitoringConsoleEntry>();
  411. if (node.NodeGroup is not PowerNet netQ)
  412. return;
  413. var indexedSources = new Dictionary<EntityUid, PowerMonitoringConsoleEntry>();
  414. var currentSupply = 0f;
  415. var currentDemand = 0f;
  416. foreach (var powerSupplier in netQ.Suppliers)
  417. {
  418. var ent = powerSupplier.Owner;
  419. if (uid == ent)
  420. continue;
  421. currentSupply += powerSupplier.CurrentSupply;
  422. if (TryComp<PowerMonitoringDeviceComponent>(ent, out var entDevice))
  423. {
  424. // Combine entities represented by an master into a single entry
  425. if (entDevice.IsCollectionMasterOrChild && !entDevice.IsCollectionMaster)
  426. ent = entDevice.CollectionMaster;
  427. if (indexedSources.TryGetValue(ent, out var entry))
  428. {
  429. entry.PowerValue += powerSupplier.CurrentSupply;
  430. indexedSources[ent] = entry;
  431. continue;
  432. }
  433. indexedSources.Add(ent, new PowerMonitoringConsoleEntry(EntityManager.GetNetEntity(ent), entDevice.Group, powerSupplier.CurrentSupply, GetBatteryLevel(ent)));
  434. }
  435. }
  436. foreach (var batteryDischarger in netQ.Dischargers)
  437. {
  438. var ent = batteryDischarger.Owner;
  439. if (uid == ent)
  440. continue;
  441. if (!TryComp<PowerNetworkBatteryComponent>(ent, out var entBattery))
  442. continue;
  443. currentSupply += entBattery.CurrentSupply;
  444. if (TryComp<PowerMonitoringDeviceComponent>(ent, out var entDevice))
  445. {
  446. // Combine entities represented by an master into a single entry
  447. if (entDevice.IsCollectionMasterOrChild && !entDevice.IsCollectionMaster)
  448. ent = entDevice.CollectionMaster;
  449. if (indexedSources.TryGetValue(ent, out var entry))
  450. {
  451. entry.PowerValue += entBattery.CurrentSupply;
  452. indexedSources[ent] = entry;
  453. continue;
  454. }
  455. indexedSources.Add(ent, new PowerMonitoringConsoleEntry(EntityManager.GetNetEntity(ent), entDevice.Group, entBattery.CurrentSupply, GetBatteryLevel(ent)));
  456. }
  457. }
  458. sources = indexedSources.Values.ToList();
  459. // Get the total demand for the network
  460. foreach (var powerConsumer in netQ.Consumers)
  461. {
  462. currentDemand += powerConsumer.ReceivedPower;
  463. }
  464. foreach (var batteryCharger in netQ.Chargers)
  465. {
  466. var ent = batteryCharger.Owner;
  467. if (!TryComp<PowerNetworkBatteryComponent>(ent, out var entBattery))
  468. continue;
  469. currentDemand += entBattery.CurrentReceiving;
  470. }
  471. // Exit if supply / demand is negligible
  472. if (MathHelper.CloseTo(currentDemand, 0) || MathHelper.CloseTo(currentSupply, 0))
  473. return;
  474. // Work out how much power this device (and those it represents) is actually receiving
  475. if (!TryComp<PowerNetworkBatteryComponent>(uid, out var battery))
  476. return;
  477. var powerUsage = battery.CurrentReceiving;
  478. if (TryComp<PowerMonitoringDeviceComponent>(uid, out var device) && device.IsCollectionMaster)
  479. {
  480. foreach ((var child, var _) in device.ChildDevices)
  481. {
  482. if (TryComp<PowerNetworkBatteryComponent>(child, out var childBattery))
  483. powerUsage += childBattery.CurrentReceiving;
  484. }
  485. }
  486. // Update the power value for each source based on the fraction of power the entity is actually draining from each
  487. var powerFraction = Math.Min(powerUsage / currentSupply, 1f) * Math.Min(currentSupply / currentDemand, 1f);
  488. for (int i = 0; i < sources.Count; i++)
  489. {
  490. var entry = sources[i];
  491. sources[i] = new PowerMonitoringConsoleEntry(entry.NetEntity, entry.Group, entry.PowerValue * powerFraction, entry.BatteryLevel);
  492. }
  493. }
  494. private void GetLoadsForNode(EntityUid uid, Node node, out List<PowerMonitoringConsoleEntry> loads, List<EntityUid>? children = null)
  495. {
  496. loads = new List<PowerMonitoringConsoleEntry>();
  497. if (node.NodeGroup is not PowerNet netQ)
  498. return;
  499. var indexedLoads = new Dictionary<EntityUid, PowerMonitoringConsoleEntry>();
  500. var currentDemand = 0f;
  501. foreach (var powerConsumer in netQ.Consumers)
  502. {
  503. var ent = powerConsumer.Owner;
  504. if (uid == ent)
  505. continue;
  506. currentDemand += powerConsumer.ReceivedPower;
  507. if (TryComp<PowerMonitoringDeviceComponent>(ent, out var entDevice))
  508. {
  509. // Combine entities represented by an master into a single entry
  510. if (entDevice.IsCollectionMasterOrChild && !entDevice.IsCollectionMaster)
  511. ent = entDevice.CollectionMaster;
  512. if (indexedLoads.TryGetValue(ent, out var entry))
  513. {
  514. entry.PowerValue += powerConsumer.ReceivedPower;
  515. indexedLoads[ent] = entry;
  516. continue;
  517. }
  518. indexedLoads.Add(ent, new PowerMonitoringConsoleEntry(EntityManager.GetNetEntity(ent), entDevice.Group, powerConsumer.ReceivedPower, GetBatteryLevel(ent)));
  519. }
  520. }
  521. foreach (var batteryCharger in netQ.Chargers)
  522. {
  523. var ent = batteryCharger.Owner;
  524. if (uid == ent)
  525. continue;
  526. if (!TryComp<PowerNetworkBatteryComponent>(ent, out var battery))
  527. continue;
  528. currentDemand += battery.CurrentReceiving;
  529. if (TryComp<PowerMonitoringDeviceComponent>(ent, out var entDevice))
  530. {
  531. // Combine entities represented by an master into a single entry
  532. if (entDevice.IsCollectionMasterOrChild && !entDevice.IsCollectionMaster)
  533. ent = entDevice.CollectionMaster;
  534. if (indexedLoads.TryGetValue(ent, out var entry))
  535. {
  536. entry.PowerValue += battery.CurrentReceiving;
  537. indexedLoads[ent] = entry;
  538. continue;
  539. }
  540. indexedLoads.Add(ent, new PowerMonitoringConsoleEntry(EntityManager.GetNetEntity(ent), entDevice.Group, battery.CurrentReceiving, GetBatteryLevel(ent)));
  541. }
  542. }
  543. loads = indexedLoads.Values.ToList();
  544. // Exit if demand is negligible
  545. if (MathHelper.CloseTo(currentDemand, 0))
  546. return;
  547. var supplying = 0f;
  548. // Work out how much power this device (and those it represents) is actually supplying
  549. if (TryComp<PowerNetworkBatteryComponent>(uid, out var entBattery))
  550. supplying = entBattery.CurrentSupply;
  551. else if (TryComp<PowerSupplierComponent>(uid, out var entSupplier))
  552. supplying = entSupplier.CurrentSupply;
  553. if (TryComp<PowerMonitoringDeviceComponent>(uid, out var device) && device.IsCollectionMaster)
  554. {
  555. foreach ((var child, var _) in device.ChildDevices)
  556. {
  557. if (TryComp<PowerNetworkBatteryComponent>(child, out var childBattery))
  558. supplying += childBattery.CurrentSupply;
  559. else if (TryComp<PowerSupplierComponent>(child, out var childSupplier))
  560. supplying += childSupplier.CurrentSupply;
  561. }
  562. }
  563. // Update the power value for each load based on the fraction of power these entities are actually draining from this device
  564. var powerFraction = Math.Min(supplying / currentDemand, 1f);
  565. for (int i = 0; i < indexedLoads.Values.Count; i++)
  566. {
  567. var entry = loads[i];
  568. loads[i] = new PowerMonitoringConsoleEntry(entry.NetEntity, entry.Group, entry.PowerValue * powerFraction, entry.BatteryLevel);
  569. }
  570. }
  571. // Designates a supplied entity as a 'collection master'. Other entities which share this
  572. // entities collection name and are attached on the same load network are assigned this entity
  573. // as the master that represents them on the console UI. This way you can have one device
  574. // represent multiple connected devices
  575. private void AssignEntityAsCollectionMaster
  576. (EntityUid uid,
  577. PowerMonitoringDeviceComponent? device = null,
  578. TransformComponent? xform = null,
  579. NodeContainerComponent? nodeContainer = null)
  580. {
  581. if (!Resolve(uid, ref device, ref nodeContainer, ref xform, false))
  582. return;
  583. // If the device is not attached to a network, exit
  584. var nodeName = device.SourceNode == string.Empty ? device.LoadNode : device.SourceNode;
  585. if (!nodeContainer.Nodes.TryGetValue(nodeName, out var node) ||
  586. node.ReachableNodes.Count == 0)
  587. {
  588. // Make a child the new master of the collection if necessary
  589. if (device.ChildDevices.TryFirstOrNull(out var kvp))
  590. {
  591. var newMaster = kvp.Value.Key;
  592. var newMasterDevice = kvp.Value.Value;
  593. newMasterDevice.CollectionMaster = newMaster;
  594. newMasterDevice.ChildDevices.Clear();
  595. foreach ((var child, var childDevice) in device.ChildDevices)
  596. {
  597. newMasterDevice.ChildDevices.Add(child, childDevice);
  598. childDevice.CollectionMaster = newMaster;
  599. UpdateCollectionChildMetaData(child, newMaster);
  600. }
  601. UpdateCollectionMasterMetaData(newMaster, newMasterDevice.ChildDevices.Count);
  602. }
  603. device.CollectionMaster = uid;
  604. device.ChildDevices.Clear();
  605. UpdateCollectionMasterMetaData(uid, 0);
  606. return;
  607. }
  608. // Check to see if the device has a valid existing master
  609. if (!device.IsCollectionMaster &&
  610. device.CollectionMaster.IsValid() &&
  611. TryComp<NodeContainerComponent>(device.CollectionMaster, out var masterNodeContainer) &&
  612. DevicesHaveMatchingNodes(nodeContainer, masterNodeContainer))
  613. return;
  614. // If not, make this a new master
  615. device.CollectionMaster = uid;
  616. device.ChildDevices.Clear();
  617. // Search for children
  618. var query = AllEntityQuery<PowerMonitoringDeviceComponent, TransformComponent, NodeContainerComponent>();
  619. while (query.MoveNext(out var ent, out var entDevice, out var entXform, out var entNodeContainer))
  620. {
  621. if (entDevice.CollectionName != device.CollectionName)
  622. continue;
  623. if (ent == uid)
  624. continue;
  625. if (entXform.GridUid != xform.GridUid)
  626. continue;
  627. if (!DevicesHaveMatchingNodes(nodeContainer, entNodeContainer))
  628. continue;
  629. device.ChildDevices.Add(ent, entDevice);
  630. entDevice.CollectionMaster = uid;
  631. UpdateCollectionChildMetaData(ent, uid);
  632. }
  633. UpdateCollectionMasterMetaData(uid, device.ChildDevices.Count);
  634. }
  635. private bool DevicesHaveMatchingNodes(NodeContainerComponent nodeContainerA, NodeContainerComponent nodeContainerB)
  636. {
  637. foreach ((var key, var nodeA) in nodeContainerA.Nodes)
  638. {
  639. if (!nodeContainerB.Nodes.TryGetValue(key, out var nodeB))
  640. return false;
  641. if (nodeA.NodeGroup != nodeB.NodeGroup)
  642. return false;
  643. }
  644. return true;
  645. }
  646. private void UpdateCollectionChildMetaData(EntityUid child, EntityUid master)
  647. {
  648. var netEntity = EntityManager.GetNetEntity(child);
  649. var xform = Transform(child);
  650. var query = AllEntityQuery<PowerMonitoringConsoleComponent, TransformComponent>();
  651. while (query.MoveNext(out var ent, out var entConsole, out var entXform))
  652. {
  653. if (entXform.GridUid != xform.GridUid)
  654. continue;
  655. if (!entConsole.PowerMonitoringDeviceMetaData.TryGetValue(netEntity, out var metaData))
  656. continue;
  657. metaData.CollectionMaster = EntityManager.GetNetEntity(master);
  658. entConsole.PowerMonitoringDeviceMetaData[netEntity] = metaData;
  659. Dirty(ent, entConsole);
  660. }
  661. }
  662. private void UpdateCollectionMasterMetaData(EntityUid master, int childCount)
  663. {
  664. var netEntity = EntityManager.GetNetEntity(master);
  665. var xform = Transform(master);
  666. var query = AllEntityQuery<PowerMonitoringConsoleComponent, TransformComponent>();
  667. while (query.MoveNext(out var ent, out var entConsole, out var entXform))
  668. {
  669. if (entXform.GridUid != xform.GridUid)
  670. continue;
  671. if (!entConsole.PowerMonitoringDeviceMetaData.TryGetValue(netEntity, out var metaData))
  672. continue;
  673. if (childCount > 0)
  674. {
  675. var name = MetaData(master).EntityPrototype?.Name ?? MetaData(master).EntityName;
  676. metaData.EntityName = Loc.GetString("power-monitoring-window-object-array", ("name", name), ("count", childCount + 1));
  677. }
  678. else
  679. {
  680. metaData.EntityName = MetaData(master).EntityName;
  681. }
  682. metaData.CollectionMaster = null;
  683. entConsole.PowerMonitoringDeviceMetaData[netEntity] = metaData;
  684. Dirty(ent, entConsole);
  685. }
  686. }
  687. private Dictionary<Vector2i, PowerCableChunk> RefreshPowerCableGrid(EntityUid gridUid, MapGridComponent grid)
  688. {
  689. // Clears all chunks for the associated grid
  690. var allChunks = new Dictionary<Vector2i, PowerCableChunk>();
  691. _gridPowerCableChunks[gridUid] = allChunks;
  692. // Adds all power cables to the grid
  693. var query = AllEntityQuery<CableComponent, TransformComponent>();
  694. while (query.MoveNext(out var ent, out var cable, out var entXform))
  695. {
  696. if (entXform.GridUid != gridUid)
  697. continue;
  698. var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates);
  699. var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize);
  700. if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
  701. {
  702. chunk = new PowerCableChunk(chunkOrigin);
  703. allChunks[chunkOrigin] = chunk;
  704. }
  705. var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize);
  706. var flag = GetFlag(relative);
  707. chunk.PowerCableData[(int) cable.CableType] |= flag;
  708. }
  709. return allChunks;
  710. }
  711. private void UpdateFocusNetwork(EntityUid uid, PowerMonitoringCableNetworksComponent component, EntityUid gridUid, MapGridComponent grid, List<EntityUid> nodeList)
  712. {
  713. component.FocusChunks.Clear();
  714. foreach (var ent in nodeList)
  715. {
  716. var xform = Transform(ent);
  717. var tile = _sharedMapSystem.GetTileRef(gridUid, grid, xform.Coordinates);
  718. var gridIndices = tile.GridIndices;
  719. var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, ChunkSize);
  720. if (!component.FocusChunks.TryGetValue(chunkOrigin, out var chunk))
  721. {
  722. chunk = new PowerCableChunk(chunkOrigin);
  723. component.FocusChunks[chunkOrigin] = chunk;
  724. }
  725. var relative = SharedMapSystem.GetChunkRelative(gridIndices, ChunkSize);
  726. var flag = GetFlag(relative);
  727. if (TryComp<CableComponent>(ent, out var cable))
  728. chunk.PowerCableData[(int) cable.CableType] |= flag;
  729. }
  730. Dirty(uid, component);
  731. }
  732. private void RefreshPowerMonitoringConsole(EntityUid uid, PowerMonitoringConsoleComponent component)
  733. {
  734. component.Focus = null;
  735. component.FocusGroup = PowerMonitoringConsoleGroup.Generator;
  736. component.PowerMonitoringDeviceMetaData.Clear();
  737. component.Flags = 0;
  738. var xform = Transform(uid);
  739. if (xform.GridUid == null)
  740. return;
  741. var grid = xform.GridUid.Value;
  742. var query = AllEntityQuery<PowerMonitoringDeviceComponent, TransformComponent>();
  743. while (query.MoveNext(out var ent, out var entDevice, out var entXform))
  744. {
  745. if (grid != entXform.GridUid)
  746. continue;
  747. var netEntity = EntityManager.GetNetEntity(ent);
  748. var name = MetaData(ent).EntityName;
  749. var netCoords = EntityManager.GetNetCoordinates(entXform.Coordinates);
  750. var metaData = new PowerMonitoringDeviceMetaData(name, netCoords, entDevice.Group, entDevice.SpritePath, entDevice.SpriteState);
  751. if (entDevice.IsCollectionMasterOrChild)
  752. {
  753. if (!entDevice.IsCollectionMaster)
  754. {
  755. metaData.CollectionMaster = EntityManager.GetNetEntity(entDevice.CollectionMaster);
  756. }
  757. else if (entDevice.ChildDevices.Count > 0)
  758. {
  759. name = MetaData(ent).EntityPrototype?.Name ?? MetaData(ent).EntityName;
  760. metaData.EntityName = Loc.GetString("power-monitoring-window-object-array", ("name", name), ("count", entDevice.ChildDevices.Count + 1));
  761. }
  762. }
  763. component.PowerMonitoringDeviceMetaData.Add(netEntity, metaData);
  764. }
  765. Dirty(uid, component);
  766. }
  767. private void RefreshPowerMonitoringCableNetworks(EntityUid uid, PowerMonitoringCableNetworksComponent component)
  768. {
  769. var xform = Transform(uid);
  770. if (xform.GridUid == null)
  771. return;
  772. var grid = xform.GridUid.Value;
  773. if (!TryComp<MapGridComponent>(grid, out var map))
  774. return;
  775. if (!_gridPowerCableChunks.TryGetValue(grid, out var allChunks))
  776. allChunks = RefreshPowerCableGrid(grid, map);
  777. component.AllChunks = allChunks;
  778. component.FocusChunks.Clear();
  779. Dirty(uid, component);
  780. }
  781. private struct PowerStats
  782. {
  783. public double PowerValue { get; set; }
  784. public double PowerSupplied { get; set; }
  785. public double PowerUsage { get; set; }
  786. public double BatteryUsage { get; set; }
  787. public float? BatteryLevel { get; set; }
  788. }
  789. }