]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Admin playerlist antag presentation rework (#35538)
authorErrant <35878406+Errant-4@users.noreply.github.com>
Tue, 25 Mar 2025 16:03:59 +0000 (17:03 +0100)
committerGitHub <noreply@github.com>
Tue, 25 Mar 2025 16:03:59 +0000 (17:03 +0100)
* refactor(src): Minor refactor of Draw in "AdminNameOverlay. And new info about playtime player

* fix(src): Add configure classic admin owerlay

* fix

* antag status indication rework

* the cvars are free, you can just take them

* update playerlist on cvar change

* more overlay options

* tweak(src): Use _antagLabelClassic and tweak style

* tweak(src): Add config display overlay for startingJob and playTime

* tweak(src): Vector2 is replaced by var

* tweak(src): return to the end of the list

* add new option checkboxes

* passing ConfigurationManager through constructor, some format changes

* made sorting values more futureproof

* comments

* labels

* no point commenting this out when the overlay stack PR will uncomment it again anyway

* sorting prototype

* localize symbols because why not

* symmetry

* Revert "localize symbols because why not"

This reverts commit 922d4030300285a45777d62fcfd9c74b25fe7a60.

* layout and formatting stuff

* fix errant space

---------

Co-authored-by: Schrödinger <132720404+Schrodinger71@users.noreply.github.com>
19 files changed:
Content.Client/Administration/AdminNameOverlay.cs
Content.Client/Administration/Systems/AdminSystem.Overlay.cs
Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs
Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml
Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs
Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml
Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml.cs
Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml
Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs
Content.Server/Administration/Systems/AdminSystem.cs
Content.Shared/Administration/PlayerInfo.cs
Content.Shared/CCVar/CCVars.Interface.cs
Content.Shared/Mind/RoleTypePrototype.cs
Content.Shared/Roles/MindRoleComponent.cs
Content.Shared/Roles/SharedRoleSystem.cs
Resources/Locale/en-US/administration/ui/tabs/player-tab.ftl
Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
Resources/Prototypes/Roles/MindRoles/mind_roles.yml
Resources/Prototypes/Roles/role_types.yml

index 3c968ec17cc1ba5ac382b51a43228b338cdb81cb..205e9757e642aa974b12a789ad18678044abcb89 100644 (file)
@@ -1,6 +1,7 @@
 using System.Linq;
 using System.Numerics;
 using Content.Client.Administration.Systems;
+using Content.Client.Stylesheets;
 using Content.Shared.CCVar;
 using Content.Shared.Mind;
 using Robust.Client.Graphics;
@@ -14,14 +15,17 @@ namespace Content.Client.Administration;
 
 internal sealed class AdminNameOverlay : Overlay
 {
-    [Dependency] private readonly IConfigurationManager _config = default!;
-
     private readonly AdminSystem _system;
     private readonly IEntityManager _entityManager;
     private readonly IEyeManager _eyeManager;
     private readonly EntityLookupSystem _entityLookup;
     private readonly IUserInterfaceManager _userInterfaceManager;
     private readonly Font _font;
+    private readonly Font _fontBold;
+    private bool _overlayClassic;
+    private bool _overlaySymbols;
+    private bool _overlayPlaytime;
+    private bool _overlayStartingJob;
 
     //TODO make this adjustable via GUI
     private readonly ProtoId<RoleTypePrototype>[] _filter =
@@ -29,17 +33,29 @@ internal sealed class AdminNameOverlay : Overlay
     private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
     private readonly Color _antagColorClassic = Color.OrangeRed;
 
-    public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
+    public AdminNameOverlay(
+        AdminSystem system,
+        IEntityManager entityManager,
+        IEyeManager eyeManager,
+        IResourceCache resourceCache,
+        EntityLookupSystem entityLookup,
+        IUserInterfaceManager userInterfaceManager,
+        IConfigurationManager config)
     {
-        IoCManager.InjectDependencies(this);
-
         _system = system;
         _entityManager = entityManager;
         _eyeManager = eyeManager;
         _entityLookup = entityLookup;
         _userInterfaceManager = userInterfaceManager;
         ZIndex = 200;
-        _font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
+        // Setting this to a specific font would break the antag symbols
+        _font = resourceCache.NotoStack();
+        _fontBold = resourceCache.NotoStack(variation: "Bold");
+
+        config.OnValueChanged(CCVars.AdminOverlayClassic, (show) => { _overlayClassic = show; }, true);
+        config.OnValueChanged(CCVars.AdminOverlaySymbols, (show) => { _overlaySymbols = show; }, true);
+        config.OnValueChanged(CCVars.AdminOverlayPlaytime, (show) => { _overlayPlaytime = show; }, true);
+        config.OnValueChanged(CCVars.AdminOverlayStartingJob, (show) => { _overlayStartingJob = show; }, true);
     }
 
     public override OverlaySpace Space => OverlaySpace.ScreenSpace;
@@ -48,11 +64,6 @@ internal sealed class AdminNameOverlay : Overlay
     {
         var viewport = args.WorldAABB;
 
-        //TODO make this adjustable via GUI
-        var classic = _config.GetCVar(CCVars.AdminOverlayClassic);
-        var playTime = _config.GetCVar(CCVars.AdminOverlayPlaytime);
-        var startingJob = _config.GetCVar(CCVars.AdminOverlayStartingJob);
-
         foreach (var playerInfo in _system.PlayerList)
         {
             var entity = _entityManager.GetEntity(playerInfo.NetEntity);
@@ -85,35 +96,51 @@ internal sealed class AdminNameOverlay : Overlay
 
             var currentOffset = Vector2.Zero;
 
+            // Character name
             args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
             currentOffset += lineoffset;
 
+            // Username
             args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
             currentOffset += lineoffset;
 
-            if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && playTime)
+            // Playtime
+            if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && _overlayPlaytime)
             {
                 args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? Color.Orange : Color.White);
                 currentOffset += lineoffset;
             }
 
-            if (!string.IsNullOrEmpty(playerInfo.StartingJob) && startingJob)
+            // Job
+            if (!string.IsNullOrEmpty(playerInfo.StartingJob) && _overlayStartingJob)
             {
                 args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? Color.GreenYellow : Color.White);
                 currentOffset += lineoffset;
             }
 
