using Content.Client.Popups; using Content.Client.UserInterface.Controls; using Content.Shared.Access.Systems; using Content.Shared.Holopad; using Content.Shared.Telephone; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Timing; using Robust.Shared.Utility; using System.Linq; namespace Content.Client.Holopad; [GenerateTypedNameReferences] public sealed partial class HolopadWindow : FancyWindow { [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IGameTiming _timing = default!; private readonly SharedHolopadSystem _holopadSystem = default!; private readonly SharedTelephoneSystem _telephoneSystem = default!; private readonly AccessReaderSystem _accessReaderSystem = default!; private readonly PopupSystem _popupSystem = default!; private EntityUid? _owner = null; private HolopadUiKey _currentUiKey; private TelephoneState _currentState; private TelephoneState _previousState; private TimeSpan _buttonUnlockTime; private float _updateTimer = 0.25f; private const float UpdateTime = 0.25f; private TimeSpan _buttonUnlockDelay = TimeSpan.FromSeconds(0.5f); public event Action? SendHolopadStartNewCallMessageAction; public event Action? SendHolopadAnswerCallMessageAction; public event Action? SendHolopadEndCallMessageAction; public event Action? SendHolopadStartBroadcastMessageAction; public event Action? SendHolopadActivateProjectorMessageAction; public event Action? SendHolopadRequestStationAiMessageAction; public HolopadWindow() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _holopadSystem = _entManager.System(); _telephoneSystem = _entManager.System(); _accessReaderSystem = _entManager.System(); _popupSystem = _entManager.System(); _buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay; // Assign button actions AnswerCallButton.OnPressed += args => { OnHolopadAnswerCallMessage(); }; EndCallButton.OnPressed += args => { OnHolopadEndCallMessage(); }; StartBroadcastButton.OnPressed += args => { OnHolopadStartBroadcastMessage(); }; ActivateProjectorButton.OnPressed += args => { OnHolopadActivateProjectorMessage(); }; RequestStationAiButton.OnPressed += args => { OnHolopadRequestStationAiMessage(); }; // XML formatting AnswerCallButton.AddStyleClass("ButtonAccept"); EndCallButton.AddStyleClass("Caution"); StartBroadcastButton.AddStyleClass("Caution"); HolopadContactListPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = new Color(47, 47, 59) * Color.DarkGray, BorderColor = new Color(82, 82, 82), //new Color(70, 73, 102), BorderThickness = new Thickness(2), }; HolopadContactListHeaderPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = new Color(82, 82, 82), }; EmergencyBroadcastText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-emergency-broadcast-in-progress"))); SubtitleText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-subtitle"))); OptionsText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-options"))); } #region: Button actions private void OnSendHolopadStartNewCallMessage(NetEntity receiver) { SendHolopadStartNewCallMessageAction?.Invoke(receiver); } private void OnHolopadAnswerCallMessage() { SendHolopadAnswerCallMessageAction?.Invoke(); } private void OnHolopadEndCallMessage() { SendHolopadEndCallMessageAction?.Invoke(); if (_currentUiKey == HolopadUiKey.AiRequestWindow) Close(); } private void OnHolopadStartBroadcastMessage() { if (_playerManager.LocalSession?.AttachedEntity == null || _owner == null) return; var player = _playerManager.LocalSession.AttachedEntity; if (!_accessReaderSystem.IsAllowed(player.Value, _owner.Value)) { _popupSystem.PopupClient(Loc.GetString("holopad-window-access-denied"), _owner.Value, player.Value); return; } SendHolopadStartBroadcastMessageAction?.Invoke(); } private void OnHolopadActivateProjectorMessage() { SendHolopadActivateProjectorMessageAction?.Invoke(); } private void OnHolopadRequestStationAiMessage() { SendHolopadRequestStationAiMessageAction?.Invoke(); } #endregion public void SetState(EntityUid owner, HolopadUiKey uiKey) { _owner = owner; _currentUiKey = uiKey; // Determines what UI containers are available to the user. // Components of these will be toggled on and off when // UpdateAppearance() is called switch (uiKey) { case HolopadUiKey.InteractionWindow: RequestStationAiContainer.Visible = true; HolopadContactListContainer.Visible = true; StartBroadcastContainer.Visible = true; break; case HolopadUiKey.InteractionWindowForAi: ActivateProjectorContainer.Visible = true; StartBroadcastContainer.Visible = true; break; case HolopadUiKey.AiActionWindow: HolopadContactListContainer.Visible = true; StartBroadcastContainer.Visible = true; break; case HolopadUiKey.AiRequestWindow: break; } } public void UpdateState(Dictionary holopads) { if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var telephone)) return; // Caller ID text var callerId = _telephoneSystem.GetFormattedCallerIdForEntity(telephone.LastCallerId.Item1, telephone.LastCallerId.Item2, Color.LightGray, "Default", 11); var holoapdId = _telephoneSystem.GetFormattedDeviceIdForEntity(telephone.LastCallerId.Item3, Color.LightGray, "Default", 11); CallerIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId)); HolopadIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(holoapdId)); LockOutIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId)); // Sort holopads alphabetically var holopadArray = holopads.ToArray(); Array.Sort(holopadArray, AlphabeticalSort); // Clear excess children from the contact list while (ContactsList.ChildCount > holopadArray.Length) ContactsList.RemoveChild(ContactsList.GetChild(ContactsList.ChildCount - 1)); // Make / update required children for (int i = 0; i < holopadArray.Length; i++) { var (netEntity, label) = holopadArray[i]; if (i >= ContactsList.ChildCount) { var newContactButton = new HolopadContactButton(); newContactButton.OnPressed += args => { OnSendHolopadStartNewCallMessage(newContactButton.NetEntity); }; ContactsList.AddChild(newContactButton); } var child = ContactsList.GetChild(i); if (child is not HolopadContactButton) continue; var contactButton = (HolopadContactButton)child; contactButton.UpdateValues(netEntity, label); } // Update buttons UpdateAppearance(); } private void UpdateAppearance() { if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var telephone)) return; if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var holopad)) return; var hasBroadcastAccess = !_holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad)); var localPlayer = _playerManager.LocalSession?.AttachedEntity; ControlsLockOutContainer.Visible = _holopadSystem.IsHolopadControlLocked((_owner.Value, holopad), localPlayer); ControlsContainer.Visible = !ControlsLockOutContainer.Visible; // Temporarily disable the interface buttons when the call state changes to prevent any misclicks if (_currentState != telephone.CurrentState) { _previousState = _currentState; _currentState = telephone.CurrentState; _buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay; } var lockButtons = _timing.CurTime < _buttonUnlockTime; // Make / update required children foreach (var child in ContactsList.Children) { if (child is not HolopadContactButton contactButton) continue; var passesFilter = string.IsNullOrEmpty(SearchLineEdit.Text) || contactButton.Text?.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase) == true; contactButton.Visible = passesFilter; contactButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons); } // Update control text var cooldown = _holopadSystem.GetHolopadBroadcastCoolDown((_owner.Value, holopad)); var cooldownString = $"{cooldown.Minutes:00}:{cooldown.Seconds:00}"; StartBroadcastButton.Text = _holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad)) ? Loc.GetString("holopad-window-emergency-broadcast-with-countdown", ("countdown", cooldownString)) : Loc.GetString("holopad-window-emergency-broadcast"); var lockout = _holopadSystem.GetHolopadControlLockedPeriod((_owner.Value, holopad)); var lockoutString = $"{lockout.Minutes:00}:{lockout.Seconds:00}"; LockOutCountDownText.Text = Loc.GetString("holopad-window-controls-unlock-countdown", ("countdown", lockoutString)); switch (_currentState) { case TelephoneState.Idle: CallStatusText.Text = Loc.GetString("holopad-window-no-calls-in-progress"); break; case TelephoneState.Calling: CallStatusText.Text = Loc.GetString("holopad-window-outgoing-call"); break; case TelephoneState.Ringing: CallStatusText.Text = (_currentUiKey == HolopadUiKey.AiRequestWindow) ? Loc.GetString("holopad-window-ai-request") : Loc.GetString("holopad-window-incoming-call"); break; case TelephoneState.InCall: CallStatusText.Text = Loc.GetString("holopad-window-call-in-progress"); break; case TelephoneState.EndingCall: if (_previousState == TelephoneState.Calling || _previousState == TelephoneState.Idle) CallStatusText.Text = Loc.GetString("holopad-window-call-rejected"); else CallStatusText.Text = Loc.GetString("holopad-window-call-ending"); break; } // Update control disability AnswerCallButton.Disabled = (_currentState != TelephoneState.Ringing || lockButtons); EndCallButton.Disabled = (_currentState == TelephoneState.Idle || _currentState == TelephoneState.EndingCall || lockButtons); StartBroadcastButton.Disabled = (_currentState != TelephoneState.Idle || !hasBroadcastAccess || lockButtons); RequestStationAiButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons); ActivateProjectorButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons); // Update control visibility FetchingAvailableHolopadsContainer.Visible = (ContactsList.ChildCount == 0); ActiveCallControlsContainer.Visible = (_currentState != TelephoneState.Idle || _currentUiKey == HolopadUiKey.AiRequestWindow); CallPlacementControlsContainer.Visible = !ActiveCallControlsContainer.Visible; CallerIdContainer.Visible = (_currentState == TelephoneState.Ringing); AnswerCallButton.Visible = (_currentState == TelephoneState.Ringing); } protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); _updateTimer += args.DeltaSeconds; if (_updateTimer >= UpdateTime) { _updateTimer -= UpdateTime; UpdateAppearance(); } } private sealed class HolopadContactButton : Button { public NetEntity NetEntity; public HolopadContactButton() { HorizontalExpand = true; SetHeight = 32; Margin = new Thickness(0f, 1f, 0f, 1f); ReservesSpace = false; } public void UpdateValues(NetEntity netEntity, string label) { NetEntity = netEntity; Text = Loc.GetString("holopad-window-contact-label", ("label", label)); } } private int AlphabeticalSort(KeyValuePair x, KeyValuePair y) { if (string.IsNullOrEmpty(x.Value)) return -1; if (string.IsNullOrEmpty(y.Value)) return 1; return x.Value.CompareTo(y.Value); } }