AtmosAlarmableSystem.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using Content.Server.Atmos.Monitor.Components;
  4. using Content.Server.DeviceNetwork;
  5. using Content.Server.DeviceNetwork.Components;
  6. using Content.Server.DeviceNetwork.Systems;
  7. using Content.Server.Power.Components;
  8. using Content.Shared.Atmos.Monitor;
  9. using Content.Shared.DeviceNetwork;
  10. using Content.Shared.Power;
  11. using Content.Shared.Tag;
  12. using Robust.Server.Audio;
  13. using Robust.Server.GameObjects;
  14. using Robust.Shared.Audio;
  15. using Robust.Shared.Prototypes;
  16. using Robust.Shared.Utility;
  17. namespace Content.Server.Atmos.Monitor.Systems;
  18. public sealed class AtmosAlarmableSystem : EntitySystem
  19. {
  20. [Dependency] private readonly AppearanceSystem _appearance = default!;
  21. [Dependency] private readonly AudioSystem _audioSystem = default!;
  22. [Dependency] private readonly DeviceNetworkSystem _deviceNet = default!;
  23. [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNetSystem = default!;
  24. /// <summary>
  25. /// An alarm. Has three valid states: Normal, Warning, Danger.
  26. /// Will attempt to fetch the tags from the alarming entity
  27. /// to send over.
  28. /// </summary>
  29. public const string AlertCmd = "atmos_alarm";
  30. public const string AlertSource = "atmos_alarm_source";
  31. public const string AlertTypes = "atmos_alarm_types";
  32. /// <summary>
  33. /// Syncs alerts from this alarm receiver to other alarm receivers.
  34. /// Creates a network effect as a result. Note: if the alert receiver
  35. /// is not aware of the device beforehand, it will not sync.
  36. /// </summary>
  37. public const string SyncAlerts = "atmos_alarmable_sync_alerts";
  38. public const string ResetAll = "atmos_alarmable_reset_all";
  39. public override void Initialize()
  40. {
  41. SubscribeLocalEvent<AtmosAlarmableComponent, MapInitEvent>(OnMapInit);
  42. SubscribeLocalEvent<AtmosAlarmableComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
  43. SubscribeLocalEvent<AtmosAlarmableComponent, PowerChangedEvent>(OnPowerChange);
  44. }
  45. private void OnMapInit(EntityUid uid, AtmosAlarmableComponent component, MapInitEvent args)
  46. {
  47. TryUpdateAlert(
  48. uid,
  49. TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosAlarmType.Normal,
  50. component,
  51. false);
  52. }
  53. private void OnPowerChange(EntityUid uid, AtmosAlarmableComponent component, ref PowerChangedEvent args)
  54. {
  55. if (!args.Powered)
  56. {
  57. Reset(uid, component);
  58. }
  59. else
  60. {
  61. // sussy
  62. _atmosDevNetSystem.Register(uid, null);
  63. _atmosDevNetSystem.Sync(uid, null);
  64. TryUpdateAlert(
  65. uid,
  66. TryGetHighestAlert(uid, out var alarm) ? alarm.Value : AtmosAlarmType.Normal,
  67. component,
  68. false);
  69. }
  70. }
  71. private void OnPacketRecv(EntityUid uid, AtmosAlarmableComponent component, DeviceNetworkPacketEvent args)
  72. {
  73. if (component.IgnoreAlarms) return;
  74. if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn))
  75. return;
  76. if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? cmd)
  77. || !args.Data.TryGetValue(AlertSource, out HashSet<ProtoId<TagPrototype>>? sourceTags))
  78. {
  79. return;
  80. }
  81. var isValid = sourceTags.Any(source => component.SyncWithTags.Contains(source));
  82. if (!isValid)
  83. {
  84. return;
  85. }
  86. switch (cmd)
  87. {
  88. case AlertCmd:
  89. // Set the alert state, and then cache it so we can calculate
  90. // the maximum alarm state at all times.
  91. if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out AtmosAlarmType state))
  92. {
  93. break;
  94. }
  95. if (args.Data.TryGetValue(AlertTypes, out HashSet<AtmosMonitorThresholdType>? types) && component.MonitorAlertTypes != null)
  96. {
  97. isValid = types.Any(type => component.MonitorAlertTypes.Contains(type));
  98. }
  99. if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress))
  100. {
  101. if (!isValid)
  102. {
  103. break;
  104. }
  105. component.NetworkAlarmStates.Add(args.SenderAddress, state);
  106. }
  107. else
  108. {
  109. // This is because if the alert is no longer valid,
  110. // it may mean that the threshold we need to look at has
  111. // been removed from the threshold types passed:
  112. // basically, we need to reset this state to normal here.
  113. component.NetworkAlarmStates[args.SenderAddress] = isValid ? state : AtmosAlarmType.Normal;
  114. }
  115. if (!TryGetHighestAlert(uid, out var netMax, component))
  116. {
  117. netMax = AtmosAlarmType.Normal;
  118. }
  119. TryUpdateAlert(uid, netMax.Value, component);
  120. break;
  121. case ResetAll:
  122. Reset(uid, component);
  123. break;
  124. case SyncAlerts:
  125. if (!args.Data.TryGetValue(SyncAlerts,
  126. out IReadOnlyDictionary<string, AtmosAlarmType>? alarms))
  127. {
  128. break;
  129. }
  130. foreach (var (key, alarm) in alarms)
  131. {
  132. if (!component.NetworkAlarmStates.TryAdd(key, alarm))
  133. {
  134. component.NetworkAlarmStates[key] = alarm;
  135. }
  136. }
  137. if (TryGetHighestAlert(uid, out var maxAlert, component))
  138. {
  139. TryUpdateAlert(uid, maxAlert.Value, component);
  140. }
  141. break;
  142. }
  143. }
  144. private void TryUpdateAlert(EntityUid uid, AtmosAlarmType type, AtmosAlarmableComponent alarmable, bool sync = true)
  145. {
  146. if (alarmable.LastAlarmState == type)
  147. {
  148. return;
  149. }
  150. if (sync)
  151. {
  152. SyncAlertsToNetwork(uid, null, alarmable);
  153. }
  154. alarmable.LastAlarmState = type;
  155. UpdateAppearance(uid, type);
  156. PlayAlertSound(uid, type, alarmable);
  157. RaiseLocalEvent(uid, new AtmosAlarmEvent(type), true);
  158. }
  159. public void SyncAlertsToNetwork(EntityUid uid, string? address = null, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
  160. {
  161. if (!Resolve(uid, ref alarmable, ref tags) || alarmable.ReceiveOnly)
  162. {
  163. return;
  164. }
  165. var payload = new NetworkPayload
  166. {
  167. [DeviceNetworkConstants.Command] = SyncAlerts,
  168. [SyncAlerts] = alarmable.NetworkAlarmStates,
  169. [AlertSource] = tags.Tags
  170. };
  171. _deviceNet.QueuePacket(uid, address, payload);
  172. }
  173. /// <summary>
  174. /// Forces this alarmable to have a specific alert. This will not be reset until the alarmable
  175. /// is manually reset. This will store the alarmable as a device in its network states.
  176. /// </summary>
  177. /// <param name="uid"></param>
  178. /// <param name="alarmType"></param>
  179. /// <param name="alarmable"></param>
  180. public void ForceAlert(EntityUid uid, AtmosAlarmType alarmType,
  181. AtmosAlarmableComponent? alarmable = null, DeviceNetworkComponent? devNet = null, TagComponent? tags = null)
  182. {
  183. if (!Resolve(uid, ref alarmable, ref devNet, ref tags))
  184. {
  185. return;
  186. }
  187. TryUpdateAlert(uid, alarmType, alarmable, false);
  188. if (alarmable.ReceiveOnly)
  189. {
  190. return;
  191. }
  192. if (!alarmable.NetworkAlarmStates.TryAdd(devNet.Address, alarmType))
  193. {
  194. alarmable.NetworkAlarmStates[devNet.Address] = alarmType;
  195. }
  196. var payload = new NetworkPayload
  197. {
  198. [DeviceNetworkConstants.Command] = AlertCmd,
  199. [DeviceNetworkConstants.CmdSetState] = alarmType,
  200. [AlertSource] = tags.Tags
  201. };
  202. _deviceNet.QueuePacket(uid, null, payload);
  203. }
  204. /// <summary>
  205. /// Resets the state of this alarmable to normal.
  206. /// </summary>
  207. /// <param name="uid"></param>
  208. /// <param name="alarmable"></param>
  209. public void Reset(EntityUid uid, AtmosAlarmableComponent? alarmable = null, TagComponent? tags = null)
  210. {
  211. if (!Resolve(uid, ref alarmable, ref tags, false) || alarmable.LastAlarmState == AtmosAlarmType.Normal)
  212. {
  213. return;
  214. }
  215. alarmable.NetworkAlarmStates.Clear();
  216. TryUpdateAlert(uid, AtmosAlarmType.Normal, alarmable);
  217. if (!alarmable.ReceiveOnly)
  218. {
  219. var payload = new NetworkPayload
  220. {
  221. [DeviceNetworkConstants.Command] = ResetAll,
  222. [AlertSource] = tags.Tags
  223. };
  224. _deviceNet.QueuePacket(uid, null, payload);
  225. }
  226. }
  227. public void ResetAllOnNetwork(EntityUid uid, AtmosAlarmableComponent? alarmable = null)
  228. {
  229. if (!Resolve(uid, ref alarmable) || alarmable.ReceiveOnly)
  230. {
  231. return;
  232. }
  233. Reset(uid, alarmable);
  234. }
  235. /// <summary>
  236. /// Tries to get the highest possible alert stored in this alarm.
  237. /// </summary>
  238. /// <param name="uid"></param>
  239. /// <param name="alarm"></param>
  240. /// <param name="alarmable"></param>
  241. /// <returns></returns>
  242. public bool TryGetHighestAlert(EntityUid uid, [NotNullWhen(true)] out AtmosAlarmType? alarm,
  243. AtmosAlarmableComponent? alarmable = null)
  244. {
  245. alarm = null;
  246. if (!Resolve(uid, ref alarmable, false))
  247. {
  248. return false;
  249. }
  250. foreach (var alarmState in alarmable.NetworkAlarmStates.Values)
  251. {
  252. alarm = alarm == null || alarm < alarmState ? alarmState : alarm;
  253. }
  254. return alarm != null;
  255. }
  256. private void PlayAlertSound(EntityUid uid, AtmosAlarmType alarm, AtmosAlarmableComponent alarmable)
  257. {
  258. if (alarm == AtmosAlarmType.Danger)
  259. {
  260. _audioSystem.PlayPvs(alarmable.AlarmSound, uid, AudioParams.Default.WithVolume(alarmable.AlarmVolume));
  261. }
  262. }
  263. private void UpdateAppearance(EntityUid uid, AtmosAlarmType alarm)
  264. {
  265. _appearance.SetData(uid, AtmosMonitorVisuals.AlarmType, alarm);
  266. }
  267. }
  268. public sealed class AtmosAlarmEvent : EntityEventArgs
  269. {
  270. public AtmosAlarmType AlarmType { get; }
  271. public AtmosAlarmEvent(AtmosAlarmType netMax)
  272. {
  273. AlarmType = netMax;
  274. }
  275. }