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;
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 =
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;
{
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);
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;
}
}
[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!;
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;
}
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;
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 = "↑";
_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;
#region ListContainer
+ private void PlayerListSettingsChanged(bool _)
+ {
+ RefreshPlayerList(_adminSystem.PlayerList);
+ }
+
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
_players = players;
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
};
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
- <Label Name="AntagonistLabel"
- SizeFlagsStretchRatio="1"
- HorizontalExpand="True"
- ClipText="True"/>
- <customControls:VSeparator/>
<Label Name="RoleTypeLabel"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"
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;
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;
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"
UsernameLabel.OnKeyBindDown += UsernameClicked;
CharacterLabel.OnKeyBindDown += CharacterClicked;
JobLabel.OnKeyBindDown += JobClicked;
- AntagonistLabel.OnKeyBindDown += AntagonistClicked;
RoleTypeLabel.OnKeyBindDown += RoleTypeClicked;
PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
}
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)
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");
}
HeaderClicked(args, Header.Job);
}
- private void AntagonistClicked(GUIBoundKeyEventArgs args)
- {
- HeaderClicked(args, Header.Antagonist);
- }
-
private void RoleTypeClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.RoleType);
UsernameLabel.OnKeyBindDown -= UsernameClicked;
CharacterLabel.OnKeyBindDown -= CharacterClicked;
JobLabel.OnKeyBindDown -= JobClicked;
- AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
RoleTypeLabel.OnKeyBindDown -= RoleTypeClicked;
PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
}
Username,
Character,
Job,
- Antagonist,
RoleType,
Playtime
}
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" />
{
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();
}
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)
// 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
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)
string StartingJob,
bool Antag,
RoleTypePrototype RoleProto,
+ int SortWeight,
NetEntity? NetEntity,
NetUserId SessionId,
bool Connected,
/// 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);
}
/// </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;
}
/// </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?
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);
}
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
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 = 🗡
## 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
antag: true
antagPrototype: GenericAntagonist
roleType: SoloAntagonist
+ sortWeight: 50
#Observer
- type: entity
name: Observer Role
components:
- type: ObserverRole
+ - type: MindRole
+ sortWeight: -10
#Ghost Roles
- type: entity
- type: MindRole
roleType: FreeAgent
antagPrototype: GenericFreeAgent
+ sortWeight: 30
- type: entity
parent: MindRoleGhostRoleNeutral
components:
- type: MindRole
roleType: FreeAgent
+ sortWeight: 0 # Maybe 10?
- type: entity
parent: MindRoleGhostRoleNeutral
- 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
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: "⛞"