diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs index 3e05018c10..973f1a090b 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs @@ -36,6 +36,9 @@ namespace Content.Client.Administration.UI.Bwoink 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 AdminAHelpUIHandler helper) return; @@ -59,9 +62,9 @@ namespace Content.Client.Administration.UI.Bwoink var sb = new StringBuilder(); if (info.Connected) - sb.Append('●'); + sb.Append(info.ActiveThisRound ? '⚫' : '◐'); else - sb.Append(info.ActiveThisRound ? '○' : '·'); + sb.Append(info.ActiveThisRound ? '⭘' : '·'); sb.Append(' '); if (AHelpHelper.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0) @@ -73,10 +76,12 @@ namespace Content.Client.Administration.UI.Bwoink sb.Append(' '); } + // Mark antagonists with symbol if (info.Antag && info.ActiveThisRound) sb.Append(new Rune(0x1F5E1)); // 🗡 - if (info.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold))) + // Mark new players with symbol + if (IsNewPlayer(info)) sb.Append(new Rune(0x23F2)); // ⏲ sb.AppendFormat("\"{0}\"", text); @@ -84,6 +89,19 @@ namespace Content.Client.Administration.UI.Bwoink 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 = AHelpHelper.EnsurePanel(a.SessionId); @@ -93,31 +111,37 @@ namespace Content.Client.Administration.UI.Bwoink if (a.IsPinned != b.IsPinned) return a.IsPinned ? -1 : 1; - // First, sort by unread. Any chat with unread messages appears first. + // Then, any chat with unread messages. var aUnread = ach.Unread > 0; var bUnread = bch.Unread > 0; if (aUnread != bUnread) return aUnread ? -1 : 1; - // Sort by recent messages during the current round. + // 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; - // Next, sort by connection status. Any disconnected players are grouped towards the end. + // Sort by connection status. Disconnected players will be last. if (a.Connected != b.Connected) return a.Connected ? -1 : 1; - // Sort connected players by New Player status, then by Antag status + // 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 = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)); - var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)); + 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; } diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs index e8653843c7..e6cd4942a6 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs @@ -22,12 +22,9 @@ namespace Content.Client.Administration.UI.Bwoink return; } - Title = $"{sel.CharacterName} / {sel.Username}"; + Title = $"{sel.CharacterName} / {sel.Username} | {Loc.GetString("generic-playtime-title")}: "; - if (sel.OverallPlaytime != null) - { - Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}"; - } + Title += sel.OverallPlaytime != null ? sel.PlaytimeString : Loc.GetString("generic-unknown-title"); }; OnOpen += () => diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index c1b08437de..7d50730456 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -222,6 +222,7 @@ public sealed class AdminSystem : EntitySystem var entityName = string.Empty; var identityName = string.Empty; + // Visible (identity) name can be different from real name if (session?.AttachedEntity != null) { entityName = EntityManager.GetComponent(session.AttachedEntity.Value).EntityName; @@ -230,6 +231,7 @@ public sealed class AdminSystem : EntitySystem var antag = false; + // Starting role, antagonist status and role type RoleTypePrototype roleType = new(); var startingRole = string.Empty; if (_minds.TryGetMind(session, out var mindId, out var mindComp)) @@ -243,8 +245,13 @@ public sealed class AdminSystem : EntitySystem startingRole = _jobs.MindTryGetJobName(mindId); } + // Connection status and playtime var connected = session != null && session.Status is SessionStatus.Connected or SessionStatus.InGame; - TimeSpan? overallPlaytime = null; + + // Start with the last available playtime data + var cachedInfo = GetCachedPlayerInfo(data.UserId); + var overallPlaytime = cachedInfo?.OverallPlaytime; + // Overwrite with current playtime data, unless it's null (such as if the player just disconnected) if (session != null && _playTime.TryGetTrackerTimes(session, out var playTimes) && playTimes.TryGetValue(PlayTimeTrackingShared.TrackerOverall, out var playTime))