Files
tbd-station-14/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs
Errant 46d58bf22a Role Types (#33420)
* mindcomponent namespace

* wip MindRole stuff

* admin player tab

* mindroletype comment

* mindRolePrototype redesign

* broken param

* wip RoleType implementation

* basic role type switching for antags

* traitor fix

* fix AdminPanel update

* the renameningTM

* cleanup

* feature uncreeping

* roletypes on mind roles

* update MindComponent.RoleType when MindRoles change

* ghostrole configuration

* ghostrole config improvements

* live update of roleType on the character window

* logging stuff and notes

* remove thing no one asked for

* weh

* Mind Role Entities wip

* headrev count fix

* silicon stuff, cleanup

* exclusive antag config, cleanup

* jobroleadd overwerite

* logging stuff

* MindHasRole cleanup, admin log stuff

* last second cleanup

* ocd

* move roletypeprototype to its own file, minor note stuff

* remove Roletype.Created

* log stuff

* roletype setup for ghostroles and autotraitor reinforcements

* ghostrole type configs

* adjustable admin overlay

* cleanup

* fix this in its own PR

* silicon antagonist

* borg stuff

* mmi roletype handling

* spawnable borg roletype handling

* weh

* ghost role cleanup

* weh

* RoleEvent update

* polish

* log stuff

* admin overlay config

* ghostrolecomponent cleanup

* weh

* admin overlay code cleanup

* minor cleanup

* Obsolete MindRoleAddedEvent

* comment

* minor code cleanup

* MindOnDoGreeting fix

* Role update message

* fix duplicate job greeting for cyborgs

* fix emag job message dupe

* nicer-looking role type update

* crew aligned

* syndicate assault borg role fix

* fix test fail

* fix a merge mistake

* fix LoneOp role type

* Update Content.Client/Administration/AdminNameOverlay.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Update Content.Shared/Roles/SharedRoleSystem.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* comment formatting

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* change logging category

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* fix a space

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* use MindAddRoles

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* get MindComponent from TryGetMind

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* move var declaration outside loop

* remove TryComp

* take RoleEnum behind the barn

* don't use ensurecomp unnecessarily

* cvar comments

* toggleableghostrolecomponent documentation

* skrek

* use EntProtoId

* mindrole config

* merge baserolecomponent into basemindrolecomponent

* ai and borg silicon role tweaks

* formatting

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* I will end you (the color)

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* use LocId type for a locale id

* update RoleEvent documentation

* update RoleEvent documentation

* remove obsolete MindRoleAddedEvent

* refine MindRolesUpdate()

* use dependency

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* inject dependency

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* roleType.Name no longer required

* reformatted draw code logic

* GhostRoleMarkerRoleComponent comment

* minor SharedRoleSystem cleanup

* StartingMindRoleComponent, unhardcode roundstart silicon

* Update Content.Shared/Roles/SharedRoleSystem.cs

* remove a whitespace

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-01-11 22:17:26 +01:00

230 lines
7.0 KiB
C#

using System.Linq;
using Content.Client.Administration.Systems;
using Content.Client.UserInterface.Controls;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
[GenerateTypedNameReferences]
public sealed partial class PlayerTab : Control
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _playerMan = default!;
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
private readonly AdminSystem _adminSystem;
private IReadOnlyList<PlayerInfo> _players = new List<PlayerInfo>();
private Header _headerClicked = Header.Username;
private bool _ascending = true;
private bool _showDisconnected;
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
public PlayerTab()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_adminSystem = _entManager.System<AdminSystem>();
_adminSystem.PlayerListChanged += RefreshPlayerList;
_adminSystem.OverlayEnabled += OverlayEnabled;
_adminSystem.OverlayDisabled += OverlayDisabled;
OverlayButton.OnPressed += OverlayButtonPressed;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
#region Antag Overlay
private void OverlayEnabled()
{
OverlayButton.Pressed = true;
}
private void OverlayDisabled()
{
OverlayButton.Pressed = false;
}
private void OverlayButtonPressed(ButtonEventArgs args)
{
if (args.Button.Pressed)
{
_adminSystem.AdminOverlayOn();
}
else
{
_adminSystem.AdminOverlayOff();
}
}
#endregion
private void ShowDisconnectedPressed(ButtonEventArgs args)
{
_showDisconnected = args.Button.Pressed;
RefreshPlayerList(_players);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_adminSystem.PlayerListChanged -= RefreshPlayerList;
_adminSystem.OverlayEnabled -= OverlayEnabled;
_adminSystem.OverlayDisabled -= OverlayDisabled;
OverlayButton.OnPressed -= OverlayButtonPressed;
ListHeader.OnHeaderClicked -= HeaderClicked;
}
}
#region ListContainer
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
_players = players;
PlayerCount.Text = Loc.GetString("player-tab-player-count", ("count", _playerMan.PlayerCount));
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
.ToList());
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var player})
return;
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
}
/// <summary>
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
/// If all characters are lowercase, the comparison ignores case.
/// If there is an uppercase character, the comparison is case sensitive.
/// </summary>
/// <param name="filter"></param>
/// <param name="listData"></param>
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
return false;
if (!_showDisconnected && !info.Connected)
return false;
if (IsAllLower(filter))
{
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase))
return false;
}
else
{
if (!playerString.Contains(filter))
return false;
}
return true;
}
private bool IsAllLower(string input)
{
foreach (var c in input)
{
if (char.IsLetter(c) && !char.IsLower(c))
return false;
}
return true;
}
#endregion
#region Header
private void UpdateHeaderSymbols()
{
ListHeader.ResetHeaderText();
ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}";
}
private int Compare(PlayerInfo x, PlayerInfo y)
{
if (!_ascending)
{
(x, y) = (y, x);
}
return _headerClicked switch
{
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.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
_ => 1
};
}
private int Compare(string x, string y)
{
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
}
private void HeaderClicked(Header header)
{
if (_headerClicked == header)
{
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
}
RefreshPlayerList(_adminSystem.PlayerList);
}
#endregion
}
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;