Files
tbd-station-14/Content.Client/Holopad/HolopadWindow.xaml.cs
chromiumboy 7780b867ac Holopads (#32711)
* Initial resources commit

* Initial code commit

* Added additional resources

* Continuing to build holopad and telephone systems

* Added hologram shader

* Added hologram system and entity

* Holo calls now have a hologram of the user appear on them

* Initial implementation of holopads transmitting nearby chatter

* Added support for linking across multiple telephones/holopads/entities

* Fixed a bunch of bugs

* Tried simplifying holopad entity dependence, added support for mid-call user switching

* Replaced PVS expansion with manually networked sprite states

* Adjusted volume of ring tone

* Added machine board

* Minor features and tweaks

* Resolving merge conflict

* Recommit audio attributions

* Telephone chat adjustments

* Added support for AI interactions with holopads

* Building the holopad UI

* Holopad UI finished

* Further UI tweaks

* Station AI can hear local chatter when being projected from a holopad

* Minor bug fixes

* Added wire panels to holopads

* Basic broadcasting

* Start of emergency broadcasting code

* Fixing issues with broadcasting

* More work on emergency broadcasting

* Updated holopad visuals

* Added cooldown text to emergency broadcast and control lock out screen

* Code clean up

* Fixed issue with timing

* Broadcasting now requires command access

* Fixed some bugs

* Added multiple holopad prototypes with different ranges

* The AI no longer requires power to interact with holopads

* Fixed some additional issues

* Addressing more issues

* Added emote support for holograms

* Changed the broadcast lockout durations to their proper values

* Added AI vision wire to holopads

* Bug fixes

* AI vision and interaction wires can be added to the same wire panel

* Fixed error

* More bug fixes

* Fixed test fail

* Embellished the emergency call lock out window

* Holopads play borg sounds when speaking

* Borg and AI names are listed as the caller ID on the holopad

* Borg chassis can now be seen on holopad holograms

* Holopad returns to a machine frame when badly damaged

* Clarified some text

* Fix merge conflict

* Fixed merge conflict

* Fixing merge conflict

* Fixing merge conflict

* Fixing merge conflict

* Offset menu on open

* AI can alt click on holopads to activate the projector

* Bug fixes for intellicard interactions

* Fixed speech issue with intellicards

* The UI automatically opens for the AI when it alt-clicks on the holopad

* Simplified shader math

* Telephones will auto hang up 60 seconds after the last person on a call stops speaking

* Added better support for AI requests when multiple AI cores are on the station

* The call controls pop up for the AI when they accept a summons from a holopad

* Compatibility mode fix for the hologram shader

* Further shader fixes for compatibility mode

* File clean up

* More cleaning up

* Removed access requirements from quantum holopads so they can used by nukies

* The title of the holopad window now reflects the name of the device

* Linked telephones will lose their connection if both move out of range of each other
2024-12-17 20:18:15 +01:00

339 lines
13 KiB
C#

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<NetEntity>? 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<SharedHolopadSystem>();
_telephoneSystem = _entManager.System<SharedTelephoneSystem>();
_accessReaderSystem = _entManager.System<AccessReaderSystem>();
_popupSystem = _entManager.System<PopupSystem>();
_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<NetEntity, string> holopads)
{
if (_owner == null || !_entManager.TryGetComponent<TelephoneComponent>(_owner.Value, out var telephone))
return;
// Caller ID text
var callerId = _telephoneSystem.GetFormattedCallerIdForEntity(telephone.LastCallerId.Item1, telephone.LastCallerId.Item2, Color.LightGray, "Default", 11);
CallerIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
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<TelephoneComponent>(_owner.Value, out var telephone))
return;
if (_owner == null || !_entManager.TryGetComponent<HolopadComponent>(_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)
continue;
var contactButton = (HolopadContactButton)child;
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;
CallerIdText.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);
}
public void UpdateValues(NetEntity netEntity, string label)
{
NetEntity = netEntity;
Text = Loc.GetString("holopad-window-contact-label", ("label", label));
}
}
private int AlphabeticalSort(KeyValuePair<NetEntity, string> x, KeyValuePair<NetEntity, string> y)
{
if (string.IsNullOrEmpty(x.Value))
return -1;
if (string.IsNullOrEmpty(y.Value))
return 1;
return x.Value.CompareTo(y.Value);
}
}