AirAlarmSystem.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. using Content.Server.Atmos.Monitor.Components;
  2. using Content.Server.Atmos.Piping.Components;
  3. using Content.Server.DeviceLinking.Systems;
  4. using Content.Server.DeviceNetwork;
  5. using Content.Server.DeviceNetwork.Components;
  6. using Content.Server.DeviceNetwork.Systems;
  7. using Content.Server.Popups;
  8. using Content.Server.Power.Components;
  9. using Content.Server.Power.EntitySystems;
  10. using Content.Shared.Access.Components;
  11. using Content.Shared.Access.Systems;
  12. using Content.Shared.Administration.Logs;
  13. using Content.Shared.Atmos;
  14. using Content.Shared.Atmos.Monitor;
  15. using Content.Shared.Atmos.Monitor.Components;
  16. using Content.Shared.Atmos.Piping.Unary.Components;
  17. using Content.Shared.Database;
  18. using Content.Shared.DeviceLinking;
  19. using Content.Shared.DeviceNetwork;
  20. using Content.Shared.DeviceNetwork.Systems;
  21. using Content.Shared.Interaction;
  22. using Content.Shared.Power;
  23. using Content.Shared.Wires;
  24. using Robust.Server.GameObjects;
  25. using Robust.Shared.Player;
  26. using System.Linq;
  27. namespace Content.Server.Atmos.Monitor.Systems;
  28. // AirAlarm system - specific for atmos devices, rather than
  29. // atmos monitors.
  30. //
  31. // oh boy, message passing!
  32. //
  33. // Commands should always be sent into packet's Command
  34. // data key. In response, a packet will be transmitted
  35. // with the response type as its command, and the
  36. // response data in its data key.
  37. public sealed class AirAlarmSystem : EntitySystem
  38. {
  39. [Dependency] private readonly AccessReaderSystem _access = default!;
  40. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  41. [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
  42. [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
  43. [Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
  44. [Dependency] private readonly DeviceLinkSystem _deviceLink = default!;
  45. [Dependency] private readonly DeviceListSystem _deviceList = default!;
  46. [Dependency] private readonly PopupSystem _popup = default!;
  47. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  48. #region Device Network API
  49. /// <summary>
  50. /// Command to set an air alarm's mode.
  51. /// </summary>
  52. public const string AirAlarmSetMode = "air_alarm_set_mode";
  53. // -- API --
  54. /// <summary>
  55. /// Set the data for an air alarm managed device.
  56. /// </summary>
  57. /// <param name="address">The address of the device.</param>
  58. /// <param name="data">The data to send to the device.</param>
  59. public void SetData(EntityUid uid, string address, IAtmosDeviceData data)
  60. {
  61. _atmosDevNet.SetDeviceState(uid, address, data);
  62. _atmosDevNet.Sync(uid, address);
  63. }
  64. /// <summary>
  65. /// Broadcast a sync packet to an air alarm's local network.
  66. /// </summary>
  67. private void SyncAllDevices(EntityUid uid)
  68. {
  69. _atmosDevNet.Sync(uid, null);
  70. }
  71. /// <summary>
  72. /// Send a sync packet to a specific device from an air alarm.
  73. /// </summary>
  74. /// <param name="address">The address of the device.</param>
  75. private void SyncDevice(EntityUid uid, string address)
  76. {
  77. _atmosDevNet.Sync(uid, address);
  78. }
  79. /// <summary>
  80. /// Register and synchronize with all devices
  81. /// on this network.
  82. /// </summary>
  83. /// <param name="uid"></param>
  84. private void SyncRegisterAllDevices(EntityUid uid)
  85. {
  86. _atmosDevNet.Register(uid, null);
  87. _atmosDevNet.Sync(uid, null);
  88. }
  89. /// <summary>
  90. /// Synchronize all sensors on an air alarm, but only if its current tab is set to Sensors.
  91. /// </summary>
  92. /// <param name="uid"></param>
  93. /// <param name="monitor"></param>
  94. private void SyncAllSensors(EntityUid uid, AirAlarmComponent? monitor = null)
  95. {
  96. if (!Resolve(uid, ref monitor))
  97. {
  98. return;
  99. }
  100. foreach (var addr in monitor.SensorData.Keys)
  101. {
  102. SyncDevice(uid, addr);
  103. }
  104. }
  105. private void SetThreshold(EntityUid uid, string address, AtmosMonitorThresholdType type,
  106. AtmosAlarmThreshold threshold, Gas? gas = null)
  107. {
  108. var payload = new NetworkPayload
  109. {
  110. [DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetThresholdCmd,
  111. [AtmosMonitorSystem.AtmosMonitorThresholdDataType] = type,
  112. [AtmosMonitorSystem.AtmosMonitorThresholdData] = threshold,
  113. };
  114. if (gas != null)
  115. {
  116. payload.Add(AtmosMonitorSystem.AtmosMonitorThresholdGasType, gas);
  117. }
  118. _deviceNet.QueuePacket(uid, address, payload);
  119. SyncDevice(uid, address);
  120. }
  121. private void SetAllThresholds(EntityUid uid, string address, AtmosSensorData data)
  122. {
  123. var payload = new NetworkPayload
  124. {
  125. [DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetAllThresholdsCmd,
  126. [AtmosMonitorSystem.AtmosMonitorAllThresholdData] = data
  127. };
  128. _deviceNet.QueuePacket(uid, address, payload);
  129. SyncDevice(uid, address);
  130. }
  131. /// <summary>
  132. /// Sync this air alarm's mode with the rest of the network.
  133. /// </summary>
  134. /// <param name="mode">The mode to sync with the rest of the network.</param>
  135. private void SyncMode(EntityUid uid, AirAlarmMode mode)
  136. {
  137. if (TryComp<AtmosMonitorComponent>(uid, out var monitor) && !monitor.NetEnabled)
  138. return;
  139. var payload = new NetworkPayload
  140. {
  141. [DeviceNetworkConstants.Command] = AirAlarmSetMode,
  142. [AirAlarmSetMode] = mode
  143. };
  144. _deviceNet.QueuePacket(uid, null, payload);
  145. }
  146. #endregion
  147. #region Events
  148. public override void Initialize()
  149. {
  150. SubscribeLocalEvent<AirAlarmComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
  151. SubscribeLocalEvent<AirAlarmComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
  152. SubscribeLocalEvent<AirAlarmComponent, AtmosAlarmEvent>(OnAtmosAlarm);
  153. SubscribeLocalEvent<AirAlarmComponent, PowerChangedEvent>(OnPowerChanged);
  154. SubscribeLocalEvent<AirAlarmComponent, DeviceListUpdateEvent>(OnDeviceListUpdate);
  155. SubscribeLocalEvent<AirAlarmComponent, ComponentInit>(OnInit);
  156. SubscribeLocalEvent<AirAlarmComponent, MapInitEvent>(OnMapInit);
  157. SubscribeLocalEvent<AirAlarmComponent, ComponentShutdown>(OnShutdown);
  158. SubscribeLocalEvent<AirAlarmComponent, ActivateInWorldEvent>(OnActivate);
  159. Subs.BuiEvents<AirAlarmComponent>(SharedAirAlarmInterfaceKey.Key, subs =>
  160. {
  161. subs.Event<BoundUIClosedEvent>(OnClose);
  162. subs.Event<AirAlarmResyncAllDevicesMessage>(OnResyncAll);
  163. subs.Event<AirAlarmUpdateAlarmModeMessage>(OnUpdateAlarmMode);
  164. subs.Event<AirAlarmUpdateAutoModeMessage>(OnUpdateAutoMode);
  165. subs.Event<AirAlarmUpdateAlarmThresholdMessage>(OnUpdateThreshold);
  166. subs.Event<AirAlarmUpdateDeviceDataMessage>(OnUpdateDeviceData);
  167. subs.Event<AirAlarmCopyDeviceDataMessage>(OnCopyDeviceData);
  168. });
  169. }
  170. private void OnDeviceListUpdate(EntityUid uid, AirAlarmComponent component, DeviceListUpdateEvent args)
  171. {
  172. var query = GetEntityQuery<DeviceNetworkComponent>();
  173. foreach (var device in args.OldDevices)
  174. {
  175. if (!query.TryGetComponent(device, out var deviceNet))
  176. {
  177. continue;
  178. }
  179. _atmosDevNet.Deregister(uid, deviceNet.Address);
  180. }
  181. component.ScrubberData.Clear();
  182. component.SensorData.Clear();
  183. component.VentData.Clear();
  184. component.KnownDevices.Clear();
  185. UpdateUI(uid, component);
  186. SyncRegisterAllDevices(uid);
  187. }
  188. private void OnPowerChanged(EntityUid uid, AirAlarmComponent component, ref PowerChangedEvent args)
  189. {
  190. if (args.Powered)
  191. {
  192. return;
  193. }
  194. ForceCloseAllInterfaces(uid);
  195. component.CurrentModeUpdater = null;
  196. component.KnownDevices.Clear();
  197. component.ScrubberData.Clear();
  198. component.SensorData.Clear();
  199. component.VentData.Clear();
  200. }
  201. private void OnClose(EntityUid uid, AirAlarmComponent component, BoundUIClosedEvent args)
  202. {
  203. if (!_ui.IsUiOpen(uid, SharedAirAlarmInterfaceKey.Key))
  204. RemoveActiveInterface(uid);
  205. }
  206. private void OnInit(EntityUid uid, AirAlarmComponent comp, ComponentInit args)
  207. {
  208. _deviceLink.EnsureSourcePorts(uid, comp.DangerPort, comp.WarningPort, comp.NormalPort);
  209. }
  210. private void OnMapInit(EntityUid uid, AirAlarmComponent comp, MapInitEvent args)
  211. {
  212. // for mapped linked air alarms, start with high so when it changes for the first time it goes from high to low
  213. // without this the output would suddenly get sent a low signal after nothing which is bad
  214. _deviceLink.SendSignal(uid, GetPort(comp), true);
  215. }
  216. private void OnShutdown(EntityUid uid, AirAlarmComponent component, ComponentShutdown args)
  217. {
  218. _activeUserInterfaces.Remove(uid);
  219. }
  220. private void OnActivate(EntityUid uid, AirAlarmComponent component, ActivateInWorldEvent args)
  221. {
  222. if (!args.Complex)
  223. return;
  224. if (TryComp<WiresPanelComponent>(uid, out var panel) && panel.Open)
  225. {
  226. args.Handled = false;
  227. return;
  228. }
  229. if (!this.IsPowered(uid, EntityManager))
  230. return;
  231. _ui.OpenUi(uid, SharedAirAlarmInterfaceKey.Key, args.User);
  232. AddActiveInterface(uid);
  233. SyncAllDevices(uid);
  234. UpdateUI(uid, component);
  235. }
  236. private void OnResyncAll(EntityUid uid, AirAlarmComponent component, AirAlarmResyncAllDevicesMessage args)
  237. {
  238. if (!AccessCheck(uid, args.Actor, component))
  239. {
  240. return;
  241. }
  242. component.KnownDevices.Clear();
  243. component.VentData.Clear();
  244. component.ScrubberData.Clear();
  245. component.SensorData.Clear();
  246. SyncRegisterAllDevices(uid);
  247. }
  248. private void OnUpdateAlarmMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmModeMessage args)
  249. {
  250. if (AccessCheck(uid, args.Actor, component))
  251. {
  252. var addr = string.Empty;
  253. if (TryComp<DeviceNetworkComponent>(uid, out var netConn))
  254. {
  255. addr = netConn.Address;
  256. }
  257. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} mode to {args.Mode}");
  258. SetMode(uid, addr, args.Mode, false);
  259. }
  260. else
  261. {
  262. UpdateUI(uid, component);
  263. }
  264. }
  265. private void OnUpdateAutoMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAutoModeMessage args)
  266. {
  267. component.AutoMode = args.Enabled;
  268. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} auto mode to {args.Enabled}");
  269. UpdateUI(uid, component);
  270. }
  271. private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args)
  272. {
  273. if (AccessCheck(uid, args.Actor, component))
  274. {
  275. if (args.Gas != null)
  276. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Gas} {args.Type} threshold using {ToPrettyString(uid)}");
  277. else
  278. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Type} threshold using {ToPrettyString(uid)}");
  279. SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas);
  280. }
  281. else
  282. {
  283. UpdateUI(uid, component);
  284. }
  285. }
  286. private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args)
  287. {
  288. if (AccessCheck(uid, args.Actor, component)
  289. && _deviceList.ExistsInDeviceList(uid, args.Address))
  290. {
  291. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} settings using {ToPrettyString(uid)}");
  292. SetDeviceData(uid, args.Address, args.Data);
  293. }
  294. else
  295. {
  296. UpdateUI(uid, component);
  297. }
  298. }
  299. private void OnCopyDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmCopyDeviceDataMessage args)
  300. {
  301. if (!AccessCheck(uid, args.Actor, component))
  302. {
  303. UpdateUI(uid, component);
  304. return;
  305. }
  306. switch (args.Data)
  307. {
  308. case GasVentPumpData ventData:
  309. foreach (string addr in component.VentData.Keys)
  310. {
  311. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to vent {addr}");
  312. SetData(uid, addr, args.Data);
  313. }
  314. break;
  315. case GasVentScrubberData scrubberData:
  316. foreach (string addr in component.ScrubberData.Keys)
  317. {
  318. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to scrubber {addr}");
  319. SetData(uid, addr, args.Data);
  320. }
  321. break;
  322. case AtmosSensorData sensorData:
  323. foreach (string addr in component.SensorData.Keys)
  324. {
  325. SetAllThresholds(uid, addr, sensorData);
  326. }
  327. break;
  328. }
  329. }
  330. private bool AccessCheck(EntityUid uid, EntityUid? user, AirAlarmComponent? component = null)
  331. {
  332. if (!Resolve(uid, ref component))
  333. return false;
  334. // if it has no access reader behave as if the user has AA
  335. if (!TryComp<AccessReaderComponent>(uid, out var reader))
  336. return true;
  337. if (user == null)
  338. return false;
  339. if (!_access.IsAllowed(user.Value, uid, reader))
  340. {
  341. _popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, user.Value);
  342. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Low, $"{ToPrettyString(user)} attempted to access {ToPrettyString(uid)} without access");
  343. return false;
  344. }
  345. return true;
  346. }
  347. private void OnAtmosAlarm(EntityUid uid, AirAlarmComponent component, AtmosAlarmEvent args)
  348. {
  349. if (_ui.IsUiOpen(uid, SharedAirAlarmInterfaceKey.Key))
  350. {
  351. SyncAllDevices(uid);
  352. }
  353. var addr = string.Empty;
  354. if (TryComp<DeviceNetworkComponent>(uid, out var netConn))
  355. {
  356. addr = netConn.Address;
  357. }
  358. if (component.AutoMode)
  359. {
  360. if (args.AlarmType == AtmosAlarmType.Danger)
  361. {
  362. SetMode(uid, addr, AirAlarmMode.WideFiltering, false);
  363. }
  364. else if (args.AlarmType == AtmosAlarmType.Normal || args.AlarmType == AtmosAlarmType.Warning)
  365. {
  366. SetMode(uid, addr, AirAlarmMode.Filtering, false);
  367. }
  368. }
  369. if (component.State != args.AlarmType)
  370. {
  371. TryComp<DeviceLinkSourceComponent>(uid, out var source);
  372. // send low to old state's port
  373. _deviceLink.SendSignal(uid, GetPort(component), false, source);
  374. // send high to new state's port, along with updating the cached state
  375. component.State = args.AlarmType;
  376. _deviceLink.SendSignal(uid, GetPort(component), true, source);
  377. }
  378. UpdateUI(uid, component);
  379. }
  380. private string GetPort(AirAlarmComponent comp)
  381. {
  382. if (comp.State == AtmosAlarmType.Danger)
  383. return comp.DangerPort;
  384. if (comp.State == AtmosAlarmType.Warning)
  385. return comp.WarningPort;
  386. return comp.NormalPort;
  387. }
  388. #endregion
  389. #region Air Alarm Settings
  390. /// <summary>
  391. /// Set an air alarm's mode.
  392. /// </summary>
  393. /// <param name="origin">The origin address of this mode set. Used for network sync.</param>
  394. /// <param name="mode">The mode to set the alarm to.</param>
  395. /// <param name="uiOnly">Whether this change is for the UI only, or if it changes the air alarm's operating mode. Defaults to true.</param>
  396. public void SetMode(EntityUid uid, string origin, AirAlarmMode mode, bool uiOnly = true, AirAlarmComponent? controller = null)
  397. {
  398. if (!Resolve(uid, ref controller) || controller.CurrentMode == mode)
  399. {
  400. return;
  401. }
  402. controller.CurrentMode = mode;
  403. // setting it to UI only means we don't have
  404. // to deal with the issue of not-single-owner
  405. // alarm mode executors
  406. if (!uiOnly)
  407. {
  408. var newMode = AirAlarmModeFactory.ModeToExecutor(mode);
  409. if (newMode != null)
  410. {
  411. newMode.Execute(uid);
  412. if (newMode is IAirAlarmModeUpdate updatedMode)
  413. {
  414. controller.CurrentModeUpdater = updatedMode;
  415. controller.CurrentModeUpdater.NetOwner = origin;
  416. }
  417. else if (controller.CurrentModeUpdater != null)
  418. controller.CurrentModeUpdater = null;
  419. }
  420. }
  421. // only one air alarm in a network can use an air alarm mode
  422. // that updates, so even if it's a ui-only change,
  423. // we have to invalidate the last mode's updater and
  424. // remove it because otherwise it'll execute a now
  425. // invalid mode
  426. else if (controller.CurrentModeUpdater != null
  427. && controller.CurrentModeUpdater.NetOwner != origin)
  428. {
  429. controller.CurrentModeUpdater = null;
  430. }
  431. UpdateUI(uid, controller);
  432. // setting sync deals with the issue of air alarms
  433. // in the same network needing to have the same mode
  434. // as other alarms
  435. SyncMode(uid, mode);
  436. }
  437. /// <summary>
  438. /// Sets device data. Practically a wrapper around the packet sending function, SetData.
  439. /// </summary>
  440. /// <param name="address">The address to send the new data to.</param>
  441. /// <param name="devData">The device data to be sent.</param>
  442. private void SetDeviceData(EntityUid uid, string address, IAtmosDeviceData devData, AirAlarmComponent? controller = null)
  443. {
  444. if (!Resolve(uid, ref controller))
  445. {
  446. return;
  447. }
  448. devData.Dirty = true;
  449. SetData(uid, address, devData);
  450. }
  451. private void OnPacketRecv(EntityUid uid, AirAlarmComponent controller, DeviceNetworkPacketEvent args)
  452. {
  453. if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd))
  454. return;
  455. switch (cmd)
  456. {
  457. case AtmosDeviceNetworkSystem.SyncData:
  458. if (!args.Data.TryGetValue(AtmosDeviceNetworkSystem.SyncData, out IAtmosDeviceData? data)
  459. || !controller.CanSync)
  460. break;
  461. // Save into component.
  462. // Sync data to interface.
  463. switch (data)
  464. {
  465. case GasVentPumpData ventData:
  466. if (!controller.VentData.TryAdd(args.SenderAddress, ventData))
  467. controller.VentData[args.SenderAddress] = ventData;
  468. break;
  469. case GasVentScrubberData scrubberData:
  470. if (!controller.ScrubberData.TryAdd(args.SenderAddress, scrubberData))
  471. controller.ScrubberData[args.SenderAddress] = scrubberData;
  472. break;
  473. case AtmosSensorData sensorData:
  474. if (!controller.SensorData.TryAdd(args.SenderAddress, sensorData))
  475. controller.SensorData[args.SenderAddress] = sensorData;
  476. break;
  477. }
  478. controller.KnownDevices.Add(args.SenderAddress);
  479. UpdateUI(uid, controller);
  480. return;
  481. case AirAlarmSetMode:
  482. if (!args.Data.TryGetValue(AirAlarmSetMode, out AirAlarmMode alarmMode))
  483. break;
  484. SetMode(uid, args.SenderAddress, alarmMode, uiOnly: false);
  485. return;
  486. }
  487. }
  488. #endregion
  489. #region UI
  490. // List of active user interfaces.
  491. private readonly HashSet<EntityUid> _activeUserInterfaces = new();
  492. /// <summary>
  493. /// Adds an active interface to be updated.
  494. /// </summary>
  495. private void AddActiveInterface(EntityUid uid)
  496. {
  497. _activeUserInterfaces.Add(uid);
  498. }
  499. /// <summary>
  500. /// Removes an active interface from the system update loop.
  501. /// </summary>
  502. private void RemoveActiveInterface(EntityUid uid)
  503. {
  504. _activeUserInterfaces.Remove(uid);
  505. }
  506. /// <summary>
  507. /// Force closes all interfaces currently open related to this air alarm.
  508. /// </summary>
  509. private void ForceCloseAllInterfaces(EntityUid uid)
  510. {
  511. _ui.CloseUi(uid, SharedAirAlarmInterfaceKey.Key);
  512. }
  513. private void OnAtmosUpdate(EntityUid uid, AirAlarmComponent alarm, ref AtmosDeviceUpdateEvent args)
  514. {
  515. alarm.CurrentModeUpdater?.Update(uid);
  516. }
  517. public float CalculatePressureAverage(AirAlarmComponent alarm)
  518. {
  519. return alarm.SensorData.Count != 0
  520. ? alarm.SensorData.Values.Select(v => v.Pressure).Average()
  521. : 0f;
  522. }
  523. public float CalculateTemperatureAverage(AirAlarmComponent alarm)
  524. {
  525. return alarm.SensorData.Count != 0
  526. ? alarm.SensorData.Values.Select(v => v.Temperature).Average()
  527. : 0f;
  528. }
  529. public float CalculateGasMolarConcentrationAverage(AirAlarmComponent alarm, Gas gas, out float percentage)
  530. {
  531. percentage = 0f;
  532. var data = alarm.SensorData.Values.SelectMany(v => v.Gases.Where(g => g.Key == gas));
  533. if (data.Count() == 0)
  534. return 0f;
  535. var averageMol = data.Select(kvp => kvp.Value).Average();
  536. percentage = data.Select(kvp => kvp.Value).Sum() / alarm.SensorData.Values.Select(v => v.TotalMoles).Sum();
  537. return averageMol;
  538. }
  539. public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetworkComponent? devNet = null, AtmosAlarmableComponent? alarmable = null)
  540. {
  541. if (!Resolve(uid, ref alarm, ref devNet, ref alarmable))
  542. {
  543. return;
  544. }
  545. var pressure = CalculatePressureAverage(alarm);
  546. var temperature = CalculateTemperatureAverage(alarm);
  547. var dataToSend = new List<(string, IAtmosDeviceData)>();
  548. foreach (var (addr, data) in alarm.VentData)
  549. {
  550. dataToSend.Add((addr, data));
  551. }
  552. foreach (var (addr, data) in alarm.ScrubberData)
  553. {
  554. dataToSend.Add((addr, data));
  555. }
  556. foreach (var (addr, data) in alarm.SensorData)
  557. {
  558. dataToSend.Add((addr, data));
  559. }
  560. var deviceCount = alarm.KnownDevices.Count;
  561. if (!_atmosAlarmable.TryGetHighestAlert(uid, out var highestAlarm))
  562. {
  563. highestAlarm = AtmosAlarmType.Normal;
  564. }
  565. _ui.SetUiState(
  566. uid,
  567. SharedAirAlarmInterfaceKey.Key,
  568. new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, highestAlarm.Value, alarm.AutoMode));
  569. }
  570. private const float Delay = 8f;
  571. private float _timer;
  572. public override void Update(float frameTime)
  573. {
  574. _timer += frameTime;
  575. if (_timer >= Delay)
  576. {
  577. _timer = 0f;
  578. foreach (var uid in _activeUserInterfaces)
  579. {
  580. SyncAllSensors(uid);
  581. }
  582. }
  583. }
  584. #endregion
  585. }