CommunicationsConsoleSystem.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.AlertLevel;
  3. using Content.Server.Chat.Systems;
  4. using Content.Server.DeviceNetwork.Components;
  5. using Content.Server.DeviceNetwork.Systems;
  6. using Content.Server.Interaction;
  7. using Content.Server.Popups;
  8. using Content.Server.RoundEnd;
  9. using Content.Server.Screens.Components;
  10. using Content.Server.Shuttles.Systems;
  11. using Content.Server.Station.Systems;
  12. using Content.Shared.Access.Components;
  13. using Content.Shared.Access.Systems;
  14. using Content.Shared.CCVar;
  15. using Content.Shared.Chat;
  16. using Content.Shared.Communications;
  17. using Content.Shared.Database;
  18. using Content.Shared.DeviceNetwork;
  19. using Content.Shared.IdentityManagement;
  20. using Content.Shared.Popups;
  21. using Robust.Server.GameObjects;
  22. using Robust.Shared.Configuration;
  23. namespace Content.Server.Communications
  24. {
  25. public sealed class CommunicationsConsoleSystem : EntitySystem
  26. {
  27. [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
  28. [Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
  29. [Dependency] private readonly ChatSystem _chatSystem = default!;
  30. [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
  31. [Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
  32. [Dependency] private readonly PopupSystem _popupSystem = default!;
  33. [Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
  34. [Dependency] private readonly StationSystem _stationSystem = default!;
  35. [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
  36. [Dependency] private readonly IConfigurationManager _cfg = default!;
  37. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  38. private const float UIUpdateInterval = 5.0f;
  39. public override void Initialize()
  40. {
  41. // All events that refresh the BUI
  42. SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
  43. SubscribeLocalEvent<CommunicationsConsoleComponent, ComponentInit>((uid, comp, _) => UpdateCommsConsoleInterface(uid, comp));
  44. SubscribeLocalEvent<RoundEndSystemChangedEvent>(_ => OnGenericBroadcastEvent());
  45. SubscribeLocalEvent<AlertLevelDelayFinishedEvent>(_ => OnGenericBroadcastEvent());
  46. // Messages from the BUI
  47. SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleSelectAlertLevelMessage>(OnSelectAlertLevelMessage);
  48. SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleAnnounceMessage>(OnAnnounceMessage);
  49. SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleBroadcastMessage>(OnBroadcastMessage);
  50. SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleCallEmergencyShuttleMessage>(OnCallShuttleMessage);
  51. SubscribeLocalEvent<CommunicationsConsoleComponent, CommunicationsConsoleRecallEmergencyShuttleMessage>(OnRecallShuttleMessage);
  52. // On console init, set cooldown
  53. SubscribeLocalEvent<CommunicationsConsoleComponent, MapInitEvent>(OnCommunicationsConsoleMapInit);
  54. }
  55. public override void Update(float frameTime)
  56. {
  57. var query = EntityQueryEnumerator<CommunicationsConsoleComponent>();
  58. while (query.MoveNext(out var uid, out var comp))
  59. {
  60. // TODO refresh the UI in a less horrible way
  61. if (comp.AnnouncementCooldownRemaining >= 0f)
  62. {
  63. comp.AnnouncementCooldownRemaining -= frameTime;
  64. }
  65. comp.UIUpdateAccumulator += frameTime;
  66. if (comp.UIUpdateAccumulator < UIUpdateInterval)
  67. continue;
  68. comp.UIUpdateAccumulator -= UIUpdateInterval;
  69. if (_uiSystem.IsUiOpen(uid, CommunicationsConsoleUiKey.Key))
  70. UpdateCommsConsoleInterface(uid, comp);
  71. }
  72. base.Update(frameTime);
  73. }
  74. public void OnCommunicationsConsoleMapInit(EntityUid uid, CommunicationsConsoleComponent comp, MapInitEvent args)
  75. {
  76. comp.AnnouncementCooldownRemaining = comp.InitialDelay;
  77. }
  78. /// <summary>
  79. /// Update the UI of every comms console.
  80. /// </summary>
  81. private void OnGenericBroadcastEvent()
  82. {
  83. var query = EntityQueryEnumerator<CommunicationsConsoleComponent>();
  84. while (query.MoveNext(out var uid, out var comp))
  85. {
  86. UpdateCommsConsoleInterface(uid, comp);
  87. }
  88. }
  89. /// <summary>
  90. /// Updates all comms consoles belonging to the station that the alert level was set on
  91. /// </summary>
  92. /// <param name="args">Alert level changed event arguments</param>
  93. private void OnAlertLevelChanged(AlertLevelChangedEvent args)
  94. {
  95. var query = EntityQueryEnumerator<CommunicationsConsoleComponent>();
  96. while (query.MoveNext(out var uid, out var comp))
  97. {
  98. var entStation = _stationSystem.GetOwningStation(uid);
  99. if (args.Station == entStation)
  100. UpdateCommsConsoleInterface(uid, comp);
  101. }
  102. }
  103. /// <summary>
  104. /// Updates the UI for all comms consoles.
  105. /// </summary>
  106. public void UpdateCommsConsoleInterface()
  107. {
  108. var query = EntityQueryEnumerator<CommunicationsConsoleComponent>();
  109. while (query.MoveNext(out var uid, out var comp))
  110. {
  111. UpdateCommsConsoleInterface(uid, comp);
  112. }
  113. }
  114. /// <summary>
  115. /// Updates the UI for a particular comms console.
  116. /// </summary>
  117. public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComponent comp)
  118. {
  119. var stationUid = _stationSystem.GetOwningStation(uid);
  120. List<string>? levels = null;
  121. string currentLevel = default!;
  122. float currentDelay = 0;
  123. if (stationUid != null)
  124. {
  125. if (TryComp(stationUid.Value, out AlertLevelComponent? alertComp) &&
  126. alertComp.AlertLevels != null)
  127. {
  128. if (alertComp.IsSelectable)
  129. {
  130. levels = new();
  131. foreach (var (id, detail) in alertComp.AlertLevels.Levels)
  132. {
  133. if (detail.Selectable)
  134. {
  135. levels.Add(id);
  136. }
  137. }
  138. }
  139. currentLevel = alertComp.CurrentLevel;
  140. currentDelay = _alertLevelSystem.GetAlertLevelDelay(stationUid.Value, alertComp);
  141. }
  142. }
  143. _uiSystem.SetUiState(uid, CommunicationsConsoleUiKey.Key, new CommunicationsConsoleInterfaceState(
  144. CanAnnounce(comp),
  145. CanCallOrRecall(comp),
  146. levels,
  147. currentLevel,
  148. currentDelay,
  149. _roundEndSystem.ExpectedCountdownEnd
  150. ));
  151. }
  152. private static bool CanAnnounce(CommunicationsConsoleComponent comp)
  153. {
  154. return comp.AnnouncementCooldownRemaining <= 0f;
  155. }
  156. private bool CanUse(EntityUid user, EntityUid console)
  157. {
  158. if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent))
  159. {
  160. return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);
  161. }
  162. return true;
  163. }
  164. private bool CanCallOrRecall(CommunicationsConsoleComponent comp)
  165. {
  166. // Defer to what the round end system thinks we should be able to do.
  167. if (_emergency.EmergencyShuttleArrived || !_roundEndSystem.CanCallOrRecall())
  168. return false;
  169. // Ensure that we can communicate with the shuttle (either call or recall)
  170. if (!comp.CanShuttle)
  171. return false;
  172. // Calling shuttle checks
  173. if (_roundEndSystem.ExpectedCountdownEnd is null)
  174. return true;
  175. // Recalling shuttle checks
  176. var recallThreshold = _cfg.GetCVar(CCVars.EmergencyRecallTurningPoint);
  177. // shouldn't really be happening if we got here
  178. if (_roundEndSystem.ShuttleTimeLeft is not { } left
  179. || _roundEndSystem.ExpectedShuttleLength is not { } expected)
  180. return false;
  181. return !(left.TotalSeconds / expected.TotalSeconds < recallThreshold);
  182. }
  183. private void OnSelectAlertLevelMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleSelectAlertLevelMessage message)
  184. {
  185. if (message.Actor is not { Valid: true } mob)
  186. return;
  187. if (!CanUse(mob, uid))
  188. {
  189. _popupSystem.PopupCursor(Loc.GetString("comms-console-permission-denied"), message.Actor, PopupType.Medium);
  190. return;
  191. }
  192. var stationUid = _stationSystem.GetOwningStation(uid);
  193. if (stationUid != null)
  194. {
  195. _alertLevelSystem.SetLevel(stationUid.Value, message.Level, true, true);
  196. }
  197. }
  198. private void OnAnnounceMessage(EntityUid uid, CommunicationsConsoleComponent comp,
  199. CommunicationsConsoleAnnounceMessage message)
  200. {
  201. var maxLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
  202. var msg = SharedChatSystem.SanitizeAnnouncement(message.Message, maxLength);
  203. var author = Loc.GetString("comms-console-announcement-unknown-sender");
  204. if (message.Actor is { Valid: true } mob)
  205. {
  206. if (!CanAnnounce(comp))
  207. {
  208. return;
  209. }
  210. if (!CanUse(mob, uid))
  211. {
  212. _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Actor);
  213. return;
  214. }
  215. var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(uid, mob);
  216. RaiseLocalEvent(tryGetIdentityShortInfoEvent);
  217. author = tryGetIdentityShortInfoEvent.Title;
  218. }
  219. comp.AnnouncementCooldownRemaining = comp.Delay;
  220. UpdateCommsConsoleInterface(uid, comp);
  221. var ev = new CommunicationConsoleAnnouncementEvent(uid, comp, msg, message.Actor);
  222. RaiseLocalEvent(ref ev);
  223. // allow admemes with vv
  224. Loc.TryGetString(comp.Title, out var title);
  225. title ??= comp.Title;
  226. msg += "\n" + Loc.GetString("comms-console-announcement-sent-by") + " " + author;
  227. if (comp.Global)
  228. {
  229. _chatSystem.DispatchGlobalAnnouncement(msg, title, announcementSound: comp.Sound, colorOverride: comp.Color);
  230. _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Actor):player} has sent the following global announcement: {msg}");
  231. return;
  232. }
  233. _chatSystem.DispatchStationAnnouncement(uid, msg, title, colorOverride: comp.Color);
  234. _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Actor):player} has sent the following world announcement: {msg}");
  235. }
  236. private void OnBroadcastMessage(EntityUid uid, CommunicationsConsoleComponent component, CommunicationsConsoleBroadcastMessage message)
  237. {
  238. if (!TryComp<DeviceNetworkComponent>(uid, out var net))
  239. return;
  240. var payload = new NetworkPayload
  241. {
  242. [ScreenMasks.Text] = message.Message
  243. };
  244. _deviceNetworkSystem.QueuePacket(uid, null, payload, net.TransmitFrequency);
  245. _adminLogger.Add(LogType.DeviceNetwork, LogImpact.Low, $"{ToPrettyString(message.Actor):player} has sent the following broadcast: {message.Message:msg}");
  246. }
  247. private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleCallEmergencyShuttleMessage message)
  248. {
  249. if (!CanCallOrRecall(comp))
  250. return;
  251. var mob = message.Actor;
  252. if (!CanUse(mob, uid))
  253. {
  254. _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Actor);
  255. return;
  256. }
  257. var ev = new CommunicationConsoleCallShuttleAttemptEvent(uid, comp, mob);
  258. RaiseLocalEvent(ref ev);
  259. if (ev.Cancelled)
  260. {
  261. _popupSystem.PopupEntity(ev.Reason ?? Loc.GetString("comms-console-shuttle-unavailable"), uid, message.Actor);
  262. return;
  263. }
  264. _roundEndSystem.RequestRoundEnd(uid);
  265. _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(mob):player} has called the shuttle.");
  266. }
  267. private void OnRecallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleRecallEmergencyShuttleMessage message)
  268. {
  269. if (!CanCallOrRecall(comp))
  270. return;
  271. if (!CanUse(message.Actor, uid))
  272. {
  273. _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Actor);
  274. return;
  275. }
  276. _roundEndSystem.CancelRoundEndCountdown(uid);
  277. _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(message.Actor):player} has recalled the shuttle.");
  278. }
  279. }
  280. /// <summary>
  281. /// Raised on announcement
  282. /// </summary>
  283. [ByRefEvent]
  284. public record struct CommunicationConsoleAnnouncementEvent(EntityUid Uid, CommunicationsConsoleComponent Component, string Text, EntityUid? Sender)
  285. {
  286. public EntityUid Uid = Uid;
  287. public CommunicationsConsoleComponent Component = Component;
  288. public EntityUid? Sender = Sender;
  289. public string Text = Text;
  290. }
  291. /// <summary>
  292. /// Raised on shuttle call attempt. Can be cancelled
  293. /// </summary>
  294. [ByRefEvent]
  295. public record struct CommunicationConsoleCallShuttleAttemptEvent(EntityUid Uid, CommunicationsConsoleComponent Component, EntityUid? Sender)
  296. {
  297. public bool Cancelled = false;
  298. public EntityUid Uid = Uid;
  299. public CommunicationsConsoleComponent Component = Component;
  300. public EntityUid? Sender = Sender;
  301. public string? Reason;
  302. }
  303. }