--- /dev/null
+<ui:FancyWindow
+ xmlns="https://spacestation14.io"
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
+ Title="{Loc ban-panel-title}" MinSize="300 300">
+ <BoxContainer Orientation="Vertical">
+ <BoxContainer Orientation="Horizontal">
+ <Label Name="PlayerName"/>
+ <Button Name="UsernameCopyButton" Text="{Loc player-panel-copy-username}"/>
+ </BoxContainer>
+ <BoxContainer Orientation="Horizontal">
+ <Label Name="Whitelisted"/>
+ <controls:ConfirmButton Name="WhitelistToggle" Text="{Loc 'player-panel-false'}" Visible="False"></controls:ConfirmButton>
+ </BoxContainer>
+ <Label Name="Playtime"/>
+ <Label Name="Notes"/>
+ <Label Name="Bans"/>
+ <Label Name="RoleBans"/>
+ <Label Name="SharedConnections"/>
+
+ <BoxContainer Align="Center">
+ <GridContainer Rows="5">
+ <Button Name="NotesButton" Text="{Loc player-panel-show-notes}" SetWidth="136" Disabled="True"/>
+ <Button Name="AhelpButton" Text="{Loc player-panel-help}" Disabled="True"/>
+ <Button Name="FreezeButton" Text = "{Loc player-panel-freeze}" Disabled="True"/>
+ <controls:ConfirmButton Name="KickButton" Text="{Loc player-panel-kick}" Disabled="True"/>
+ <controls:ConfirmButton Name="DeleteButton" Text="{Loc player-panel-delete}" Disabled="True"/>
+ <Button Name="ShowBansButton" Text="{Loc player-panel-show-bans}" SetWidth="136" Disabled="True"/>
+ <Button Name="LogsButton" Text="{Loc player-panel-logs}" Disabled="True"/>
+ <Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
+ <Button Name="BanButton" Text="{Loc player-panel-ban}" Disabled="True"/>
+ <controls:ConfirmButton Name="RejuvenateButton" Text="{Loc player-panel-rejuvenate}" Disabled="True"/>
+ </GridContainer>
+ </BoxContainer>
+ </BoxContainer>
+</ui:FancyWindow>
--- /dev/null
+using Content.Client.Administration.Managers;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Administration;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Network;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Administration.UI.PlayerPanel;
+
+[GenerateTypedNameReferences]
+public sealed partial class PlayerPanel : FancyWindow
+{
+ private readonly IClientAdminManager _adminManager;
+
+ public event Action<string>? OnUsernameCopy;
+ public event Action<NetUserId?>? OnOpenNotes;
+ public event Action<NetUserId?>? OnOpenBans;
+ public event Action<NetUserId?>? OnAhelp;
+ public event Action<string?>? OnKick;
+ public event Action<NetUserId?>? OnOpenBanPanel;
+ public event Action<NetUserId?, bool>? OnWhitelistToggle;
+ public event Action? OnFreezeAndMuteToggle;
+ public event Action? OnFreeze;
+ public event Action? OnLogs;
+ public event Action? OnDelete;
+ public event Action? OnRejuvenate;
+
+ public NetUserId? TargetPlayer;
+ public string? TargetUsername;
+ private bool _isWhitelisted;
+
+ public PlayerPanel(IClientAdminManager adminManager)
+ {
+ RobustXamlLoader.Load(this);
+ _adminManager = adminManager;
+
+ UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(PlayerName.Text ?? "");
+ BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
+ KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
+ NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
+ ShowBansButton.OnPressed += _ => OnOpenBans?.Invoke(TargetPlayer);
+ AhelpButton.OnPressed += _ => OnAhelp?.Invoke(TargetPlayer);
+ WhitelistToggle.OnPressed += _ =>
+ {
+ OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
+ SetWhitelisted(!_isWhitelisted);
+ };
+ FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
+ FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
+ LogsButton.OnPressed += _ => OnLogs?.Invoke();
+ DeleteButton.OnPressed += _ => OnDelete?.Invoke();
+ RejuvenateButton.OnPressed += _ => OnRejuvenate?.Invoke();
+ }
+
+ public void SetUsername(string player)
+ {
+ Title = Loc.GetString("player-panel-title", ("player", player));
+ PlayerName.Text = Loc.GetString("player-panel-username", ("player", player));
+ }
+
+ public void SetWhitelisted(bool? whitelisted)
+ {
+ if (whitelisted == null)
+ {
+ Whitelisted.Text = null;
+ WhitelistToggle.Visible = false;
+ }
+ else
+ {
+ Whitelisted.Text = Loc.GetString("player-panel-whitelisted");
+ WhitelistToggle.Text = whitelisted.Value.ToString();
+ WhitelistToggle.Visible = true;
+ _isWhitelisted = whitelisted.Value;
+ }
+ }
+
+ public void SetBans(int? totalBans, int? totalRoleBans)
+ {
+ // If one value exists then so should the other.
+ DebugTools.Assert(totalBans.HasValue && totalRoleBans.HasValue || totalBans == null && totalRoleBans == null);
+
+ Bans.Text = totalBans != null ? Loc.GetString("player-panel-bans", ("totalBans", totalBans)) : null;
+
+ RoleBans.Text = totalRoleBans != null ? Loc.GetString("player-panel-rolebans", ("totalRoleBans", totalRoleBans)) : null;
+ }
+
+ public void SetNotes(int? totalNotes)
+ {
+ Notes.Text = totalNotes != null ? Loc.GetString("player-panel-notes", ("totalNotes", totalNotes)) : null;
+ }
+
+ public void SetSharedConnections(int sharedConnections)
+ {
+ SharedConnections.Text = Loc.GetString("player-panel-shared-connections", ("sharedConnections", sharedConnections));
+ }
+
+ public void SetPlaytime(TimeSpan playtime)
+ {
+ Playtime.Text = Loc.GetString("player-panel-playtime",
+ ("days", playtime.Days),
+ ("hours", playtime.Hours % 24),
+ ("minutes", playtime.Minutes % (24 * 60)));
+ }
+
+ public void SetFrozen(bool canFreeze, bool frozen)
+ {
+ FreezeAndMuteToggleButton.Disabled = !canFreeze;
+ FreezeButton.Disabled = !canFreeze || frozen;
+
+ FreezeAndMuteToggleButton.Text = Loc.GetString(!frozen ? "player-panel-freeze-and-mute" : "player-panel-unfreeze");
+ }
+
+ public void SetAhelp(bool canAhelp)
+ {
+ AhelpButton.Disabled = !canAhelp;
+ }
+
+ public void SetButtons()
+ {
+ BanButton.Disabled = !_adminManager.CanCommand("banpanel");
+ KickButton.Disabled = !_adminManager.CanCommand("kick");
+ NotesButton.Disabled = !_adminManager.CanCommand("adminnotes");
+ ShowBansButton.Disabled = !_adminManager.CanCommand("banlist");
+ WhitelistToggle.Disabled =
+ !(_adminManager.CanCommand("addwhitelist") && _adminManager.CanCommand("removewhitelist"));
+ LogsButton.Disabled = !_adminManager.CanCommand("adminlogs");
+ RejuvenateButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
+ DeleteButton.Disabled = !_adminManager.HasFlag(AdminFlags.Debug);
+ }
+}
--- /dev/null
+using Content.Client.Administration.Managers;
+using Content.Client.Eui;
+using Content.Shared.Administration;
+using Content.Shared.Eui;
+using JetBrains.Annotations;
+using Robust.Client.Console;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.Administration.UI.PlayerPanel;
+
+[UsedImplicitly]
+public sealed class PlayerPanelEui : BaseEui
+{
+ [Dependency] private readonly IClientConsoleHost _console = default!;
+ [Dependency] private readonly IClientAdminManager _admin = default!;
+ [Dependency] private readonly IClipboardManager _clipboard = default!;
+
+ private PlayerPanel PlayerPanel { get; }
+
+ public PlayerPanelEui()
+ {
+ PlayerPanel = new PlayerPanel(_admin);
+
+ PlayerPanel.OnUsernameCopy += username => _clipboard.SetText(username);
+ PlayerPanel.OnOpenNotes += id => _console.ExecuteCommand($"adminnotes \"{id}\"");
+ // Kick command does not support GUIDs
+ PlayerPanel.OnKick += username => _console.ExecuteCommand($"kick \"{username}\"");
+ PlayerPanel.OnOpenBanPanel += id => _console.ExecuteCommand($"banpanel \"{id}\"");
+ PlayerPanel.OnOpenBans += id => _console.ExecuteCommand($"banlist \"{id}\"");
+ PlayerPanel.OnAhelp += id => _console.ExecuteCommand($"openahelp \"{id}\"");
+ PlayerPanel.OnWhitelistToggle += (id, whitelisted) =>
+ {
+ _console.ExecuteCommand(whitelisted ? $"whitelistremove \"{id}\"" : $"whitelistadd \"{id}\"");
+ };
+
+ PlayerPanel.OnFreezeAndMuteToggle += () => SendMessage(new PlayerPanelFreezeMessage(true));
+ PlayerPanel.OnFreeze += () => SendMessage(new PlayerPanelFreezeMessage());
+ PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
+ PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
+ PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
+
+ PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
+ }
+
+ public override void Opened()
+ {
+ PlayerPanel.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ PlayerPanel.Close();
+ }
+
+ public override void HandleState(EuiStateBase state)
+ {
+ if (state is not PlayerPanelEuiState s)
+ return;
+
+ PlayerPanel.TargetPlayer = s.Guid;
+ PlayerPanel.TargetUsername = s.Username;
+ PlayerPanel.SetUsername(s.Username);
+ PlayerPanel.SetPlaytime(s.Playtime);
+ PlayerPanel.SetBans(s.TotalBans, s.TotalRoleBans);
+ PlayerPanel.SetNotes(s.TotalNotes);
+ PlayerPanel.SetWhitelisted(s.Whitelisted);
+ PlayerPanel.SetSharedConnections(s.SharedConnections);
+ PlayerPanel.SetFrozen(s.CanFreeze, s.Frozen);
+ PlayerPanel.SetAhelp(s.CanAhelp);
+ PlayerPanel.SetButtons();
+ }
+}
--- /dev/null
+using System.Linq;
+using Content.Server.EUI;
+using Content.Shared.Administration;
+using Robust.Server.Player;
+using Robust.Shared.Console;
+
+namespace Content.Server.Administration.Commands;
+
+[AdminCommand(AdminFlags.Admin)]
+public sealed class PlayerPanelCommand : LocalizedCommands
+{
+ [Dependency] private readonly IPlayerLocator _locator = default!;
+ [Dependency] private readonly EuiManager _euis = default!;
+ [Dependency] private readonly IPlayerManager _players = default!;
+
+ public override string Command => "playerpanel";
+
+ public override async void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (shell.Player is not { } admin)
+ {
+ shell.WriteError(Loc.GetString("cmd-playerpanel-server"));
+ return;
+ }
+
+ if (args.Length != 1)
+ {
+ shell.WriteError(Loc.GetString("cmd-playerpanel-invalid-arguments"));
+ return;
+ }
+
+ var queriedPlayer = await _locator.LookupIdByNameOrIdAsync(args[0]);
+
+ if (queriedPlayer == null)
+ {
+ shell.WriteError(Loc.GetString("cmd-playerpanel-invalid-player"));
+ return;
+ }
+
+ var ui = new PlayerPanelEui(queriedPlayer);
+ _euis.OpenEui(ui, admin);
+ ui.SetPlayerState();
+ }
+
+ public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+ {
+ if (args.Length == 1)
+ {
+ var options = _players.Sessions.OrderBy(c => c.Name).Select(c => c.Name).ToArray();
+
+ return CompletionResult.FromHintOptions(options, LocalizationManager.GetString("cmd-playerpanel-completion"));
+ }
+
+ return CompletionResult.Empty;
+ }
+}
--- /dev/null
+using System.Linq;
+using Content.Server.Administration.Logs;
+using Content.Server.Administration.Managers;
+using Content.Server.Administration.Notes;
+using Content.Server.Administration.Systems;
+using Content.Server.Database;
+using Content.Server.EUI;
+using Content.Shared.Administration;
+using Content.Shared.Database;
+using Content.Shared.Eui;
+using Robust.Server.Player;
+using Robust.Shared.Player;
+
+namespace Content.Server.Administration;
+
+public sealed class PlayerPanelEui : BaseEui
+{
+ [Dependency] private readonly IAdminManager _admins = default!;
+ [Dependency] private readonly IServerDbManager _db = default!;
+ [Dependency] private readonly IAdminNotesManager _notesMan = default!;
+ [Dependency] private readonly IEntityManager _entity = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly EuiManager _eui = default!;
+ [Dependency] private readonly IAdminLogManager _adminLog = default!;
+
+ private readonly LocatedPlayerData _targetPlayer;
+ private int? _notes;
+ private int? _bans;
+ private int? _roleBans;
+ private int _sharedConnections;
+ private bool? _whitelisted;
+ private TimeSpan _playtime;
+ private bool _frozen;
+ private bool _canFreeze;
+ private bool _canAhelp;
+
+ public PlayerPanelEui(LocatedPlayerData player)
+ {
+ IoCManager.InjectDependencies(this);
+ _targetPlayer = player;
+ }
+
+ public override void Opened()
+ {
+ base.Opened();
+ _admins.OnPermsChanged += OnPermsChanged;
+ }
+
+ public override void Closed()
+ {
+ base.Closed();
+ _admins.OnPermsChanged -= OnPermsChanged;
+ }
+
+ public override EuiStateBase GetNewState()
+ {
+ return new PlayerPanelEuiState(_targetPlayer.UserId,
+ _targetPlayer.Username,
+ _playtime,
+ _notes,
+ _bans,
+ _roleBans,
+ _sharedConnections,
+ _whitelisted,
+ _canFreeze,
+ _frozen,
+ _canAhelp);
+ }
+
+ private void OnPermsChanged(AdminPermsChangedEventArgs args)
+ {
+ if (args.Player != Player)
+ return;
+
+ SetPlayerState();
+ }
+
+ public override void HandleMessage(EuiMessageBase msg)
+ {
+ base.HandleMessage(msg);
+
+ ICommonSession? session;
+
+ switch (msg)
+ {
+ case PlayerPanelFreezeMessage freezeMsg:
+ if (!_admins.IsAdmin(Player) ||
+ !_entity.TrySystem<AdminFrozenSystem>(out var frozenSystem) ||
+ !_player.TryGetSessionById(_targetPlayer.UserId, out session) ||
+ session.AttachedEntity == null)
+ return;
+
+ if (_entity.HasComponent<AdminFrozenComponent>(session.AttachedEntity))
+ {
+ _adminLog.Add(LogType.Action,$"{Player:actor} unfroze {_entity.ToPrettyString(session.AttachedEntity):subject}");
+ _entity.RemoveComponent<AdminFrozenComponent>(session.AttachedEntity.Value);
+ SetPlayerState();
+ return;
+ }
+
+ if (freezeMsg.Mute)
+ {
+ _adminLog.Add(LogType.Action,$"{Player:actor} froze and muted {_entity.ToPrettyString(session.AttachedEntity):subject}");
+ frozenSystem.FreezeAndMute(session.AttachedEntity.Value);
+ }
+ else
+ {
+ _adminLog.Add(LogType.Action,$"{Player:actor} froze {_entity.ToPrettyString(session.AttachedEntity):subject}");
+ _entity.EnsureComponent<AdminFrozenComponent>(session.AttachedEntity.Value);
+ }
+ SetPlayerState();
+ break;
+
+ case PlayerPanelLogsMessage:
+ if (!_admins.HasAdminFlag(Player, AdminFlags.Logs))
+ return;
+
+ _adminLog.Add(LogType.Action, $"{Player:actor} opened logs on {_targetPlayer.Username:subject}");
+ var ui = new AdminLogsEui();
+ _eui.OpenEui(ui, Player);
+ ui.SetLogFilter(search: _targetPlayer.Username);
+ break;
+ case PlayerPanelDeleteMessage:
+ case PlayerPanelRejuvenationMessage:
+ if (!_admins.HasAdminFlag(Player, AdminFlags.Debug) ||
+ !_player.TryGetSessionById(_targetPlayer.UserId, out session) ||
+ session.AttachedEntity == null)
+ return;
+
+ if (msg is PlayerPanelRejuvenationMessage)
+ {
+ _adminLog.Add(LogType.Action,$"{Player:actor} rejuvenated {_entity.ToPrettyString(session.AttachedEntity):subject}");
+ if (!_entity.TrySystem<RejuvenateSystem>(out var rejuvenate))
+ return;
+
+ rejuvenate.PerformRejuvenate(session.AttachedEntity.Value);
+ }
+ else
+ {
+ _adminLog.Add(LogType.Action,$"{Player:actor} deleted {_entity.ToPrettyString(session.AttachedEntity):subject}");
+ _entity.DeleteEntity(session.AttachedEntity);
+ }
+ break;
+ }
+ }
+
+ public async void SetPlayerState()
+ {
+ if (!_admins.IsAdmin(Player))
+ {
+ Close();
+ return;
+ }
+
+ _playtime = (await _db.GetPlayTimes(_targetPlayer.UserId))
+ .Where(p => p.Tracker == "Overall")
+ .Select(p => p.TimeSpent)
+ .FirstOrDefault();
+
+ if (_notesMan.CanView(Player))
+ {
+ _notes = (await _notesMan.GetAllAdminRemarks(_targetPlayer.UserId)).Count;
+ }
+ else
+ {
+ _notes = null;
+ }
+
+ _sharedConnections = _player.Sessions.Count(s => s.Channel.RemoteEndPoint.Address.Equals(_targetPlayer.LastAddress) && s.UserId != _targetPlayer.UserId);
+
+ // Apparently the Bans flag is also used for whitelists
+ if (_admins.HasAdminFlag(Player, AdminFlags.Ban))
+ {
+ _whitelisted = await _db.GetWhitelistStatusAsync(_targetPlayer.UserId);
+ // This won't get associated ip or hwid bans but they were not placed on this account anyways
+ _bans = (await _db.GetServerBansAsync(null, _targetPlayer.UserId, null)).Count;
+ // Unfortunately role bans for departments and stuff are issued individually. This means that a single role ban can have many individual role bans internally
+ // The only way to distinguish whether a role ban is the same is to compare the ban time.
+ // This is horrible and I would love to just erase the database and start from scratch instead but that's what I can do for now.
+ _roleBans = (await _db.GetServerRoleBansAsync(null, _targetPlayer.UserId, null)).DistinctBy(rb => rb.BanTime).Count();
+ }
+ else
+ {
+ _whitelisted = null;
+ _bans = null;
+ _roleBans = null;
+ }
+
+ if (_player.TryGetSessionById(_targetPlayer.UserId, out var session))
+ {
+ _canFreeze = session.AttachedEntity != null;
+ _frozen = _entity.HasComponent<AdminFrozenComponent>(session.AttachedEntity);
+ }
+ else
+ {
+ _canFreeze = false;
+ }
+
+ if (_admins.HasAdminFlag(Player, AdminFlags.Adminhelp))
+ {
+ _canAhelp = true;
+ }
+ else
+ {
+ _canAhelp = false;
+ }
+
+ StateDirty();
+ }
+}
ConfirmationPopup = true,
Impact = LogImpact.High,
});
+
+ // PlayerPanel
+ args.Verbs.Add(new Verb
+ {
+ Text = Loc.GetString("admin-player-actions-player-panel"),
+ Category = VerbCategory.Admin,
+ Act = () => _console.ExecuteCommand(player, $"playerpanel \"{targetActor.PlayerSession.UserId}\""),
+ Impact = LogImpact.Low
+ });
}
// Freeze
var loc = IoCManager.Resolve<IPlayerLocator>();
var name = string.Join(' ', args).Trim();
- var data = await loc.LookupIdByNameAsync(name);
+ var data = await loc.LookupIdByNameOrIdAsync(name);
if (data != null)
{
var loc = IoCManager.Resolve<IPlayerLocator>();
var name = string.Join(' ', args).Trim();
- var data = await loc.LookupIdByNameAsync(name);
+ var data = await loc.LookupIdByNameOrIdAsync(name);
if (data != null)
{
--- /dev/null
+using Content.Shared.Eui;
+using Robust.Shared.Network;
+using Robust.Shared.Serialization;
+using YamlDotNet.Serialization.Callbacks;
+
+namespace Content.Shared.Administration;
+
+[Serializable, NetSerializable]
+public sealed class PlayerPanelEuiState(NetUserId guid,
+ string username,
+ TimeSpan playtime,
+ int? totalNotes,
+ int? totalBans,
+ int? totalRoleBans,
+ int sharedConnections,
+ bool? whitelisted,
+ bool canFreeze,
+ bool frozen,
+ bool canAhelp)
+ : EuiStateBase
+{
+ public readonly NetUserId Guid = guid;
+ public readonly string Username = username;
+ public readonly TimeSpan Playtime = playtime;
+ public readonly int? TotalNotes = totalNotes;
+ public readonly int? TotalBans = totalBans;
+ public readonly int? TotalRoleBans = totalRoleBans;
+ public readonly int SharedConnections = sharedConnections;
+ public readonly bool? Whitelisted = whitelisted;
+ public readonly bool CanFreeze = canFreeze;
+ public readonly bool Frozen = frozen;
+ public readonly bool CanAhelp = canAhelp;
+}
+
+
+[Serializable, NetSerializable]
+public sealed class PlayerPanelFreezeMessage : EuiMessageBase
+{
+ public readonly bool Mute;
+
+ public PlayerPanelFreezeMessage(bool mute = false)
+ {
+ Mute = mute;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class PlayerPanelLogsMessage : EuiMessageBase;
+
+[Serializable, NetSerializable]
+public sealed class PlayerPanelDeleteMessage : EuiMessageBase;
+
+[Serializable, NetSerializable]
+public sealed class PlayerPanelRejuvenationMessage: EuiMessageBase;
admin-player-actions-respawn = Respawn
admin-player-actions-spawn = Spawn here
admin-player-spawn-failed = Failed to find valid coordinates
+admin-player-actions-player-panel = Open Player Panel
admin-player-actions-clone = Clone
admin-player-actions-follow = Follow
--- /dev/null
+player-panel-title = information for {$player}
+player-panel-username = Username: {$player}
+player-panel-whitelisted = Whitelisted:
+player-panel-bans = Total Bans: {$totalBans}
+player-panel-rolebans = Total Role Bans: {$totalRoleBans}
+player-panel-notes = Total Notes: {$totalNotes}
+player-panel-playtime = Total Playtime: {$days}d:{$hours}h:{$minutes}m
+player-panel-shared-connections = Shared Connections: {$sharedConnections}
+
+player-panel-copy-username = Copy
+player-panel-show-notes = Notes
+player-panel-show-bans = Show Bans
+player-panel-help = Ahelp
+player-panel-freeze-and-mute = Freeze & Mute
+player-panel-freeze = Freeze
+player-panel-unfreeze = Unfreeze
+player-panel-kick = Kick
+player-panel-ban = Ban
+player-panel-logs = Logs
+player-panel-delete = Delete
+player-panel-rejuvenate = Rejuvenate
+player-panel-false = False
whitelist-not-whitelisted-rp = You are not whitelisted. To become whitelisted, visit our Discord (which can be found at https://spacestation14.io) and check the #rp-whitelist channel.
cmd-whitelistadd-desc = Adds the player with the given username to the server whitelist.
-cmd-whitelistadd-help = Usage: whitelistadd <username>
+cmd-whitelistadd-help = Usage: whitelistadd <username or User ID>
cmd-whitelistadd-existing = {$username} is already on the whitelist!
cmd-whitelistadd-added = {$username} added to the whitelist
cmd-whitelistadd-not-found = Unable to find '{$username}'
cmd-whitelistadd-arg-player = [player]
cmd-whitelistremove-desc = Removes the player with the given username from the server whitelist.
-cmd-whitelistremove-help = Usage: whitelistremove <username>
+cmd-whitelistremove-help = Usage: whitelistremove <username or User ID>
cmd-whitelistremove-existing = {$username} is not on the whitelist!
cmd-whitelistremove-removed = {$username} removed from the whitelist
cmd-whitelistremove-not-found = Unable to find '{$username}'
--- /dev/null
+cmd-playerpanel-desc = Displays general information and actions for a player
+cmd-playerpanel-help = Usage: playerpanel <name or user ID>
+
+cmd-playerpanel-server = This command cannot be run from the server
+cmd-playerpanel-invalid-arguments = Invalid amount of arguments
+cmd-playerpanel-invalid-player = Player not found
+cmd-playerpanel-completion = <PlayerIndex>