-            if (classic && playerInfo.Antag)
+            // Classic Antag Label
+            if (_overlayClassic && playerInfo.Antag)
             {
-                args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, _antagLabelClassic, uiScale, Color.OrangeRed);
+                var symbol = _overlaySymbols ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
+                var label = _overlaySymbols
+                    ? Loc.GetString("player-tab-character-name-antag-symbol",
+                        ("symbol", symbol),
+                        ("name", _antagLabelClassic))
+                    : _antagLabelClassic;
+                args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, _antagColorClassic);
                 currentOffset += lineoffset;
             }
-            else if (!classic && _filter.Contains(playerInfo.RoleProto))
+            // Role Type
+            else if (!_overlayClassic && _filter.Contains(playerInfo.RoleProto))
             {
-                var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
+                var symbol = _overlaySymbols && playerInfo.Antag ? playerInfo.RoleProto.Symbol : string.Empty;
+                var role = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
+                var label = _overlaySymbols
+                ? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", role))
+                : role;
                 var color = playerInfo.RoleProto.Color;
 
-                args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, label, uiScale, color);
+                args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
                 currentOffset += lineoffset;
             }
         }
index ba6ce40ca0b796c32f71dde73d078b493df0bc70..ba56f4694ff990724a5e0b2e45a0533a7335c4ce 100644 (file)
@@ -14,6 +14,7 @@ namespace Content.Client.Administration.Systems
         [Dependency] private readonly IEyeManager _eyeManager = default!;
         [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
         [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
+        [Dependency] private readonly IConfigurationManager _configurationManager = default!;
 
         private AdminNameOverlay _adminNameOverlay = default!;
 
@@ -22,7 +23,14 @@ namespace Content.Client.Administration.Systems
 
         private void InitializeOverlay()
         {
-            _adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager);
+            _adminNameOverlay = new AdminNameOverlay(
+                this,
+                EntityManager,
+                _eyeManager,
+                _resourceCache,
+                _entityLookup,
+                _userInterfaceManager,
+                _configurationManager);
             _adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
         }
 
index b7231869c9edd36feafe2ce95f3590a201b89022..b5b2b0e345611c9f035d0c497b22e974ab973b04 100644 (file)
@@ -2,11 +2,13 @@ using System.Linq;
 using Content.Client.Administration.Systems;
 using Content.Client.UserInterface.Controls;
 using Content.Shared.Administration;
+using Content.Shared.CCVar;
 using Robust.Client.AutoGenerated;
 using Robust.Client.Graphics;
 using Robust.Client.Player;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
 using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
 using static Robust.Client.UserInterface.Controls.BaseButton;
 
@@ -16,6 +18,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab;
 public sealed partial class PlayerTab : Control
 {
     [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IConfigurationManager _config = default!;
     [Dependency] private readonly IPlayerManager _playerMan = default!;
 
     private const string ArrowUp = "↑";
@@ -41,6 +44,10 @@ public sealed partial class PlayerTab : Control
         _adminSystem.OverlayEnabled += OverlayEnabled;
         _adminSystem.OverlayDisabled += OverlayDisabled;
 
+        _config.OnValueChanged(CCVars.AdminPlayerlistSeparateSymbols, PlayerListSettingsChanged);
+        _config.OnValueChanged(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerListSettingsChanged);
+        _config.OnValueChanged(CCVars.AdminPlayerlistRoleTypeColor, PlayerListSettingsChanged);
+
         OverlayButton.OnPressed += OverlayButtonPressed;
         ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
 
@@ -106,6 +113,11 @@ public sealed partial class PlayerTab : Control
 
     #region ListContainer
 
+    private void PlayerListSettingsChanged(bool _)
+    {
+        RefreshPlayerList(_adminSystem.PlayerList);
+    }
+
     private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
     {
         _players = players;
@@ -196,8 +208,7 @@ public sealed partial class PlayerTab : Control
             Header.Username => Compare(x.Username, y.Username),
             Header.Character => Compare(x.CharacterName, y.CharacterName),
             Header.Job => Compare(x.StartingJob, y.StartingJob),
-            Header.Antagonist => x.Antag.CompareTo(y.Antag),
-            Header.RoleType => Compare(x.RoleProto.Name , y.RoleProto.Name),
+            Header.RoleType => y.SortWeight - x.SortWeight,
             Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
             _ => 1
         };
index 54e51747fb9e8028648b7c987bc28e9bf1986d16..a30b9092263be8a69a83dfbdc88db56789c0ca2c 100644 (file)
                HorizontalExpand="True"
                ClipText="True"/>
         <customControls:VSeparator/>
-        <Label Name="AntagonistLabel"
-               SizeFlagsStretchRatio="1"
-               HorizontalExpand="True"
-               ClipText="True"/>
-        <customControls:VSeparator/>
         <Label Name="RoleTypeLabel"
                SizeFlagsStretchRatio="2"
                HorizontalExpand="True"
index e876b8cff5370e4c8b74004ef3c22fc749094a8e..939fe38fc051e288a194a63053203bf44717a31c 100644 (file)
@@ -1,8 +1,10 @@
 using Content.Shared.Administration;
+using Content.Shared.CCVar;
 using Robust.Client.AutoGenerated;
 using Robust.Client.Graphics;
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
 
 namespace Content.Client.Administration.UI.Tabs.PlayerTab;
 
@@ -14,17 +16,25 @@ public sealed partial class PlayerTabEntry : PanelContainer
     public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat)
     {
         RobustXamlLoader.Load(this);
+        var config = IoCManager.Resolve<IConfigurationManager>();
 
         UsernameLabel.Text = player.Username;
         if (!player.Connected)
             UsernameLabel.StyleClasses.Add("Disabled");
         JobLabel.Text = player.StartingJob;
-        CharacterLabel.Text = player.CharacterName;
+        var separateAntagSymbols = config.GetCVar(CCVars.AdminPlayerlistSeparateSymbols);
+        var genericAntagSymbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
+        var roleSymbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
+        var symbol = separateAntagSymbols ? roleSymbol : genericAntagSymbol;
+        CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
+
+        if (player.Antag && config.GetCVar(CCVars.AdminPlayerlistHighlightedCharacterColor))
+            CharacterLabel.FontColorOverride = player.RoleProto.Color;
         if (player.IdentityName != player.CharacterName)
             CharacterLabel.Text += $" [{player.IdentityName}]";
-        AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
         RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
-        RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
+        if (config.GetCVar(CCVars.AdminPlayerlistRoleTypeColor))
+            RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
         BackgroundColorPanel.PanelOverride = styleBoxFlat;
         OverallPlaytimeLabel.Text = player.PlaytimeString;
         PlayerEntity = player.NetEntity;
index 9ae1724945de59d0d3cdeffa89109a18b4e51a44..a192ad017a34c29f33b4ea946f6a0be8e447bd1c 100644 (file)
                Text="{Loc player-tab-job}"
                MouseFilter="Pass"/>
         <cc:VSeparator/>
-        <Label Name="AntagonistLabel"
-               SizeFlagsStretchRatio="1"
-               HorizontalExpand="True"
-               ClipText="True"
-               Text="{Loc player-tab-antagonist}"
-               MouseFilter="Pass"/>
-        <cc:VSeparator/>
         <Label Name="RoleTypeLabel"
                SizeFlagsStretchRatio="2"
                HorizontalExpand="True"
index 5a076a40b63ed626ca910bb0a9ab1377d20940c6..ef505d00f08ea6200cd788e131983b10a30acb0f 100644 (file)
@@ -18,7 +18,6 @@ public sealed partial class PlayerTabHeader : Control
         UsernameLabel.OnKeyBindDown += UsernameClicked;
         CharacterLabel.OnKeyBindDown += CharacterClicked;
         JobLabel.OnKeyBindDown += JobClicked;
-        AntagonistLabel.OnKeyBindDown += AntagonistClicked;
         RoleTypeLabel.OnKeyBindDown += RoleTypeClicked;
         PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
     }
@@ -30,7 +29,6 @@ public sealed partial class PlayerTabHeader : Control
             Header.Username => UsernameLabel,
             Header.Character => CharacterLabel,
             Header.Job => JobLabel,
-            Header.Antagonist => AntagonistLabel,
             Header.RoleType => RoleTypeLabel,
             Header.Playtime => PlaytimeLabel,
             _ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
@@ -42,7 +40,6 @@ public sealed partial class PlayerTabHeader : Control
         UsernameLabel.Text = Loc.GetString("player-tab-username");
         CharacterLabel.Text = Loc.GetString("player-tab-character");
         JobLabel.Text = Loc.GetString("player-tab-job");
-        AntagonistLabel.Text = Loc.GetString("player-tab-antagonist");
         RoleTypeLabel.Text = Loc.GetString("player-tab-roletype");
         PlaytimeLabel.Text = Loc.GetString("player-tab-playtime");
     }
@@ -73,11 +70,6 @@ public sealed partial class PlayerTabHeader : Control
         HeaderClicked(args, Header.Job);
     }
 
