HolopadWindow.xaml.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. using Content.Client.Popups;
  2. using Content.Client.UserInterface.Controls;
  3. using Content.Shared.Access.Systems;
  4. using Content.Shared.Holopad;
  5. using Content.Shared.Telephone;
  6. using Robust.Client.AutoGenerated;
  7. using Robust.Client.Graphics;
  8. using Robust.Client.Player;
  9. using Robust.Client.UserInterface.Controls;
  10. using Robust.Client.UserInterface.XAML;
  11. using Robust.Shared.Timing;
  12. using Robust.Shared.Utility;
  13. using System.Linq;
  14. namespace Content.Client.Holopad;
  15. [GenerateTypedNameReferences]
  16. public sealed partial class HolopadWindow : FancyWindow
  17. {
  18. [Dependency] private readonly IEntityManager _entManager = default!;
  19. [Dependency] private readonly IPlayerManager _playerManager = default!;
  20. [Dependency] private readonly IGameTiming _timing = default!;
  21. private readonly SharedHolopadSystem _holopadSystem = default!;
  22. private readonly SharedTelephoneSystem _telephoneSystem = default!;
  23. private readonly AccessReaderSystem _accessReaderSystem = default!;
  24. private readonly PopupSystem _popupSystem = default!;
  25. private EntityUid? _owner = null;
  26. private HolopadUiKey _currentUiKey;
  27. private TelephoneState _currentState;
  28. private TelephoneState _previousState;
  29. private TimeSpan _buttonUnlockTime;
  30. private float _updateTimer = 0.25f;
  31. private const float UpdateTime = 0.25f;
  32. private TimeSpan _buttonUnlockDelay = TimeSpan.FromSeconds(0.5f);
  33. public event Action<NetEntity>? SendHolopadStartNewCallMessageAction;
  34. public event Action? SendHolopadAnswerCallMessageAction;
  35. public event Action? SendHolopadEndCallMessageAction;
  36. public event Action? SendHolopadStartBroadcastMessageAction;
  37. public event Action? SendHolopadActivateProjectorMessageAction;
  38. public event Action? SendHolopadRequestStationAiMessageAction;
  39. public HolopadWindow()
  40. {
  41. RobustXamlLoader.Load(this);
  42. IoCManager.InjectDependencies(this);
  43. _holopadSystem = _entManager.System<SharedHolopadSystem>();
  44. _telephoneSystem = _entManager.System<SharedTelephoneSystem>();
  45. _accessReaderSystem = _entManager.System<AccessReaderSystem>();
  46. _popupSystem = _entManager.System<PopupSystem>();
  47. _buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay;
  48. // Assign button actions
  49. AnswerCallButton.OnPressed += args => { OnHolopadAnswerCallMessage(); };
  50. EndCallButton.OnPressed += args => { OnHolopadEndCallMessage(); };
  51. StartBroadcastButton.OnPressed += args => { OnHolopadStartBroadcastMessage(); };
  52. ActivateProjectorButton.OnPressed += args => { OnHolopadActivateProjectorMessage(); };
  53. RequestStationAiButton.OnPressed += args => { OnHolopadRequestStationAiMessage(); };
  54. // XML formatting
  55. AnswerCallButton.AddStyleClass("ButtonAccept");
  56. EndCallButton.AddStyleClass("Caution");
  57. StartBroadcastButton.AddStyleClass("Caution");
  58. HolopadContactListPanel.PanelOverride = new StyleBoxFlat
  59. {
  60. BackgroundColor = new Color(47, 47, 59) * Color.DarkGray,
  61. BorderColor = new Color(82, 82, 82), //new Color(70, 73, 102),
  62. BorderThickness = new Thickness(2),
  63. };
  64. HolopadContactListHeaderPanel.PanelOverride = new StyleBoxFlat
  65. {
  66. BackgroundColor = new Color(82, 82, 82),
  67. };
  68. EmergencyBroadcastText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-emergency-broadcast-in-progress")));
  69. SubtitleText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-subtitle")));
  70. OptionsText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-options")));
  71. }
  72. #region: Button actions
  73. private void OnSendHolopadStartNewCallMessage(NetEntity receiver)
  74. {
  75. SendHolopadStartNewCallMessageAction?.Invoke(receiver);
  76. }
  77. private void OnHolopadAnswerCallMessage()
  78. {
  79. SendHolopadAnswerCallMessageAction?.Invoke();
  80. }
  81. private void OnHolopadEndCallMessage()
  82. {
  83. SendHolopadEndCallMessageAction?.Invoke();
  84. if (_currentUiKey == HolopadUiKey.AiRequestWindow)
  85. Close();
  86. }
  87. private void OnHolopadStartBroadcastMessage()
  88. {
  89. if (_playerManager.LocalSession?.AttachedEntity == null || _owner == null)
  90. return;
  91. var player = _playerManager.LocalSession.AttachedEntity;
  92. if (!_accessReaderSystem.IsAllowed(player.Value, _owner.Value))
  93. {
  94. _popupSystem.PopupClient(Loc.GetString("holopad-window-access-denied"), _owner.Value, player.Value);
  95. return;
  96. }
  97. SendHolopadStartBroadcastMessageAction?.Invoke();
  98. }
  99. private void OnHolopadActivateProjectorMessage()
  100. {
  101. SendHolopadActivateProjectorMessageAction?.Invoke();
  102. }
  103. private void OnHolopadRequestStationAiMessage()
  104. {
  105. SendHolopadRequestStationAiMessageAction?.Invoke();
  106. }
  107. #endregion
  108. public void SetState(EntityUid owner, HolopadUiKey uiKey)
  109. {
  110. _owner = owner;
  111. _currentUiKey = uiKey;
  112. // Determines what UI containers are available to the user.
  113. // Components of these will be toggled on and off when
  114. // UpdateAppearance() is called
  115. switch (uiKey)
  116. {
  117. case HolopadUiKey.InteractionWindow:
  118. RequestStationAiContainer.Visible = true;
  119. HolopadContactListContainer.Visible = true;
  120. StartBroadcastContainer.Visible = true;
  121. break;
  122. case HolopadUiKey.InteractionWindowForAi:
  123. ActivateProjectorContainer.Visible = true;
  124. StartBroadcastContainer.Visible = true;
  125. break;
  126. case HolopadUiKey.AiActionWindow:
  127. HolopadContactListContainer.Visible = true;
  128. StartBroadcastContainer.Visible = true;
  129. break;
  130. case HolopadUiKey.AiRequestWindow:
  131. break;
  132. }
  133. }
  134. public void UpdateState(Dictionary<NetEntity, string> holopads)
  135. {
  136. if (_owner == null || !_entManager.TryGetComponent<TelephoneComponent>(_owner.Value, out var telephone))
  137. return;
  138. // Caller ID text
  139. var callerId = _telephoneSystem.GetFormattedCallerIdForEntity(telephone.LastCallerId.Item1, telephone.LastCallerId.Item2, Color.LightGray, "Default", 11);
  140. var holoapdId = _telephoneSystem.GetFormattedDeviceIdForEntity(telephone.LastCallerId.Item3, Color.LightGray, "Default", 11);
  141. CallerIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
  142. HolopadIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(holoapdId));
  143. LockOutIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
  144. // Sort holopads alphabetically
  145. var holopadArray = holopads.ToArray();
  146. Array.Sort(holopadArray, AlphabeticalSort);
  147. // Clear excess children from the contact list
  148. while (ContactsList.ChildCount > holopadArray.Length)
  149. ContactsList.RemoveChild(ContactsList.GetChild(ContactsList.ChildCount - 1));
  150. // Make / update required children
  151. for (int i = 0; i < holopadArray.Length; i++)
  152. {
  153. var (netEntity, label) = holopadArray[i];
  154. if (i >= ContactsList.ChildCount)
  155. {
  156. var newContactButton = new HolopadContactButton();
  157. newContactButton.OnPressed += args => { OnSendHolopadStartNewCallMessage(newContactButton.NetEntity); };
  158. ContactsList.AddChild(newContactButton);
  159. }
  160. var child = ContactsList.GetChild(i);
  161. if (child is not HolopadContactButton)
  162. continue;
  163. var contactButton = (HolopadContactButton)child;
  164. contactButton.UpdateValues(netEntity, label);
  165. }
  166. // Update buttons
  167. UpdateAppearance();
  168. }
  169. private void UpdateAppearance()
  170. {
  171. if (_owner == null || !_entManager.TryGetComponent<TelephoneComponent>(_owner.Value, out var telephone))
  172. return;
  173. if (_owner == null || !_entManager.TryGetComponent<HolopadComponent>(_owner.Value, out var holopad))
  174. return;
  175. var hasBroadcastAccess = !_holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad));
  176. var localPlayer = _playerManager.LocalSession?.AttachedEntity;
  177. ControlsLockOutContainer.Visible = _holopadSystem.IsHolopadControlLocked((_owner.Value, holopad), localPlayer);
  178. ControlsContainer.Visible = !ControlsLockOutContainer.Visible;
  179. // Temporarily disable the interface buttons when the call state changes to prevent any misclicks
  180. if (_currentState != telephone.CurrentState)
  181. {
  182. _previousState = _currentState;
  183. _currentState = telephone.CurrentState;
  184. _buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay;
  185. }
  186. var lockButtons = _timing.CurTime < _buttonUnlockTime;
  187. // Make / update required children
  188. foreach (var child in ContactsList.Children)
  189. {
  190. if (child is not HolopadContactButton contactButton)
  191. continue;
  192. var passesFilter = string.IsNullOrEmpty(SearchLineEdit.Text) ||
  193. contactButton.Text?.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase) == true;
  194. contactButton.Visible = passesFilter;
  195. contactButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
  196. }
  197. // Update control text
  198. var cooldown = _holopadSystem.GetHolopadBroadcastCoolDown((_owner.Value, holopad));
  199. var cooldownString = $"{cooldown.Minutes:00}:{cooldown.Seconds:00}";
  200. StartBroadcastButton.Text = _holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad)) ?
  201. Loc.GetString("holopad-window-emergency-broadcast-with-countdown", ("countdown", cooldownString)) :
  202. Loc.GetString("holopad-window-emergency-broadcast");
  203. var lockout = _holopadSystem.GetHolopadControlLockedPeriod((_owner.Value, holopad));
  204. var lockoutString = $"{lockout.Minutes:00}:{lockout.Seconds:00}";
  205. LockOutCountDownText.Text = Loc.GetString("holopad-window-controls-unlock-countdown", ("countdown", lockoutString));
  206. switch (_currentState)
  207. {
  208. case TelephoneState.Idle:
  209. CallStatusText.Text = Loc.GetString("holopad-window-no-calls-in-progress"); break;
  210. case TelephoneState.Calling:
  211. CallStatusText.Text = Loc.GetString("holopad-window-outgoing-call"); break;
  212. case TelephoneState.Ringing:
  213. CallStatusText.Text = (_currentUiKey == HolopadUiKey.AiRequestWindow) ?
  214. Loc.GetString("holopad-window-ai-request") : Loc.GetString("holopad-window-incoming-call"); break;
  215. case TelephoneState.InCall:
  216. CallStatusText.Text = Loc.GetString("holopad-window-call-in-progress"); break;
  217. case TelephoneState.EndingCall:
  218. if (_previousState == TelephoneState.Calling || _previousState == TelephoneState.Idle)
  219. CallStatusText.Text = Loc.GetString("holopad-window-call-rejected");
  220. else
  221. CallStatusText.Text = Loc.GetString("holopad-window-call-ending");
  222. break;
  223. }
  224. // Update control disability
  225. AnswerCallButton.Disabled = (_currentState != TelephoneState.Ringing || lockButtons);
  226. EndCallButton.Disabled = (_currentState == TelephoneState.Idle || _currentState == TelephoneState.EndingCall || lockButtons);
  227. StartBroadcastButton.Disabled = (_currentState != TelephoneState.Idle || !hasBroadcastAccess || lockButtons);
  228. RequestStationAiButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
  229. ActivateProjectorButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
  230. // Update control visibility
  231. FetchingAvailableHolopadsContainer.Visible = (ContactsList.ChildCount == 0);
  232. ActiveCallControlsContainer.Visible = (_currentState != TelephoneState.Idle || _currentUiKey == HolopadUiKey.AiRequestWindow);
  233. CallPlacementControlsContainer.Visible = !ActiveCallControlsContainer.Visible;
  234. CallerIdContainer.Visible = (_currentState == TelephoneState.Ringing);
  235. AnswerCallButton.Visible = (_currentState == TelephoneState.Ringing);
  236. }
  237. protected override void FrameUpdate(FrameEventArgs args)
  238. {
  239. base.FrameUpdate(args);
  240. _updateTimer += args.DeltaSeconds;
  241. if (_updateTimer >= UpdateTime)
  242. {
  243. _updateTimer -= UpdateTime;
  244. UpdateAppearance();
  245. }
  246. }
  247. private sealed class HolopadContactButton : Button
  248. {
  249. public NetEntity NetEntity;
  250. public HolopadContactButton()
  251. {
  252. HorizontalExpand = true;
  253. SetHeight = 32;
  254. Margin = new Thickness(0f, 1f, 0f, 1f);
  255. ReservesSpace = false;
  256. }
  257. public void UpdateValues(NetEntity netEntity, string label)
  258. {
  259. NetEntity = netEntity;
  260. Text = Loc.GetString("holopad-window-contact-label", ("label", label));
  261. }
  262. }
  263. private int AlphabeticalSort(KeyValuePair<NetEntity, string> x, KeyValuePair<NetEntity, string> y)
  264. {
  265. if (string.IsNullOrEmpty(x.Value))
  266. return -1;
  267. if (string.IsNullOrEmpty(y.Value))
  268. return 1;
  269. return x.Value.CompareTo(y.Value);
  270. }
  271. }