From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Wed, 16 Apr 2025 17:04:48 +0000 (+0200) Subject: Role subtypes (#35359) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=fb388d2265ec34872883c1e5d4405517d8d917c2;p=space-station-14.git Role subtypes (#35359) --- diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs index c0f31f1e3d..0d424cbff0 100644 --- a/Content.Client/Administration/AdminNameOverlay.cs +++ b/Content.Client/Administration/AdminNameOverlay.cs @@ -6,6 +6,7 @@ using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.Ghost; using Content.Shared.Mind; +using Content.Shared.Roles; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; @@ -22,10 +23,11 @@ internal sealed class AdminNameOverlay : Overlay private readonly IEyeManager _eyeManager; private readonly EntityLookupSystem _entityLookup; private readonly IUserInterfaceManager _userInterfaceManager; + private readonly SharedRoleSystem _roles; private readonly Font _font; private readonly Font _fontBold; - private bool _overlayClassic; - private bool _overlaySymbols; + private AdminOverlayAntagFormat _overlayFormat; + private AdminOverlayAntagSymbolStyle _overlaySymbolStyle; private bool _overlayPlaytime; private bool _overlayStartingJob; private float _ghostFadeDistance; @@ -33,9 +35,10 @@ internal sealed class AdminNameOverlay : Overlay private int _overlayStackMax; private float _overlayMergeDistance; - //TODO make this adjustable via GUI + //TODO make this adjustable via GUI? private readonly ProtoId[] _filter = ["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"]; + private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic"); public AdminNameOverlay( @@ -45,20 +48,22 @@ internal sealed class AdminNameOverlay : Overlay IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager, - IConfigurationManager config) + IConfigurationManager config, + SharedRoleSystem roles) { _system = system; _entityManager = entityManager; _eyeManager = eyeManager; _entityLookup = entityLookup; _userInterfaceManager = userInterfaceManager; + _roles = roles; ZIndex = 200; // Setting these to a specific ttf 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.AdminOverlayAntagFormat, (show) => { _overlayFormat = UpdateOverlayFormat(show); }, true); + config.OnValueChanged(CCVars.AdminOverlaySymbolStyle, (show) => { _overlaySymbolStyle = UpdateOverlaySymbolStyle(show); }, true); config.OnValueChanged(CCVars.AdminOverlayPlaytime, (show) => { _overlayPlaytime = show; }, true); config.OnValueChanged(CCVars.AdminOverlayStartingJob, (show) => { _overlayStartingJob = show; }, true); config.OnValueChanged(CCVars.AdminOverlayGhostHideDistance, (f) => { _ghostHideDistance = f; }, true); @@ -67,6 +72,22 @@ internal sealed class AdminNameOverlay : Overlay config.OnValueChanged(CCVars.AdminOverlayMergeDistance, (f) => { _overlayMergeDistance = f; }, true); } + private AdminOverlayAntagFormat UpdateOverlayFormat(string formatString) + { + if (!Enum.TryParse(formatString, out var format)) + format = AdminOverlayAntagFormat.Binary; + + return format; + } + + private AdminOverlayAntagSymbolStyle UpdateOverlaySymbolStyle(string symbolString) + { + if (!Enum.TryParse(symbolString, out var symbolStyle)) + symbolStyle = AdminOverlayAntagSymbolStyle.Off; + + return symbolStyle; + } + public override OverlaySpace Space => OverlaySpace.ScreenSpace; protected override void Draw(in OverlayDrawArgs args) @@ -183,34 +204,56 @@ internal sealed class AdminNameOverlay : Overlay currentOffset += lineoffset; } - // Classic Antag Label - if (_overlayClassic && playerInfo.Antag) + // Determine antag symbol + string? symbol; + switch (_overlaySymbolStyle) { - 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; - color = Color.OrangeRed; - color.A = alpha; - args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color); - currentOffset += lineoffset; + case AdminOverlayAntagSymbolStyle.Specific: + symbol = playerInfo.RoleProto.Symbol; + break; + case AdminOverlayAntagSymbolStyle.Basic: + symbol = Loc.GetString("player-tab-antag-prefix"); + break; + default: + case AdminOverlayAntagSymbolStyle.Off: + symbol = string.Empty; + break; } - // Role Type - else if (!_overlayClassic && _filter.Contains(playerInfo.RoleProto)) + + // Determine antag/role type name + string? text; + switch (_overlayFormat) { - 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; - color = playerInfo.RoleProto.Color; - color.A = alpha; - args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color); - currentOffset += lineoffset; + case AdminOverlayAntagFormat.Roletype: + color = playerInfo.RoleProto.Color; + symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty; + text = _filter.Contains(playerInfo.RoleProto) + ? Loc.GetString(playerInfo.RoleProto.Name).ToUpper() + : string.Empty; + break; + case AdminOverlayAntagFormat.Subtype: + color = playerInfo.RoleProto.Color; + symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty; + text = _filter.Contains(playerInfo.RoleProto) + ? _roles.GetRoleSubtypeLabel(playerInfo.RoleProto.Name, playerInfo.Subtype).ToUpper() + : string.Empty; + break; + default: + case AdminOverlayAntagFormat.Binary: + color = Color.OrangeRed; + symbol = playerInfo.Antag ? symbol : string.Empty; + text = playerInfo.Antag ? _antagLabelClassic : string.Empty; + break; } + // Draw antag label + color.A = alpha; + var label = !string.IsNullOrEmpty(symbol) + ? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", text)) + : text; + args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color); + currentOffset += lineoffset; + //Save the coordinates and size of the text block, for stack merge check drawnOverlays.Add((screenCoordinatesCenter, currentOffset)); } diff --git a/Content.Client/Administration/OverlayOptions.cs b/Content.Client/Administration/OverlayOptions.cs new file mode 100644 index 0000000000..39195603bf --- /dev/null +++ b/Content.Client/Administration/OverlayOptions.cs @@ -0,0 +1,15 @@ +namespace Content.Client.Administration; + +public enum AdminOverlayAntagFormat +{ + Binary, + Roletype, + Subtype +} + +public enum AdminOverlayAntagSymbolStyle +{ + Off, + Basic, + Specific +} diff --git a/Content.Client/Administration/Systems/AdminSystem.Overlay.cs b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs index ba56f4694f..a630df4521 100644 --- a/Content.Client/Administration/Systems/AdminSystem.Overlay.cs +++ b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs @@ -1,4 +1,5 @@ using Content.Client.Administration.Managers; +using Content.Shared.Roles; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; @@ -15,6 +16,7 @@ namespace Content.Client.Administration.Systems [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; private AdminNameOverlay _adminNameOverlay = default!; @@ -30,7 +32,8 @@ namespace Content.Client.Administration.Systems _resourceCache, _entityLookup, _userInterfaceManager, - _configurationManager); + _configurationManager, + _roles); _adminManager.AdminStatusUpdated += OnAdminStatusUpdated; } @@ -46,7 +49,8 @@ namespace Content.Client.Administration.Systems public void AdminOverlayOn() { - if (_overlayManager.HasOverlay()) return; + if (_overlayManager.HasOverlay()) + return; _overlayManager.AddOverlay(_adminNameOverlay); OverlayEnabled?.Invoke(); } diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs index b5b2b0e345..2a25d9e0aa 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs @@ -32,6 +32,10 @@ public sealed partial class PlayerTab : Control private bool _ascending = true; private bool _showDisconnected; + private AdminPlayerTabColorOption _playerTabColorSetting; + private AdminPlayerTabRoleTypeOption _playerTabRoleSetting; + private AdminPlayerTabSymbolOption _playerTabSymbolSetting; + public event Action? OnEntryKeyBindDown; public PlayerTab() @@ -44,9 +48,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); + _config.OnValueChanged(CCVars.AdminPlayerTabRoleSetting, RoleSettingChanged, true); + _config.OnValueChanged(CCVars.AdminPlayerTabColorSetting, ColorSettingChanged, true); + _config.OnValueChanged(CCVars.AdminPlayerTabSymbolSetting, SymbolSettingChanged, true); + OverlayButton.OnPressed += OverlayButtonPressed; ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed; @@ -113,8 +118,27 @@ public sealed partial class PlayerTab : Control #region ListContainer - private void PlayerListSettingsChanged(bool _) + private void RoleSettingChanged(string s) + { + if (!Enum.TryParse(s, out var format)) + format = AdminPlayerTabRoleTypeOption.Subtype; + _playerTabRoleSetting = format; + RefreshPlayerList(_adminSystem.PlayerList); + } + + private void ColorSettingChanged(string s) + { + if (!Enum.TryParse(s, out var format)) + format = AdminPlayerTabColorOption.Both; + _playerTabColorSetting = format; + RefreshPlayerList(_adminSystem.PlayerList); + } + + private void SymbolSettingChanged(string s) { + if (!Enum.TryParse(s, out var format)) + format = AdminPlayerTabSymbolOption.Specific; + _playerTabSymbolSetting = format; RefreshPlayerList(_adminSystem.PlayerList); } @@ -140,7 +164,12 @@ public sealed partial class PlayerTab : Control if (data is not PlayerListData { Info: var player}) return; - var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor)); + var entry = new PlayerTabEntry( + player, + new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor), + _playerTabColorSetting, + _playerTabRoleSetting, + _playerTabSymbolSetting); button.AddChild(entry); button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}"; } diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs index 939fe38fc0..41e3fca761 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs @@ -1,42 +1,99 @@ using Content.Shared.Administration; -using Content.Shared.CCVar; +using Content.Shared.Roles; 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; [GenerateTypedNameReferences] public sealed partial class PlayerTabEntry : PanelContainer { - public NetEntity? PlayerEntity; + [Dependency] private readonly IEntityManager _entMan = default!; - public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat) + public PlayerTabEntry( + PlayerInfo player, + StyleBoxFlat styleBoxFlat, + AdminPlayerTabColorOption colorOption, + AdminPlayerTabRoleTypeOption roleSetting, + AdminPlayerTabSymbolOption symbolSetting) { + IoCManager.InjectDependencies(this); RobustXamlLoader.Load(this); - var config = IoCManager.Resolve(); + var roles = _entMan.System(); UsernameLabel.Text = player.Username; if (!player.Connected) UsernameLabel.StyleClasses.Add("Disabled"); JobLabel.Text = player.StartingJob; - 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; + + var colorAntags = false; + var colorRoles = false; + switch (colorOption) + { + case AdminPlayerTabColorOption.Off: + break; + case AdminPlayerTabColorOption.Character: + colorAntags = true; + break; + case AdminPlayerTabColorOption.Roletype: + colorRoles = true; + break; + default: + case AdminPlayerTabColorOption.Both: + colorAntags = true; + colorRoles = true; + break; + } + + var symbol = string.Empty; + switch (symbolSetting) + { + case AdminPlayerTabSymbolOption.Off: + break; + case AdminPlayerTabSymbolOption.Basic: + symbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty; + break; + default: + case AdminPlayerTabSymbolOption.Specific: + symbol = player.Antag ? player.RoleProto.Symbol : string.Empty; + break; + } + CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName)); - if (player.Antag && config.GetCVar(CCVars.AdminPlayerlistHighlightedCharacterColor)) + if (player.Antag && colorAntags) CharacterLabel.FontColorOverride = player.RoleProto.Color; if (player.IdentityName != player.CharacterName) CharacterLabel.Text += $" [{player.IdentityName}]"; - RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name); - if (config.GetCVar(CCVars.AdminPlayerlistRoleTypeColor)) + + var roletype = RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name); + var subtype = roles.GetRoleSubtypeLabel(player.RoleProto.Name, player.Subtype); + switch (roleSetting) + { + case AdminPlayerTabRoleTypeOption.RoleTypeSubtype: + RoleTypeLabel.Text = roletype != subtype + ? roletype + " - " +subtype + : roletype; + break; + case AdminPlayerTabRoleTypeOption.SubtypeRoleType: + RoleTypeLabel.Text = roletype != subtype + ? subtype + " - " + roletype + : roletype; + break; + case AdminPlayerTabRoleTypeOption.RoleType: + RoleTypeLabel.Text = roletype; + break; + default: + case AdminPlayerTabRoleTypeOption.Subtype: + RoleTypeLabel.Text = subtype; + break; + } + + if (colorRoles) RoleTypeLabel.FontColorOverride = player.RoleProto.Color; BackgroundColorPanel.PanelOverride = styleBoxFlat; OverallPlaytimeLabel.Text = player.PlaytimeString; - PlayerEntity = player.NetEntity; } } diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabOptions.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabOptions.cs new file mode 100644 index 0000000000..e9eab24710 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabOptions.cs @@ -0,0 +1,24 @@ +namespace Content.Client.Administration.UI.Tabs.PlayerTab; + +public enum AdminPlayerTabColorOption +{ + Off, + Character, + Roletype, + Both +} + +public enum AdminPlayerTabRoleTypeOption +{ + RoleType, + Subtype, + RoleTypeSubtype, + SubtypeRoleType +} + +public enum AdminPlayerTabSymbolOption +{ + Off, + Basic, + Specific +} diff --git a/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml b/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml index 449c16ad91..ca22d7206b 100644 --- a/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml +++ b/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml @@ -6,18 +6,18 @@ diff --git a/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs b/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs index d2e56d80ed..a25d83d337 100644 --- a/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs @@ -1,3 +1,5 @@ +using Content.Client.Administration; +using Content.Client.Administration.UI.Tabs.PlayerTab; using Content.Shared.CCVar; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; @@ -19,12 +21,43 @@ public sealed partial class AdminOptionsTab : Control { RobustXamlLoader.Load(this); - Control.AddOptionCheckBox(CCVars.AdminPlayerlistSeparateSymbols, PlayerlistSeparateSymbolsCheckBox); - Control.AddOptionCheckBox(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerlistCharacterColorCheckBox); - Control.AddOptionCheckBox(CCVars.AdminPlayerlistRoleTypeColor, PlayerlistRoleTypeColorCheckBox); + var antagFormats = new List.ValueOption>(); + foreach (var format in Enum.GetValues(typeof(AdminOverlayAntagFormat))) + { + antagFormats.Add(new OptionDropDownCVar.ValueOption(format.ToString()!, Loc.GetString($"ui-options-admin-overlay-antag-format-{format.ToString()!.ToLower()}"))); + } + + var antagSymbolStyles = new List.ValueOption>(); + foreach (var symbol in Enum.GetValues(typeof(AdminOverlayAntagSymbolStyle))) + { + antagSymbolStyles.Add(new OptionDropDownCVar.ValueOption(symbol.ToString()!, Loc.GetString($"ui-options-admin-overlay-antag-symbol-{symbol.ToString()!.ToLower()}"))); + } + + var playerTabColorSettings = new List.ValueOption>(); + foreach (var setting in Enum.GetValues(typeof(AdminPlayerTabColorOption))) + { + playerTabColorSettings.Add(new OptionDropDownCVar.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-color-setting-{setting.ToString()!.ToLower()}"))); + } + + var playerTabRoleSettings = new List.ValueOption>(); + foreach (var setting in Enum.GetValues(typeof(AdminPlayerTabRoleTypeOption))) + { + playerTabRoleSettings.Add(new OptionDropDownCVar.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-role-setting-{setting.ToString()!.ToLower()}"))); + } + + var playerTabSymbolSettings = new List.ValueOption>(); + foreach (var setting in Enum.GetValues(typeof(AdminPlayerTabSymbolOption))) + { + playerTabSymbolSettings.Add(new OptionDropDownCVar.ValueOption(setting.ToString()!, Loc.GetString($"ui-options-admin-player-tab-symbol-setting-{setting.ToString()!.ToLower()}"))); + } + + Control.AddOptionDropDown(CCVars.AdminPlayerTabSymbolSetting, DropDownPlayerTabSymbolSetting, playerTabSymbolSettings); + Control.AddOptionDropDown(CCVars.AdminPlayerTabRoleSetting, DropDownPlayerTabRoleSetting, playerTabRoleSettings); + Control.AddOptionDropDown(CCVars.AdminPlayerTabColorSetting, DropDownPlayerTabColorSetting, playerTabColorSettings); + + Control.AddOptionDropDown(CCVars.AdminOverlayAntagFormat, DropDownOverlayAntagFormat, antagFormats); + Control.AddOptionDropDown(CCVars.AdminOverlaySymbolStyle, DropDownOverlayAntagSymbol, antagSymbolStyles); - Control.AddOptionCheckBox(CCVars.AdminOverlayClassic, EnableClassicOverlayCheckBox); - Control.AddOptionCheckBox(CCVars.AdminOverlaySymbols, EnableOverlaySymbolsCheckBox); Control.AddOptionCheckBox(CCVars.AdminOverlayPlaytime, EnableOverlayPlaytimeCheckBox); Control.AddOptionCheckBox(CCVars.AdminOverlayStartingJob, EnableOverlayStartingJobCheckBox); diff --git a/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs b/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs index e69f577407..4fd395bbb4 100644 --- a/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs +++ b/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs @@ -221,18 +221,11 @@ public sealed class CharacterUIController : UIController, IOnStateEntered(container.Mind.Value, out var mind)) return; - var roleText = Loc.GetString("role-type-crew-aligned-name"); - var color = Color.White; - if (_prototypeManager.TryIndex(mind.RoleType, out var proto)) - { - roleText = Loc.GetString(proto.Name); - color = proto.Color; - } - else - _sawmill.Error($"{_player.LocalEntity} has invalid Role Type '{mind.RoleType}'. Displaying '{roleText}' instead"); + if (!_prototypeManager.TryIndex(mind.RoleType, out var proto)) + _sawmill.Error($"Player '{_player.LocalSession}' has invalid Role Type '{mind.RoleType}'. Displaying default instead"); - _window.RoleType.Text = roleText; - _window.RoleType.FontColorOverride = color; + _window.RoleType.Text = Loc.GetString(proto?.Name ?? "role-type-crew-aligned-name"); + _window.RoleType.FontColorOverride = proto?.Color ?? Color.White; } private void CharacterDetached(EntityUid uid) diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index 9e0664bd83..0868157f6e 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -235,12 +235,16 @@ public sealed class AdminSystem : EntitySystem // Starting role, antagonist status and role type RoleTypePrototype roleType = new(); var startingRole = string.Empty; + LocId? subtype = null; 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; + subtype = mindComp.Subtype; + } else Log.Error($"{ToPrettyString(mindId)} has invalid Role Type '{mindComp.RoleType}'. Displaying '{Loc.GetString(roleType.Name)}' instead"); @@ -269,6 +273,7 @@ public sealed class AdminSystem : EntitySystem startingRole, antag, roleType, + subtype, sortWeight, GetNetEntity(session?.AttachedEntity), data.UserId, diff --git a/Content.Shared/Administration/PlayerInfo.cs b/Content.Shared/Administration/PlayerInfo.cs index ef26ab4fca..5164425347 100644 --- a/Content.Shared/Administration/PlayerInfo.cs +++ b/Content.Shared/Administration/PlayerInfo.cs @@ -12,6 +12,7 @@ public sealed record PlayerInfo( string StartingJob, bool Antag, RoleTypePrototype RoleProto, + LocId? Subtype, int SortWeight, NetEntity? NetEntity, NetUserId SessionId, diff --git a/Content.Shared/CCVar/CCVars.Interface.cs b/Content.Shared/CCVar/CCVars.Interface.cs index 85e06def61..fadf7a4862 100644 --- a/Content.Shared/CCVar/CCVars.Interface.cs +++ b/Content.Shared/CCVar/CCVars.Interface.cs @@ -38,10 +38,13 @@ public sealed partial class CCVars CVarDef.Create("outline.enabled", true, CVar.CLIENTONLY); /// - /// If true, the admin overlay will be displayed in the old style (showing only "ANTAG") + /// Determines how antagonist status/roletype is displayed. Based on AdminOverlayAntagFormats enum + /// Binary: Roletypes of interest get an "ANTAG" label + /// Roletype: Roletypes of interest will have their roletype name displayed in their specific color + /// Subtype: Roletypes of interest will have their subtype displayed. if subtype is not set, roletype will be shown. /// - public static readonly CVarDef AdminOverlayClassic = - CVarDef.Create("ui.admin_overlay_classic", false, CVar.CLIENTONLY | CVar.ARCHIVE); + public static readonly CVarDef AdminOverlayAntagFormat = + CVarDef.Create("ui.admin_overlay_antag_format", "Subtype", CVar.CLIENTONLY | CVar.ARCHIVE); /// /// If true, the admin overlay will display the total time of the players @@ -50,34 +53,48 @@ public sealed partial class CCVars CVarDef.Create("ui.admin_overlay_playtime", true, CVar.CLIENTONLY | CVar.ARCHIVE); /// - /// If true, the admin overlay will display the players starting position. + /// If true, the admin overlay will display the player's starting role. /// public static readonly CVarDef AdminOverlayStartingJob = CVarDef.Create("ui.admin_overlay_starting_job", true, CVar.CLIENTONLY | CVar.ARCHIVE); /// - /// If true, the admin window player tab will show different antag symbols for each role type + /// Determines how antagonist status/roletype is displayed Before character names on the Player Tab + /// Off: No symbol is shown. + /// Basic: The same antag symbol is shown for anyone marked as antag. + /// Specific: The roletype-specific symbol is shown for anyone marked as antag. /// - public static readonly CVarDef AdminPlayerlistSeparateSymbols = - CVarDef.Create("ui.admin_playerlist_separate_symbols", false, CVar.CLIENTONLY | CVar.ARCHIVE); + public static readonly CVarDef AdminPlayerTabSymbolSetting = + CVarDef.Create("ui.admin_player_tab_symbols", "Specific", CVar.CLIENTONLY | CVar.ARCHIVE); /// - /// If true, characters with antag role types will have their names colored by their role type + /// Determines what columns are colorized + /// Off: None. + /// Character: The character names of "roletypes-of-interest" have their role type's color. + /// Roletype: Role types are shown in their respective colors. + /// Both: Both characters and role types are colorized. /// - public static readonly CVarDef AdminPlayerlistHighlightedCharacterColor = - CVarDef.Create("ui.admin_playerlist_highlighted_character_color", true, CVar.CLIENTONLY | CVar.ARCHIVE); + public static readonly CVarDef AdminPlayerTabColorSetting = + CVarDef.Create("ui.admin_player_tab_color", "Both", CVar.CLIENTONLY | CVar.ARCHIVE); /// - /// If true, the Role Types column will be colored + /// Determines what's displayed in the Role column - role type, subtype, or both. + /// RoleType + /// SubType + /// RoleTypeSubtype + /// SubtypeRoleType /// - public static readonly CVarDef AdminPlayerlistRoleTypeColor = - CVarDef.Create("ui.admin_playerlist_role_type_color", true, CVar.CLIENTONLY | CVar.ARCHIVE); + public static readonly CVarDef AdminPlayerTabRoleSetting = + CVarDef.Create("ui.admin_player_tab_role", "Subtype", CVar.CLIENTONLY | CVar.ARCHIVE); /// - /// If true, the admin overlay will show antag symbols + /// Determines how antagonist status/roletype is displayed. Based on AdminOverlayAntagSymbolStyles enum + /// Off: No symbol is shown. + /// Basic: The same antag symbol is shown for anyone marked as antag. + /// Specific: The roletype-specific symbol is shown for anyone marked as antag. /// - public static readonly CVarDef AdminOverlaySymbols = - CVarDef.Create("ui.admin_overlay_symbols", true, CVar.CLIENTONLY | CVar.ARCHIVE); + public static readonly CVarDef AdminOverlaySymbolStyle = + CVarDef.Create("ui.admin_overlay_symbol_style", "Specific", CVar.CLIENTONLY | CVar.ARCHIVE); /// /// The range (in tiles) around the cursor within which the admin overlays of ghosts start to fade out diff --git a/Content.Shared/Mind/MindComponent.cs b/Content.Shared/Mind/MindComponent.cs index fa48511a2a..3b2b934478 100644 --- a/Content.Shared/Mind/MindComponent.cs +++ b/Content.Shared/Mind/MindComponent.cs @@ -108,6 +108,12 @@ public sealed partial class MindComponent : Component [DataField, AutoNetworkedField] public ProtoId RoleType = "Neutral"; + /// + /// The role's subtype, shown only to admins to help with antag categorization + /// + [DataField] + public LocId? Subtype; + /// /// The session of the player owning this mind. /// Can be null, in which case the player is currently not logged in. diff --git a/Content.Shared/Mind/RoleTypePrototype.cs b/Content.Shared/Mind/RoleTypePrototype.cs index 8a4253a733..03f88ba751 100644 --- a/Content.Shared/Mind/RoleTypePrototype.cs +++ b/Content.Shared/Mind/RoleTypePrototype.cs @@ -21,7 +21,7 @@ public sealed partial class RoleTypePrototype : IPrototype /// The role's displayed color. /// [DataField] - public Color Color { get; private set; } = Color.FromHex("#eeeeee"); + public Color Color = Color.FromHex("#eeeeee"); /// /// A symbol used to represent the role type. diff --git a/Content.Shared/Roles/MindRoleComponent.cs b/Content.Shared/Roles/MindRoleComponent.cs index a52a3f5c02..09593c94cd 100644 --- a/Content.Shared/Roles/MindRoleComponent.cs +++ b/Content.Shared/Roles/MindRoleComponent.cs @@ -15,7 +15,7 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent /// A single antag Mind Role is enough to make the owner mind count as Antagonist. /// [DataField] - public bool Antag { get; set; } = false; + public bool Antag; /// /// The mind's current antagonist/special role, or lack thereof; @@ -23,11 +23,17 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent [DataField] public ProtoId? RoleType; + /// + /// The role's subtype, shown only to admins to help with antag categorization + /// + [DataField] + public LocId? Subtype; + /// /// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True. /// [DataField] - public bool ExclusiveAntag { get; set; } = false; + public bool ExclusiveAntag; /// /// The Mind that this role belongs to diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index d6f108faaf..83ef6cbf23 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -6,7 +6,6 @@ using Content.Shared.Database; using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Roles.Jobs; -using Content.Shared.Silicons.Borgs.Components; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; @@ -202,22 +201,22 @@ public abstract class SharedRoleSystem : EntitySystem return false; //get the most important/latest mind role - var roleType = GetRoleTypeByTime(ent.Comp); + var (roleType, subtype) = GetRoleTypeByTime(ent.Comp); - if (ent.Comp.RoleType == roleType) + if (ent.Comp.RoleType == roleType && ent.Comp.Subtype == subtype) return false; - SetRoleType(ent.Owner, roleType); + SetRoleType(ent.Owner, roleType, subtype); return true; } /// - /// Return the most recently specified role type, or Neutral + /// Return the most recently specified role type and subtype, or Neutral /// - private ProtoId GetRoleTypeByTime(MindComponent mind) + private (ProtoId, LocId?) GetRoleTypeByTime(MindComponent mind) { var role = GetRoleCompByTime(mind); - return role?.Comp?.RoleType ?? "Neutral"; + return (role?.Comp?.RoleType ?? "Neutral", role?.Comp?.Subtype); } /// @@ -238,21 +237,22 @@ public abstract class SharedRoleSystem : EntitySystem return (result); } - private void SetRoleType(EntityUid mind, ProtoId roleTypeId) + private void SetRoleType(EntityUid mind, ProtoId roleTypeId, LocId? subtype) { if (!TryComp(mind, out var comp)) { - Log.Error($"Failed to update Role Type of mind entity {ToPrettyString(mind)} to {roleTypeId}. MindComponent not found."); + Log.Error($"Failed to update Role Type of mind entity {ToPrettyString(mind)} to {roleTypeId}, {subtype}. MindComponent not found."); return; } if (!_prototypes.HasIndex(roleTypeId)) { - Log.Error($"Failed to change Role Type of {_minds.MindOwnerLoggingString(comp)} to {roleTypeId}. Invalid role"); + Log.Error($"Failed to change Role Type of {_minds.MindOwnerLoggingString(comp)} to {roleTypeId}, {subtype}. Invalid role"); return; } comp.RoleType = roleTypeId; + comp.Subtype = subtype; Dirty(mind, comp); // Update player character window @@ -269,13 +269,13 @@ public abstract class SharedRoleSystem : EntitySystem Log.Error($"{ToPrettyString(mind)} does not have an OwnedEntity!"); _adminLogger.Add(LogType.Mind, LogImpact.Medium, - $"Role Type of {ToPrettyString(mind)} changed to {roleTypeId}"); + $"Role Type of {ToPrettyString(mind)} changed to {roleTypeId}, {subtype}"); return; } _adminLogger.Add(LogType.Mind, LogImpact.High, - $"Role Type of {ToPrettyString(comp.OwnedEntity)} changed to {roleTypeId}"); + $"Role Type of {ToPrettyString(comp.OwnedEntity)} changed to {roleTypeId}, {subtype}"); } /// @@ -630,6 +630,14 @@ public abstract class SharedRoleSystem : EntitySystem return antag.Requirements; } + + /// + /// Returns the localized name of a role type's subtype. If the provided subtype parameter turns out to be empty, it returns the localized name of the role type instead. + /// + public string GetRoleSubtypeLabel(LocId roleType, LocId? subtype) + { + return string.IsNullOrEmpty(subtype) ? Loc.GetString(roleType) : Loc.GetString(subtype); + } } /// diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index 35ae20ec3c..95c002b745 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -339,15 +339,37 @@ ui-options-censor-nudity = Censor character nudity 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-player-tab-symbol-setting = Character column antag symbols +ui-options-admin-player-tab-symbol-setting-off = No antag symbol +ui-options-admin-player-tab-symbol-setting-basic = Show standard antag symbol +ui-options-admin-player-tab-symbol-setting-specific = Show specific antag symbol + +ui-options-admin-player-tab-role-setting = Role display settings +ui-options-admin-player-tab-role-setting-roletype = Show role type +ui-options-admin-player-tab-role-setting-subtype = Show subtype +ui-options-admin-player-tab-role-setting-roletypesubtype = Show role type and subtype +ui-options-admin-player-tab-role-setting-subtyperoletype = Show subtype and role type + +ui-options-admin-player-tab-color-setting = Color settings +ui-options-admin-player-tab-color-setting-off = I hate colors +ui-options-admin-player-tab-color-setting-character = Colorize antag character names +ui-options-admin-player-tab-color-setting-roletype = Colorize all role types +ui-options-admin-player-tab-color-setting-both = Colorize both 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 -ui-options-overlay-merge-distance = Stack merge distance -ui-options-overlay-ghost-fade-distance = Ghost overlay fade range from mouse -ui-options-overlay-ghost-hide-distance = Ghost overlay hide range from mouse + +ui-options-admin-overlay-antag-format = Antag label style +ui-options-admin-overlay-antag-format-binary = Show antag status +ui-options-admin-overlay-antag-format-roletype = Show role type +ui-options-admin-overlay-antag-format-subtype = Show subtype + +ui-options-admin-overlay-antag-symbol = Antag symbol style +ui-options-admin-overlay-antag-symbol-off = No antag symbol +ui-options-admin-overlay-antag-symbol-basic = Show standard antag symbol +ui-options-admin-overlay-antag-symbol-specific = Show specific antag symbol + +ui-options-admin-enable-overlay-playtime = Show playtime +ui-options-admin-enable-overlay-starting-job = Show starting job +ui-options-admin-overlay-merge-distance = Stack merge distance +ui-options-admin-overlay-ghost-fade-distance = Ghost overlay fade range from mouse +ui-options-admin-overlay-ghost-hide-distance = Ghost overlay hide range from mouse diff --git a/Resources/Locale/en-US/mind/role-types.ftl b/Resources/Locale/en-US/mind/role-types.ftl index 1139bf1ab0..7d568fd686 100644 --- a/Resources/Locale/en-US/mind/role-types.ftl +++ b/Resources/Locale/en-US/mind/role-types.ftl @@ -17,3 +17,19 @@ role-type-free-agent-color = #ffff00 role-type-familiar-color = #6495ed role-type-silicon-color = #6495ed role-type-silicon-antagonist-color =#c832e6 + +# Ideally, subtype names should be short +role-subtype-traitor = Traitor +role-subtype-thief = Thief +role-subtype-ninja = Ninja +role-subtype-nukie = Nukie +role-subtype-traitor-reinforcement = Reinforcement +role-subtype-revolutionary = Rev +role-subtype-head-revolutionary = Head Rev +role-subtype-initial-infected = Infected +role-subtype-zombie = Zombie +role-subtype-dragon = Dragon +role-subtype-survivor = Survivor +role-subtype-subverted = Subverted +role-subtype-paradox-clone = Paradox +role-subtype-wizard = Wizard diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml index cdab1c6150..687b7e17bf 100644 --- a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -95,7 +95,6 @@ antagPrototype: GenericTeamAntagonist # This should be used (or inherited) for team antags that are summoned or converted in large quantities, and are "secondary" to other antags -# TODO: sort weight - type: entity parent: MindRoleGhostRoleTeamAntagonist id: MindRoleGhostRoleTeamAntagonistFlock @@ -130,6 +129,7 @@ - type: MindRole antagPrototype: SubvertedSilicon roleType: SiliconAntagonist + subtype: role-subtype-subverted - type: SubvertedSiliconRole # Dragon @@ -141,6 +141,7 @@ - type: MindRole antagPrototype: Dragon roleType: TeamAntagonist + subtype: role-subtype-dragon exclusiveAntag: true - type: DragonRole - type: RoleBriefing @@ -154,6 +155,7 @@ components: - type: MindRole antagPrototype: SpaceNinja + subtype: role-subtype-ninja exclusiveAntag: true - type: NinjaRole @@ -165,7 +167,7 @@ components: - type: MindRole antagPrototype: ParadoxClone - roleType: SoloAntagonist + subtype: role-subtype-paradox-clone - type: ParadoxCloneRole # Nukies @@ -176,6 +178,7 @@ components: - type: MindRole roleType: TeamAntagonist + subtype: role-subtype-nukie exclusiveAntag: true antagPrototype: Nukeops - type: NukeopsRole @@ -206,6 +209,7 @@ antagPrototype: HeadRev exclusiveAntag: true roleType: TeamAntagonist + subtype: role-subtype-head-revolutionary - type: RevolutionaryRole - type: entity @@ -215,6 +219,7 @@ components: - type: MindRole antagPrototype: Rev + subtype: role-subtype-revolutionary # Survivors (Wizard) - type: entity @@ -225,6 +230,7 @@ - type: MindRole antagPrototype: Survivor roleType: FreeAgent + subtype: role-subtype-survivor - type: SurvivorRole # Thief @@ -235,6 +241,7 @@ components: - type: MindRole antagPrototype: Thief + subtype: role-subtype-thief - type: ThiefRole # Traitors @@ -246,6 +253,7 @@ - type: MindRole antagPrototype: Traitor exclusiveAntag: true + subtype: role-subtype-traitor - type: TraitorRole - type: entity @@ -263,6 +271,7 @@ components: - type: MindRole roleType: TeamAntagonist + subtype: role-subtype-traitor-reinforcement antagPrototype: GenericTeamAntagonist # Wizards @@ -274,6 +283,7 @@ - type: MindRole antagPrototype: Wizard exclusiveAntag: true + subtype: role-subtype-wizard - type: WizardRole # Zombie Squad @@ -286,6 +296,7 @@ antagPrototype: InitialInfected exclusiveAntag: true roleType: TeamAntagonist + subtype: role-subtype-initial-infected - type: InitialInfectedRole - type: entity @@ -296,5 +307,5 @@ - type: MindRole antagPrototype: Zombie exclusiveAntag: true - roleType: TeamAntagonist + subtype: role-subtype-zombie - type: ZombieRole diff --git a/Resources/Prototypes/Roles/role_types.yml b/Resources/Prototypes/Roles/role_types.yml index 7ae2b44054..eb8db63977 100644 --- a/Resources/Prototypes/Roles/role_types.yml +++ b/Resources/Prototypes/Roles/role_types.yml @@ -1,5 +1,8 @@ -# For use by Role Types -# Do not touch these +# The primary role types are referenced by RP Rules, and were specified based on head admin input. +# Do not create new ones without discussion. +# You probably want a Subtype instead, anyway. Use MindRoleComponent.Subtype + +# Any new primary role types must be listed in RoleTypes.xml # If you change/add a color here, also change it in role-types.ftl!