-    private void AntagonistClicked(GUIBoundKeyEventArgs args)
-    {
-        HeaderClicked(args, Header.Antagonist);
-    }
-
     private void RoleTypeClicked(GUIBoundKeyEventArgs args)
     {
         HeaderClicked(args, Header.RoleType);
@@ -97,7 +89,6 @@ public sealed partial class PlayerTabHeader : Control
             UsernameLabel.OnKeyBindDown -= UsernameClicked;
             CharacterLabel.OnKeyBindDown -= CharacterClicked;
             JobLabel.OnKeyBindDown -= JobClicked;
-            AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
             RoleTypeLabel.OnKeyBindDown -= RoleTypeClicked;
             PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
         }
@@ -108,7 +99,6 @@ public sealed partial class PlayerTabHeader : Control
         Username,
         Character,
         Job,
-        Antagonist,
         RoleType,
         Playtime
     }
index 38666b7ec107351cf92bc52d3c900a0e249374b8..88bfdbd19e4a32b61f5faf35c5f0a0edb277be13 100644 (file)
@@ -3,8 +3,19 @@
          xmlns:ui="clr-namespace:Content.Client.Options.UI">
     <BoxContainer Orientation="Vertical">
         <ScrollContainer VerticalExpand="True" HScrollEnabled="False">
