diff --git a/Content.Client/RoundEnd/RoundEndSummaryUIController.cs b/Content.Client/RoundEnd/RoundEndSummaryUIController.cs
index bb85d98353..0e8f5c5017 100644
--- a/Content.Client/RoundEnd/RoundEndSummaryUIController.cs
+++ b/Content.Client/RoundEnd/RoundEndSummaryUIController.cs
@@ -40,7 +40,7 @@ public sealed partial class RoundEndSummaryUIController : UIController,
return;
_window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText,
- message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, EntityManager);
+ message.RoundDuration, message.RoundId, message.AllPlayersEndInfo);
}
public void OnSystemLoaded(ClientGameTicker system)
diff --git a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs
index f0a1678b75..6224c35f5a 100644
--- a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs
+++ b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs
@@ -1,171 +1,496 @@
using System.Linq;
using System.Numerics;
using Content.Client.Message;
-using Content.Shared.GameTicking;
+using RoundEndPlayerInfo = Content.Shared.GameTicking.RoundEndMessageEvent.RoundEndPlayerInfo;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
-namespace Content.Client.RoundEnd
+namespace Content.Client.RoundEnd;
+
+///
+/// Window displaying round end information including player manifest.
+///
+public sealed partial class RoundEndSummaryWindow : DefaultWindow
{
- public sealed class RoundEndSummaryWindow : DefaultWindow
+ [Dependency] private IEntityManager _entityManager = default!;
+
+ public int RoundId;
+ private readonly RoundEndPlayerInfo[] _playersInfo;
+ private GridContainer _playerGrid = null!;
+ private readonly List _sortButtons = [];
+ private string _searchText = string.Empty;
+
+ private enum SortField
{
- private readonly IEntityManager _entityManager;
- public int RoundId;
+ ICName,
+ Role,
+ PlayerType,
+ OOCName
+ }
- public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, int roundId,
- RoundEndMessageEvent.RoundEndPlayerInfo[] info, IEntityManager entityManager)
+ private SortField _currentSortField = SortField.PlayerType;
+ private bool _sortDescending;
+
+ public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, int roundId, RoundEndPlayerInfo[] info)
+ {
+ IoCManager.InjectDependencies(this);
+ _playersInfo = info;
+
+ MinSize = SetSize = new Vector2(720, 580);
+
+ Title = Loc.GetString("round-end-summary-window-title");
+
+ // The round end window is split into two tabs, one about the round stats
+ // and the other is a list of RoundEndPlayerInfo for each player.
+ // This tab would be a good place for things like: "x many people died.",
+ // "clown slipped the crew x times.", "x shots were fired this round.", etc.
+ // Also, good for serious info.
+
+ RoundId = roundId;
+ var roundEndTabs = new TabContainer();
+ roundEndTabs.AddChild(MakeRoundEndSummaryTab(gm, roundEnd, roundTimeSpan, roundId));
+ roundEndTabs.AddChild(MakePlayerManifestTab());
+
+ ContentsContainer.AddChild(roundEndTabs);
+
+ OpenCenteredRight();
+ MoveToFront();
+ }
+
+ private static BoxContainer MakeRoundEndSummaryTab(string gamemode, string roundEnd, TimeSpan roundDuration, int roundId)
+ {
+ var roundEndSummaryTab = new BoxContainer
{
- _entityManager = entityManager;
+ Orientation = LayoutOrientation.Vertical,
+ Name = Loc.GetString("round-end-summary-window-round-end-summary-tab-title")
+ };
- MinSize = SetSize = new Vector2(520, 580);
+ var roundEndSummaryContainerScrollbox = new ScrollContainer
+ {
+ VerticalExpand = true,
+ Margin = new Thickness(10)
+ };
+ var roundEndSummaryContainer = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical
+ };
- Title = Loc.GetString("round-end-summary-window-title");
+ //Gamemode Name
+ var gamemodeLabel = new RichTextLabel();
+ var gamemodeMessage = new FormattedMessage();
+ gamemodeMessage.AddMarkupOrThrow(Loc.GetString("round-end-summary-window-round-id-label", ("roundId", roundId)));
+ gamemodeMessage.AddText(" ");
+ gamemodeMessage.AddMarkupOrThrow(Loc.GetString("round-end-summary-window-gamemode-name-label", ("gamemode", gamemode)));
+ gamemodeLabel.SetMessage(gamemodeMessage);
+ roundEndSummaryContainer.AddChild(gamemodeLabel);
- // The round end window is split into two tabs, one about the round stats
- // and the other is a list of RoundEndPlayerInfo for each player.
- // This tab would be a good place for things like: "x many people died.",
- // "clown slipped the crew x times.", "x shots were fired this round.", etc.
- // Also good for serious info.
+ //Duration
+ var roundTimeLabel = new RichTextLabel();
+ roundTimeLabel.SetMarkup(Loc.GetString("round-end-summary-window-duration-label",
+ ("hours", roundDuration.Hours),
+ ("minutes", roundDuration.Minutes),
+ ("seconds", roundDuration.Seconds)));
+ roundEndSummaryContainer.AddChild(roundTimeLabel);
- RoundId = roundId;
- var roundEndTabs = new TabContainer();
- roundEndTabs.AddChild(MakeRoundEndSummaryTab(gm, roundEnd, roundTimeSpan, roundId));
- roundEndTabs.AddChild(MakePlayerManifestTab(info));
-
- ContentsContainer.AddChild(roundEndTabs);
-
- OpenCenteredRight();
- MoveToFront();
+ //Round end text
+ if (!string.IsNullOrEmpty(roundEnd))
+ {
+ var roundEndLabel = new RichTextLabel();
+ roundEndLabel.SetMarkup(roundEnd);
+ roundEndSummaryContainer.AddChild(roundEndLabel);
}
- private BoxContainer MakeRoundEndSummaryTab(string gamemode, string roundEnd, TimeSpan roundDuration, int roundId)
+ roundEndSummaryContainerScrollbox.AddChild(roundEndSummaryContainer);
+ roundEndSummaryTab.AddChild(roundEndSummaryContainerScrollbox);
+
+ return roundEndSummaryTab;
+ }
+
+ private BoxContainer MakePlayerManifestTab()
+ {
+ var playerManifestTab = new BoxContainer
{
- var roundEndSummaryTab = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Name = Loc.GetString("round-end-summary-window-round-end-summary-tab-title")
- };
+ Orientation = LayoutOrientation.Vertical,
+ Name = Loc.GetString("round-end-summary-window-player-manifest-tab-title")
+ };
- var roundEndSummaryContainerScrollbox = new ScrollContainer
- {
- VerticalExpand = true,
- Margin = new Thickness(10)
- };
- var roundEndSummaryContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
+ // Search container
+ var searchContainer = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Margin = new Thickness(10, 10, 10, 5)
+ };
- //Gamemode Name
- var gamemodeLabel = new RichTextLabel();
- var gamemodeMessage = new FormattedMessage();
- gamemodeMessage.AddMarkupOrThrow(Loc.GetString("round-end-summary-window-round-id-label", ("roundId", roundId)));
- gamemodeMessage.AddText(" ");
- gamemodeMessage.AddMarkupOrThrow(Loc.GetString("round-end-summary-window-gamemode-name-label", ("gamemode", gamemode)));
- gamemodeLabel.SetMessage(gamemodeMessage);
- roundEndSummaryContainer.AddChild(gamemodeLabel);
+ var searchLabel = new Label
+ {
+ Text = "Filter: ",
+ VerticalAlignment = VAlignment.Center,
+ MinSize = new Vector2(40, 1)
+ };
- //Duration
- var roundTimeLabel = new RichTextLabel();
- roundTimeLabel.SetMarkup(Loc.GetString("round-end-summary-window-duration-label",
- ("hours", roundDuration.Hours),
- ("minutes", roundDuration.Minutes),
- ("seconds", roundDuration.Seconds)));
- roundEndSummaryContainer.AddChild(roundTimeLabel);
+ var searchBar = new LineEdit
+ {
+ PlaceHolder = Loc.GetString("round-end-summary-window-player-manifest-tab-search-placeholder"),
+ HorizontalExpand = true,
+ MinSize = new Vector2(200, 1)
+ };
- //Round end text
- if (!string.IsNullOrEmpty(roundEnd))
- {
- var roundEndLabel = new RichTextLabel();
- roundEndLabel.SetMarkup(roundEnd);
- roundEndSummaryContainer.AddChild(roundEndLabel);
- }
+ searchBar.OnTextChanged += OnSearchTextChanged;
- roundEndSummaryContainerScrollbox.AddChild(roundEndSummaryContainer);
- roundEndSummaryTab.AddChild(roundEndSummaryContainerScrollbox);
+ searchContainer.AddChild(searchLabel);
+ searchContainer.AddChild(searchBar);
+ playerManifestTab.AddChild(searchContainer);
- return roundEndSummaryTab;
+ // Header with sort buttons
+ var headerContainer = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Margin = new Thickness(10, 5, 10, 5)
+ };
+
+ var icNameButton = CreateSortButton("round-end-summary-window-player-manifest-tab-sort-character", SortField.ICName);
+ var roleButton = CreateSortButton("round-end-summary-window-player-manifest-tab-sort-role", SortField.Role);
+ var playerTypeButton = CreateSortButton("round-end-summary-window-player-manifest-tab-sort-player-type", SortField.PlayerType);
+ var oocNameButton = CreateSortButton("round-end-summary-window-player-manifest-tab-sort-player", SortField.OOCName);
+
+ playerTypeButton.SetSortIndicator(true);
+ headerContainer.AddChild(icNameButton);
+
+ // Add small spacer between buttons
+ headerContainer.AddChild(new Control
+ {
+ MinSize = new Vector2(5, 1),
+ HorizontalExpand = false
+ });
+
+ headerContainer.AddChild(roleButton);
+
+ // Add small spacer between buttons
+ headerContainer.AddChild(new Control
+ {
+ MinSize = new Vector2(5, 1),
+ HorizontalExpand = false
+ });
+
+ headerContainer.AddChild(playerTypeButton);
+
+ // Add small spacer between buttons
+ headerContainer.AddChild(new Control
+ {
+ MinSize = new Vector2(5, 1),
+ HorizontalExpand = false
+ });
+
+ headerContainer.AddChild(oocNameButton);
+
+ playerManifestTab.AddChild(headerContainer);
+
+ var scrollContainer = new ScrollContainer
+ {
+ VerticalExpand = true,
+ Margin = new Thickness(10, 0, 10, 10),
+ };
+
+ _playerGrid = new GridContainer
+ {
+ Columns = 6, // Player Sprite,IC Name,Role,Player Type,OOC Name
+ HorizontalExpand = true,
+ };
+
+ RefreshPlayerList();
+
+ scrollContainer.AddChild(_playerGrid);
+ playerManifestTab.AddChild(scrollContainer);
+
+ return playerManifestTab;
+ }
+
+ private SortButton CreateSortButton(string text, SortField field)
+ {
+ var button = new SortButton(Loc.GetString(text), field);
+ button.OnPressed += _ => SortBy(field);
+ _sortButtons.Add(button);
+ return button;
+ }
+
+ ///
+ /// Handles sorting by the specified field, toggling direction if the same field is clicked
+ ///
+ private void SortBy(SortField field)
+ {
+ if (_currentSortField == field)
+ {
+ _sortDescending = !_sortDescending;
+ }
+ else
+ {
+ _currentSortField = field;
+ _sortDescending = false;
}
- private BoxContainer MakePlayerManifestTab(RoundEndMessageEvent.RoundEndPlayerInfo[] playersInfo)
+ foreach (var button in _sortButtons)
{
- var playerManifestTab = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Name = Loc.GetString("round-end-summary-window-player-manifest-tab-title")
- };
+ button.SetSortIndicator(button.Field == _currentSortField, _sortDescending);
+ }
- var playerInfoContainerScrollbox = new ScrollContainer
- {
- VerticalExpand = true,
- Margin = new Thickness(10)
- };
- var playerInfoContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
+ RefreshPlayerList();
+ }
- //Put observers at the bottom of the list. Put antags on top.
- var sortedPlayersInfo = playersInfo.OrderBy(p => p.Observer).ThenBy(p => !p.Antag);
+ ///
+ /// Refreshes the player list grid by clearing it and repopulating with sorted player data
+ ///
+ private void RefreshPlayerList()
+ {
+ _playerGrid.RemoveAllChildren();
- //Create labels for each player info.
- foreach (var playerInfo in sortedPlayersInfo)
- {
- var hBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- };
-
- var playerInfoText = new RichTextLabel
- {
- VerticalAlignment = VAlignment.Center,
- VerticalExpand = true,
- };
-
- if (playerInfo.PlayerNetEntity != null)
- {
- hBox.AddChild(new SpriteView(playerInfo.PlayerNetEntity.Value, _entityManager)
- {
- OverrideDirection = Direction.South,
- VerticalAlignment = VAlignment.Center,
- SetSize = new Vector2(32, 32),
- VerticalExpand = true,
- });
- }
-
- if (playerInfo.PlayerICName != null)
- {
- if (playerInfo.Observer)
- {
- playerInfoText.SetMarkup(
- Loc.GetString("round-end-summary-window-player-info-if-observer-text",
- ("playerOOCName", playerInfo.PlayerOOCName),
- ("playerICName", playerInfo.PlayerICName)));
- }
- else
- {
- //TODO: On Hover display a popup detailing more play info.
- //For example: their antag goals and if they completed them sucessfully.
- var icNameColor = playerInfo.Antag ? "red" : "white";
- playerInfoText.SetMarkup(
- Loc.GetString("round-end-summary-window-player-info-if-not-observer-text",
- ("playerOOCName", playerInfo.PlayerOOCName),
- ("icNameColor", icNameColor),
- ("playerICName", playerInfo.PlayerICName),
- ("playerRole", Loc.GetString(playerInfo.Role))));
- }
- }
- hBox.AddChild(playerInfoText);
- playerInfoContainer.AddChild(hBox);
- }
-
- playerInfoContainerScrollbox.AddChild(playerInfoContainer);
- playerManifestTab.AddChild(playerInfoContainerScrollbox);
-
- return playerManifestTab;
+ var sortedPlayers = GetSortedPlayers();
+ foreach (var playerInfo in sortedPlayers)
+ {
+ AddPlayerRow(playerInfo);
}
}
+ ///
+ /// Adds a single player row to the grid with all columns (sprite, IC name, role, player type, OOC name)
+ ///
+ private void AddPlayerRow(RoundEndPlayerInfo playerInfo)
+ {
+ // Player Sprite column
+ if (playerInfo.PlayerNetEntity != null)
+ {
+ _playerGrid.AddChild(new SpriteView(playerInfo.PlayerNetEntity.Value, _entityManager)
+ {
+ OverrideDirection = Direction.South,
+ VerticalAlignment = VAlignment.Center,
+ SetSize = new Vector2(32, 32),
+ });
+ }
+ else
+ {
+ _playerGrid.AddChild(new Control
+ {
+ SetSize = new Vector2(32, 32),
+ });
+ }
+
+ // IC Name column
+ var icNameLabel = new Label
+ {
+ Text = playerInfo.PlayerICName ?? playerInfo.PlayerOOCName,
+ VerticalAlignment = VAlignment.Center,
+ HorizontalExpand = true,
+ ClipText = true
+ };
+
+ // Apply color coding for antagonists
+ if (playerInfo.Antag)
+ {
+ icNameLabel.FontColorOverride = Color.Red;
+ }
+
+ _playerGrid.AddChild(icNameLabel);
+
+ _playerGrid.AddChild(new Control
+ {
+ SetSize = new Vector2(32, 32),
+ });
+
+ // Role column
+ var roleLabel = new Label
+ {
+ Text = playerInfo.Observer ? "-" : Loc.GetString(playerInfo.Role),
+ VerticalAlignment = VAlignment.Center,
+ HorizontalExpand = true,
+ ClipText = true
+ };
+ _playerGrid.AddChild(roleLabel);
+
+ // Player Type column
+ var playerTypeLabel = new Label
+ {
+ Text = GetPlayerTypeText(playerInfo),
+ VerticalAlignment = VAlignment.Center,
+ HorizontalExpand = true,
+ ClipText = true
+ };
+
+ // Apply color coding based on player type
+ if (playerInfo.Antag)
+ {
+ playerTypeLabel.FontColorOverride = Color.Red;
+ }
+ else if (playerInfo.Observer)
+ {
+ playerTypeLabel.FontColorOverride = Color.Gray;
+ }
+
+ _playerGrid.AddChild(playerTypeLabel);
+
+ // OOC Name column
+ var oocNameLabel = new Label
+ {
+ Text = playerInfo.PlayerOOCName,
+ VerticalAlignment = VAlignment.Center,
+ HorizontalExpand = true,
+ ClipText = true
+ };
+
+ _playerGrid.AddChild(oocNameLabel);
+ }
+
+ ///
+ /// Gets the player type text for a player based on their observer and antagonist flags
+ ///
+ private static string GetPlayerTypeText(RoundEndPlayerInfo playerInfo)
+ {
+ if (playerInfo.Observer)
+ return Loc.GetString("round-end-summary-window-player-manifest-tab-sort-player-type-observer");
+ if (playerInfo.Antag)
+ return Loc.GetString("round-end-summary-window-player-manifest-tab-sort-player-type-antag");
+
+ return Loc.GetString("round-end-summary-window-player-manifest-tab-sort-player-type-crew");
+ }
+
+ private IEnumerable GetSortedPlayers()
+ {
+ // First filter players based on search text
+ var filteredPlayers = string.IsNullOrEmpty(_searchText)
+ ? _playersInfo
+ : _playersInfo.Where(PlayerMatchesSearch);
+
+ static string GetIcKey(RoundEndPlayerInfo p) =>
+ (p.PlayerICName ?? p.PlayerOOCName).ToLowerInvariant();
+
+ static string GetOocKey(RoundEndPlayerInfo p) =>
+ p.PlayerOOCName.ToLowerInvariant();
+
+ static string GetRoleKey(RoundEndPlayerInfo p) =>
+ (p.Observer ? "zzz_observer" : p.Role).ToLowerInvariant();
+
+ static int GetPlayerTypeSortKey(RoundEndPlayerInfo p) =>
+ p.Antag ? 1 : p.Observer ? 3 : 2;
+
+ return _currentSortField switch
+ {
+ SortField.ICName => ApplySort(filteredPlayers, GetIcKey, _sortDescending),
+ SortField.OOCName => ApplySort(filteredPlayers, GetOocKey, _sortDescending),
+ SortField.Role => ApplySort(filteredPlayers, GetRoleKey, _sortDescending),
+ SortField.PlayerType => ApplySort(filteredPlayers, GetPlayerTypeSortKey, _sortDescending),
+ _ => filteredPlayers
+ };
+ }
+
+ private static IEnumerable ApplySort(
+ IEnumerable players,
+ Func primaryKey,
+ bool descending)
+ {
+ static string SecondaryKey(RoundEndPlayerInfo p) =>
+ (p.PlayerICName ?? p.PlayerOOCName).ToLowerInvariant();
+
+ return descending
+ ? players.OrderByDescending(primaryKey).ThenByDescending(SecondaryKey)
+ : players.OrderBy(primaryKey).ThenBy(SecondaryKey);
+ }
+
+ ///
+ /// Gets a sort key for player type to ensure consistent ordering: Antagonist -> Crew -> Observer
+ ///
+
+ ///
+ /// Checks if a player matches the current search filter
+ ///
+ private bool PlayerMatchesSearch(RoundEndPlayerInfo playerInfo)
+ {
+ if (string.IsNullOrEmpty(_searchText))
+ return true;
+
+ // Search in character name (IC name)
+ if (!string.IsNullOrEmpty(playerInfo.PlayerICName) &&
+ playerInfo.PlayerICName.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ // Search in player name (OOC name)
+ if (!string.IsNullOrEmpty(playerInfo.PlayerOOCName) &&
+ playerInfo.PlayerOOCName.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ // Search in role
+ if (!string.IsNullOrEmpty(playerInfo.Role))
+ {
+ if (playerInfo.Role.Contains(_searchText, StringComparison.OrdinalIgnoreCase) ||
+ Loc.GetString(playerInfo.Role).Contains(_searchText, StringComparison.OrdinalIgnoreCase))
+ return true;
+ }
+
+ // Search in player type
+ var playerType = GetPlayerTypeText(playerInfo);
+ if (playerType.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ // Search for "Observer" when they are observers
+ if (playerInfo.Observer && "observer".Contains(_searchText, StringComparison.OrdinalIgnoreCase))
+ return true;
+
+ return false;
+ }
+
+ ///
+ /// Handles search text changes and refreshes the player list
+ ///
+ private void OnSearchTextChanged(LineEdit.LineEditEventArgs args)
+ {
+ _searchText = args.Text;
+ RefreshPlayerList();
+ }
+
+ private sealed class SortButton : Button
+ {
+ public SortField Field { get; }
+ private readonly Label _sortIndicator;
+
+ public SortButton(string text, SortField field)
+ {
+ Field = field;
+ HorizontalExpand = true;
+
+ var container = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ HorizontalExpand = true
+ };
+
+ var label = new Label
+ {
+ Text = text,
+ HorizontalExpand = true
+ };
+
+ _sortIndicator = new Label
+ {
+ Text = "",
+ HorizontalAlignment = HAlignment.Right,
+ MinSize = new Vector2(15, 1)
+ };
+
+ container.AddChild(label);
+ container.AddChild(_sortIndicator);
+
+ AddChild(container);
+ }
+
+ public void SetSortIndicator(bool active, bool descending = false)
+ {
+ if (!active)
+ {
+ _sortIndicator.Text = "";
+ return;
+ }
+
+ _sortIndicator.Text = descending ? "▼" : "▲";
+ }
+ }
}
diff --git a/Resources/Locale/en-US/round-end/round-end-summary-window.ftl b/Resources/Locale/en-US/round-end/round-end-summary-window.ftl
index 58d26319b3..1240632f1c 100644
--- a/Resources/Locale/en-US/round-end/round-end-summary-window.ftl
+++ b/Resources/Locale/en-US/round-end/round-end-summary-window.ftl
@@ -6,3 +6,12 @@ round-end-summary-window-gamemode-name-label = The game mode was [color=white]{$
round-end-summary-window-duration-label = It lasted for [color=yellow]{$hours} hours, {$minutes} minutes, and {$seconds} seconds.
round-end-summary-window-player-info-if-observer-text = [color=gray]{$playerOOCName}[/color] was [color=lightblue]{$playerICName}[/color], an observer.
round-end-summary-window-player-info-if-not-observer-text = [color=gray]{$playerOOCName}[/color] was [color={$icNameColor}]{$playerICName}[/color] playing role of [color=orange]{$playerRole}[/color].
+round-end-summary-window-player-manifest-tab-search-placeholder = Search players, roles, types...
+round-end-summary-window-player-manifest-tab-sort-character = Character
+round-end-summary-window-player-manifest-tab-sort-role = Role
+round-end-summary-window-player-manifest-tab-sort-player-type = Type
+round-end-summary-window-player-manifest-tab-sort-player = Player
+round-end-summary-window-player-manifest-tab-sort-player-type-antag = Antagonist
+round-end-summary-window-player-manifest-tab-sort-player-type-crew = Crew
+round-end-summary-window-player-manifest-tab-sort-player-type-observer = Observer
+