using System.Linq; using System.Text; using Content.Client._DV.UserInterfaces.Systems.Cwoink; using Content.Client.Administration.Managers; using Content.Client.Administration.UI.CustomControls; using Content.Shared.Administration; using Content.Shared.CCVar; using Robust.Client.AutoGenerated; using Robust.Client.Console; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Utility; namespace Content.Client._DV.Curation.UI.Cwoink; /// /// This window connects to a CwoinkSystem channel. CwoinkSystem manages the rest. /// [GenerateTypedNameReferences] public sealed partial class CwoinkControl : 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 CuratorCHelpUIHandler CHelpHelper = default!; private PlayerInfo? _currentPlayer; public CwoinkControl() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); var newPlayerThreshold = 0; _cfg.OnValueChanged(CCVars.NewPlayerThreshold, (val) => { newPlayerThreshold = val; }, true); var uiController = _ui.GetUIController(); if (uiController.UIHelper is not CuratorCHelpUIHandler helper) return; CHelpHelper = helper; _adminManager.AdminStatusUpdated += UpdateButtons; UpdateButtons(); CuratorOnly.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 (CHelpHelper.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.Append('"'); sb.Append(text); sb.Append('"'); return sb.ToString(); }; // // Returns true if the player's overall playtime is under the set threshold // 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 = CHelpHelper.EnsurePanel(a.SessionId); var bch = CHelpHelper.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 disconnected players by participation in the round if (a.ActiveThisRound != b.ActiveThisRound) return a.ActiveThisRound ? -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); // 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; } // Finally, sort by the most recent message. return bch.LastMessage.CompareTo(ach.LastMessage); }; ViewVariables.OnPressed += _ => { if (_currentPlayer is not null) _console.ExecuteCommand($"vv \"{_currentPlayer.NetEntity}\""); }; Follow.OnPressed += _ => { if (_currentPlayer is not null) _console.ExecuteCommand($"follow \"{_currentPlayer.NetEntity}\""); }; Respawn.OnPressed += _ => { if (_currentPlayer is not null) _console.ExecuteCommand($"respawn \"{_currentPlayer.Username}\""); }; PopOut.OnPressed += _ => { uiController.PopOut(); }; } public void OnCwoink() { ChannelSelector.PopulateList(); } public void SelectChannel(NetUserId channel) { if (!Extensions.TryFirstOrDefault(ChannelSelector.PlayerInfo, 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 (!Enumerable.Contains(ChannelSelector.PlayerListContainer.Data, data)) { ChannelSelector.StopFiltering(); } ChannelSelector.PopulateList(); ChannelSelector.PlayerListContainer.Select(data); } public void UpdateButtons() { var disabled = _currentPlayer == null; ViewVariables.Visible = _adminManager.CanCommand("vv"); ViewVariables.Disabled = !ViewVariables.Visible || disabled; Respawn.Visible = _adminManager.CanCommand("respawn"); Respawn.Disabled = !Respawn.Visible || disabled; Follow.Visible = _adminManager.CanCommand("follow"); Follow.Disabled = !Follow.Visible || disabled; } private void SwitchToChannel(NetUserId? ch) { UpdateButtons(); CHelpHelper.HideAllPanels(); if (ch != null) { var panel = CHelpHelper.EnsurePanel(ch.Value); panel.Visible = true; } } public void PopulateList() { // Maintain existing pin statuses var pinnedPlayers = Enumerable.Where(ChannelSelector.PlayerInfo, 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(); } }