+
             <BoxContainer Orientation="Vertical" Margin="8">
+                <Label Text="{Loc 'ui-options-admin-player-panel'}"
+                       StyleClasses="LabelKeyText"/>
+                <CheckBox Name="PlayerlistSeparateSymbolsCheckBox" Text="{Loc 'ui-options-admin-playerlist-separate-symbols'}" />
+                <CheckBox Name="PlayerlistCharacterColorCheckBox" Text="{Loc 'ui-options-admin-playerlist-character-color'}" />
+                <CheckBox Name="PlayerlistRoleTypeColorCheckBox" Text="{Loc 'ui-options-admin-playerlist-roletype-color'}" />
+                <Label Text="{Loc 'ui-options-admin-overlay-title'}"
+                       StyleClasses="LabelKeyText"/>
                 <CheckBox Name="EnableClassicOverlayCheckBox" Text="{Loc 'ui-options-enable-classic-overlay'}" />
+                <CheckBox Name="EnableOverlaySymbolsCheckBox" Text="{Loc 'ui-options-enable-overlay-symbols'}" />
+                <CheckBox Name="EnableOverlayPlaytimeCheckBox" Text="{Loc 'ui-options-enable-overlay-playtime'}" />
+                <CheckBox Name="EnableOverlayStartingJobCheckBox" Text="{Loc 'ui-options-enable-overlay-starting-job'}" />
             </BoxContainer>
         </ScrollContainer>
         <ui:OptionsTabControlRow Name="Control" Access="Public" />
