| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- using System.Linq;
- using System.Text;
- using Content.Client.Administration.Managers;
- using Content.Client.Administration.UI.CustomControls;
- using Content.Client.UserInterface.Systems.Bwoink;
- using Content.Shared.Administration;
- using Content.Shared.CCVar;
- using Robust.Client.AutoGenerated;
- using Robust.Client.Console;
- using Robust.Client.UserInterface;
- using Robust.Client.UserInterface.Controls;
- using Robust.Client.UserInterface.XAML;
- using Robust.Shared.Network;
- using Robust.Shared.Configuration;
- using Robust.Shared.Utility;
- namespace Content.Client.Administration.UI.Bwoink
- {
- /// <summary>
- /// This window connects to a BwoinkSystem channel. BwoinkSystem manages the rest.
- /// </summary>
- [GenerateTypedNameReferences]
- public sealed partial class BwoinkControl : Control
- {
- [Dependency] private readonly IClientAdminManager _adminManager = default!;
- [Dependency] private readonly IClientConsoleHost _console = default!;
- [Dependency] private readonly IUserInterfaceManager _ui = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- public AdminAHelpUIHandler AHelpHelper = default!;
- private PlayerInfo? _currentPlayer;
- private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
- public BwoinkControl()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
- var newPlayerThreshold = 0;
- _cfg.OnValueChanged(CCVars.NewPlayerThreshold, (val) => { newPlayerThreshold = val; }, true);
- var uiController = _ui.GetUIController<AHelpUIController>();
- if (uiController.UIHelper is not AdminAHelpUIHandler helper)
- return;
- AHelpHelper = helper;
- _adminManager.AdminStatusUpdated += UpdateButtons;
- UpdateButtons();
- AdminOnly.OnToggled += args => PlaySound.Disabled = args.Pressed;
- ChannelSelector.OnSelectionChanged += sel =>
- {
- _currentPlayer = sel;
- SwitchToChannel(sel?.SessionId);
- ChannelSelector.PlayerListContainer.DirtyList();
- };
- ChannelSelector.OverrideText += (info, text) =>
- {
- var sb = new StringBuilder();
- if (info.Connected)
- sb.Append(info.ActiveThisRound ? '⚫' : '◐');
- else
- sb.Append(info.ActiveThisRound ? '⭘' : '·');
- sb.Append(' ');
- if (AHelpHelper.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0)
- {
- if (panel.Unread < 11)
- sb.Append(new Rune('➀' + (panel.Unread-1)));
- else
- sb.Append(new Rune(0x2639)); // ☹
- sb.Append(' ');
- }
- // Mark antagonists with symbol
- if (info.Antag && info.ActiveThisRound)
- sb.Append(new Rune(0x1F5E1)); // 🗡
- // Mark new players with symbol
- if (IsNewPlayer(info))
- sb.Append(new Rune(0x23F2)); // ⏲
- sb.AppendFormat("\"{0}\"", text);
- return sb.ToString();
- };
- // <summary>
- // Returns true if the player's overall playtime is under the set threshold
- // </summary>
- bool IsNewPlayer(PlayerInfo info)
- {
- // Don't show every disconnected player as new, don't show 0-minute players as new if threshold is
- if (newPlayerThreshold <= 0 || info.OverallPlaytime is null && !info.Connected)
- return false;
- return (info.OverallPlaytime is null
- || info.OverallPlaytime < TimeSpan.FromMinutes(newPlayerThreshold));
- }
- ChannelSelector.Comparison = (a, b) =>
- {
- var ach = AHelpHelper.EnsurePanel(a.SessionId);
- var bch = AHelpHelper.EnsurePanel(b.SessionId);
- // Pinned players first
- if (a.IsPinned != b.IsPinned)
- return a.IsPinned ? -1 : 1;
- // Then, any chat with unread messages.
- var aUnread = ach.Unread > 0;
- var bUnread = bch.Unread > 0;
- if (aUnread != bUnread)
- return aUnread ? -1 : 1;
- // Then, any chat with recent messages from the current round
- var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue;
- var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue;
- if (aRecent != bRecent)
- return aRecent ? -1 : 1;
- // Sort by connection status. Disconnected players will be last.
- if (a.Connected != b.Connected)
- return a.Connected ? -1 : 1;
- // Sort connected players by whether they have joined the round, then by New Player status, then by Antag status
- if (a.Connected && b.Connected)
- {
- var aNewPlayer = IsNewPlayer(a);
- var bNewPlayer = IsNewPlayer(b);
- // Players who have joined the round will be listed before players in the lobby
- if (a.ActiveThisRound != b.ActiveThisRound)
- return a.ActiveThisRound ? -1 : 1;
- // Within both the joined group and lobby group, new players will be grouped and listed first
- if (aNewPlayer != bNewPlayer)
- return aNewPlayer ? -1 : 1;
- // Within all four previous groups, antagonists will be listed first.
- if (a.Antag != b.Antag)
- return a.Antag ? -1 : 1;
- }
- // Sort disconnected players by participation in the round
- if (!a.Connected && !b.Connected)
- {
- if (a.ActiveThisRound != b.ActiveThisRound)
- return a.ActiveThisRound ? -1 : 1;
- }
- // Finally, sort by the most recent message.
- return bch.LastMessage.CompareTo(ach.LastMessage);
- };
- Bans.OnPressed += _ =>
- {
- if (_currentPlayer is not null)
- _console.ExecuteCommand($"banlist \"{_currentPlayer.SessionId}\"");
- };
- Notes.OnPressed += _ =>
- {
- if (_currentPlayer is not null)
- _console.ExecuteCommand($"adminnotes \"{_currentPlayer.SessionId}\"");
- };
- Ban.OnPressed += _ =>
- {
- if (_currentPlayer is not null)
- _console.ExecuteCommand($"banpanel \"{_currentPlayer.SessionId}\"");
- };
- Kick.OnPressed += _ =>
- {
- if (!AdminUIHelpers.TryConfirm(Kick, _confirmations))
- {
- return;
- }
- // TODO: Reason field
- if (_currentPlayer is not null)
- _console.ExecuteCommand($"kick \"{_currentPlayer.Username}\"");
- };
- Follow.OnPressed += _ =>
- {
- if (_currentPlayer is not null)
- _console.ExecuteCommand($"follow \"{_currentPlayer.NetEntity}\"");
- };
- Respawn.OnPressed += _ =>
- {
- if (!AdminUIHelpers.TryConfirm(Respawn, _confirmations))
- {
- return;
- }
- if (_currentPlayer is not null)
- _console.ExecuteCommand($"respawn \"{_currentPlayer.Username}\"");
- };
- PopOut.OnPressed += _ =>
- {
- uiController.PopOut();
- };
- }
- public void OnBwoink(NetUserId channel)
- {
- ChannelSelector.PopulateList();
- }
- public void SelectChannel(NetUserId channel)
- {
- if (!ChannelSelector.PlayerInfo.TryFirstOrDefault(
- i => i.SessionId == channel, out var info))
- return;
- // clear filter if we're trying to select a channel for a player that isn't currently filtered
- // i.e. through the message verb.
- var data = new PlayerListData(info);
- if (!ChannelSelector.PlayerListContainer.Data.Contains(data))
- {
- ChannelSelector.StopFiltering();
- }
- ChannelSelector.PopulateList();
- ChannelSelector.PlayerListContainer.Select(data);
- }
- public void UpdateButtons()
- {
- var disabled = _currentPlayer == null;
- Bans.Visible = _adminManager.HasFlag(AdminFlags.Ban);
- Bans.Disabled = !Bans.Visible || disabled;
- Notes.Visible = _adminManager.HasFlag(AdminFlags.ViewNotes);
- Notes.Disabled = !Notes.Visible || disabled;
- Ban.Visible = _adminManager.HasFlag(AdminFlags.Ban);
- Ban.Disabled = !Ban.Visible || disabled;
- Kick.Visible = _adminManager.CanCommand("kick");
- Kick.Disabled = !Kick.Visible || disabled;
- Respawn.Visible = _adminManager.CanCommand("respawn");
- Respawn.Disabled = !Respawn.Visible || disabled;
- Follow.Visible = _adminManager.CanCommand("follow");
- Follow.Disabled = !Follow.Visible || disabled;
- }
- private string FormatTabTitle(ItemList.Item li, PlayerInfo? pl = default)
- {
- pl ??= (PlayerInfo) li.Metadata!;
- var sb = new StringBuilder();
- sb.Append(pl.Connected ? '●' : '○');
- sb.Append(' ');
- if (AHelpHelper.TryGetChannel(pl.SessionId, out var panel) && panel.Unread > 0)
- {
- if (panel.Unread < 11)
- sb.Append(new Rune('➀' + (panel.Unread-1)));
- else
- sb.Append(new Rune(0x2639)); // ☹
- sb.Append(' ');
- }
- if (pl.Antag)
- sb.Append(new Rune(0x1F5E1)); // 🗡
- if (pl.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
- sb.Append(new Rune(0x23F2)); // ⏲
- sb.AppendFormat("\"{0}\"", pl.CharacterName);
- if (pl.IdentityName != pl.CharacterName && pl.IdentityName != string.Empty)
- sb.Append(' ').AppendFormat("[{0}]", pl.IdentityName);
- sb.Append(' ').Append(pl.Username);
- return sb.ToString();
- }
- private void SwitchToChannel(NetUserId? ch)
- {
- UpdateButtons();
- AHelpHelper.HideAllPanels();
- if (ch != null)
- {
- var panel = AHelpHelper.EnsurePanel(ch.Value);
- panel.Visible = true;
- }
- }
- public void PopulateList()
- {
- // Maintain existing pin statuses
- var pinnedPlayers = ChannelSelector.PlayerInfo.Where(p => p.IsPinned).ToDictionary(p => p.SessionId);
- ChannelSelector.PopulateList();
- // Restore pin statuses
- foreach (var player in ChannelSelector.PlayerInfo)
- {
- if (pinnedPlayers.TryGetValue(player.SessionId, out var pinnedPlayer))
- {
- player.IsPinned = pinnedPlayer.IsPinned;
- }
- }
- UpdateButtons();
- }
- }
- }
|