index 9661adfa2037154670fc1a9c997ca56e46fb3a86..9bd41e35504bf2320dd9565bda65fc4dd36ccbb8 100644 (file)
@@ -12,7 +12,14 @@ public sealed partial class AdminOptionsTab : Control
     {
         RobustXamlLoader.Load(this);
 
+        Control.AddOptionCheckBox(CCVars.AdminPlayerlistSeparateSymbols, PlayerlistSeparateSymbolsCheckBox);
+        Control.AddOptionCheckBox(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerlistCharacterColorCheckBox);
+        Control.AddOptionCheckBox(CCVars.AdminPlayerlistRoleTypeColor, PlayerlistRoleTypeColorCheckBox);
+
         Control.AddOptionCheckBox(CCVars.AdminOverlayClassic, EnableClassicOverlayCheckBox);
+        Control.AddOptionCheckBox(CCVars.AdminOverlaySymbols, EnableOverlaySymbolsCheckBox);
+        Control.AddOptionCheckBox(CCVars.AdminOverlayPlaytime, EnableOverlayPlaytimeCheckBox);
+        Control.AddOptionCheckBox(CCVars.AdminOverlayStartingJob, EnableOverlayStartingJobCheckBox);
 
         Control.Initialize();
     }
index 7d50730456f4b8b8b47e880e840000b03242eac4..9e0664bd83f946f77253b515a530155c53cdbe4f 100644 (file)
@@ -221,6 +221,7 @@ public sealed class AdminSystem : EntitySystem
         var name = data.UserName;
         var entityName = string.Empty;
         var identityName = string.Empty;
+        var sortWeight = 0;
 
         // Visible (identity) name can be different from real name
         if (session?.AttachedEntity != null)
@@ -234,8 +235,10 @@ public sealed class AdminSystem : EntitySystem
         // Starting role, antagonist status and role type
         RoleTypePrototype roleType = new();
         var startingRole = string.Empty;
-        if (_minds.TryGetMind(session, out var mindId, out var mindComp))
+        if (_minds.TryGetMind(session, out var mindId, out var mindComp) && mindComp is not null)
         {
+            sortWeight = _role.GetRoleCompByTime(mindComp)?.Comp.SortWeight ?? 0;
+
             if (_proto.TryIndex(mindComp.RoleType, out var role))
                 roleType = role;
             else
@@ -259,8 +262,19 @@ public sealed class AdminSystem : EntitySystem
             overallPlaytime = playTime;
         }
 
-        return new PlayerInfo(name, entityName, identityName, startingRole, antag, roleType, GetNetEntity(session?.AttachedEntity), data.UserId,
-            connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime);
+        return new PlayerInfo(
+            name,
+            entityName,
+            identityName,
+            startingRole,
+            antag,
+            roleType,
+            sortWeight,
+            GetNetEntity(session?.AttachedEntity),
+            data.UserId,
+            connected,
+            _roundActivePlayers.Contains(data.UserId),
+            overallPlaytime);
     }
 
     private void OnPanicBunkerChanged(bool enabled)
index b26ecc499f97282bc9fe9c9d36466120063be64f..ef26ab4fca6ba6a3956d5a17005b74c0b75629ee 100644 (file)
@@ -12,6 +12,7 @@ public sealed record PlayerInfo(
     string StartingJob,
     bool Antag,
     RoleTypePrototype RoleProto,
+    int SortWeight,
     NetEntity? NetEntity,
     NetUserId SessionId,
     bool Connected,
index 2c7cca55e53db1596148edf5f9c46b7e3c4751f7..168d2319bfa4de9be563348d4e62f01bb245053f 100644 (file)
@@ -47,11 +47,35 @@ public sealed partial class CCVars
     /// If true, the admin overlay will display the total time of the players
     /// </summary>
     public static readonly CVarDef<bool> AdminOverlayPlaytime =
-        CVarDef.Create("ui.admin_overlay_playtime", false, CVar.CLIENTONLY | CVar.ARCHIVE);
+        CVarDef.Create("ui.admin_overlay_playtime", true, CVar.CLIENTONLY | CVar.ARCHIVE);
 
     /// <summary>
     /// If true, the admin overlay will display the players starting position.
     /// </summary>
     public static readonly CVarDef<bool> AdminOverlayStartingJob =
-        CVarDef.Create("ui.admin_overlay_starting_job", false, CVar.CLIENTONLY | CVar.ARCHIVE);
+        CVarDef.Create("ui.admin_overlay_starting_job", true, CVar.CLIENTONLY | CVar.ARCHIVE);
+
+    /// <summary>
+    /// If true, the admin window player tab will show different antag symbols for each role type
+    /// </summary>
+    public static readonly CVarDef<bool> AdminPlayerlistSeparateSymbols =
+        CVarDef.Create("ui.admin_playerlist_separate_symbols", false, CVar.CLIENTONLY | CVar.ARCHIVE);
+
+    /// <summary>
+    /// If true, characters with antag role types will have their names colored by their role type
+    /// </summary>
+    public static readonly CVarDef<bool> AdminPlayerlistHighlightedCharacterColor =
+        CVarDef.Create("ui.admin_playerlist_highlighted_character_color", true, CVar.CLIENTONLY | CVar.ARCHIVE);
+
+    /// <summary>
+    /// If true, the Role Types column will be colored
+    /// </summary>
+    public static readonly CVarDef<bool> AdminPlayerlistRoleTypeColor =
+        CVarDef.Create("ui.admin_playerlist_role_type_color", true, CVar.CLIENTONLY | CVar.ARCHIVE);
+
+    /// <summary>
+    /// If true, the admin overlay will show antag symbols
+    /// </summary>
+    public static readonly CVarDef<bool> AdminOverlaySymbols =
+        CVarDef.Create("ui.admin_overlay_symbols", true, CVar.CLIENTONLY | CVar.ARCHIVE);
 }
index cad04ce03e3acb7608bca69a9c07464110726bca..8a4253a733b2f94322c2237d11f594b478596aa8 100644 (file)
@@ -22,4 +22,10 @@ public sealed partial class RoleTypePrototype : IPrototype
     /// </summary>
     [DataField]
     public Color Color { get; private set; } = Color.FromHex("#eeeeee");
+
+    /// <summary>
+    ///     A symbol used to represent the role type.
+    /// </summary>
+    [DataField]
+    public string Symbol = string.Empty;
 }
index f1eb7a5516d5651bd80674128b13bd3d178c82ab..a52a3f5c02d7fc250ec3c1adf71ceeec55cdb098 100644 (file)
@@ -45,6 +45,12 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent
     /// </summary>
     [DataField]
     public ProtoId<JobPrototype>? JobPrototype { get; set; }
+
+    /// <summary>
+    ///     Used to order the characters on by role/antag status. Highest numbers are shown first.
+    /// </summary>
+    [DataField]
+    public int SortWeight;
 }
 
 // Why does this base component actually exist? It does make auto-categorization easy, but before that it was useless?
index 583c81d680e96ce2466a35fb70c8439de8b90b4c..d6f108faaff9815ec81c825e807700a00888c23e 100644 (file)
@@ -211,20 +211,30 @@ public abstract class SharedRoleSystem : EntitySystem
         return true;
     }
 
+    /// <summary>
+    ///     Return the most recently specified role type, or Neutral
+    /// </summary>
     private ProtoId<RoleTypePrototype> GetRoleTypeByTime(MindComponent mind)
     {
-        // If any Mind Roles specify a Role Type, return the most recent. Otherwise return Neutral
+        var role = GetRoleCompByTime(mind);
+        return role?.Comp?.RoleType ?? "Neutral";
+    }
 
-        var roles = new List<ProtoId<RoleTypePrototype>>();
+    /// <summary>
+    ///     Return the most recently specified role type's mind role entity, or null
+    /// </summary>
+    public Entity<MindRoleComponent>? GetRoleCompByTime(MindComponent mind)
+    {
+        var roles = new List<Entity<MindRoleComponent>>();
 
         foreach (var role in mind.MindRoles)
         {
             var comp = Comp<MindRoleComponent>(role);
             if (comp.RoleType is not null)
-                roles.Add(comp.RoleType.Value);
+                roles.Add((role, comp));
         }
 
-        ProtoId<RoleTypePrototype> result = (roles.Count > 0) ? roles.LastOrDefault() : "Neutral";
+        Entity<MindRoleComponent>? result = roles.Count > 0 ? roles.LastOrDefault() : null;
         return (result);
     }
 
index 29bfb978e2140470b5105bbd4ff52327680412d6..f6437c41f7a46f4fe075fff5908fd69e3595e0ee 100644 (file)
@@ -2,7 +2,6 @@
 player-tab-username = Username
 player-tab-character = Character
 player-tab-job = Job
-player-tab-antagonist = Antagonist
 player-tab-roletype = Role Type
 player-tab-playtime = Playtime
 player-tab-show-disconnected = Show Disconnected
@@ -11,3 +10,7 @@ player-tab-entry-tooltip = Playtime is displayed in days:hours:minutes.
 player-tab-filter-line-edit-placeholder = Filter
 player-tab-is-antag-yes = YES
 player-tab-is-antag-no = NO
+
+player-tab-character-name-antag-symbol = {$symbol} {$name}
+
+player-tab-antag-prefix = 🗡
index cdac9d9544b67fc1c7dda0060a52b0f41a9edea3..413ae2f695f544b60ee7b9ec13005715da9dae99 100644 (file)
@@ -337,4 +337,14 @@ ui-options-censor-nudity = Censor character nudity
 
 ## Admin menu
 
-ui-options-enable-classic-overlay = Revert antag overlay to classic mode
+ui-options-admin-player-panel = Admin Menu Players List
+
+ui-options-admin-playerlist-separate-symbols = Show separate symbols for each antag role type
+ui-options-admin-playerlist-character-color = Color names of antagonist characters
+ui-options-admin-playerlist-roletype-color = Color role types
+
+ui-options-admin-overlay-title = Admin Overlay
+ui-options-enable-classic-overlay = Revert overlay to classic mode
+ui-options-enable-overlay-symbols = Add antag symbol to text
+ui-options-enable-overlay-playtime = Show playtime
+ui-options-enable-overlay-starting-job = Show starting job
index 31e8a98e7ac7a5068e21dc5efe14240de3f0f83a..cdab1c6150bef44264e42e575b6399fc9fbd9697 100644 (file)
@@ -15,6 +15,7 @@
     antag: true
     antagPrototype: GenericAntagonist
     roleType: SoloAntagonist
+    sortWeight: 50
 
 #Observer
 - type: entity
@@ -23,6 +24,8 @@
   name: Observer Role
   components:
   - type: ObserverRole
+  - type: MindRole
+    sortWeight: -10
 
 #Ghost Roles
 - type: entity
@@ -48,6 +51,7 @@
   - type: MindRole
     roleType: FreeAgent
     antagPrototype: GenericFreeAgent
+    sortWeight: 30
 
 - type: entity
   parent: MindRoleGhostRoleNeutral
@@ -56,6 +60,7 @@
   components:
   - type: MindRole
     roleType: FreeAgent
+    sortWeight: 0 # Maybe 10?
 
 - type: entity
   parent: MindRoleGhostRoleNeutral
@@ -73,6 +78,7 @@
   - type: MindRole
     roleType: SiliconAntagonist
     antagPrototype: GenericSiliconAntagonist
+    sortWeight: 30
 
 - type: entity
   parent: [ BaseMindRoleAntag, MindRoleGhostRoleNeutral ]
   parent: MindRoleGhostRoleTeamAntagonist
   id: MindRoleGhostRoleTeamAntagonistFlock
   name: Ghost Role (Team Antagonist)
+  components:
+  - type: MindRole
+    sortWeight: 40
 
 # The Job MindRole holds the mob's Job prototype
 - type: entity
index 97e11d80b27f057a183906593e42dd479e51b21c..b395286dc0dab917dc27d2fcfc369d5a375ee71d 100644 (file)
@@ -5,34 +5,40 @@
   id: Neutral
   name: role-type-crew-aligned-name
   color: '#eeeeee'
+  symbol: "🗡" # Should never be antag, but just in case.
 
 - type: roleType
   id: SoloAntagonist
   name: role-type-solo-antagonist-name
   color: '#d82000'
+  symbol: "🗡"
 
 - type: roleType
   id: TeamAntagonist
   name: role-type-team-antagonist-name
   color: '#d82000'
+  symbol: "⚔"
 
 - type: roleType
   id: FreeAgent
   name: role-type-free-agent-name
   color: '#ffff00'
+  symbol: "☯"
 
 - type: roleType
   id: Familiar
   name: role-type-familiar-name
   color: '#6495ed'
+  symbol: "🗡" # Should never be antag, but just in case.
 
 - type: roleType
   id: Silicon
   name: role-type-silicon-name
   color: '#6495ed'
+  symbol: "🗡" # Should never be antag, but just in case.
 
 - type: roleType
   id: SiliconAntagonist
   name: role-type-silicon-antagonist-name
   color: '#c832e6'
-
+  symbol: "⛞"