Implement a playerpanel (#30238)

* Basic structure for the player panel ui

* Ensure basic functionality

Player panel now receives and displays basic info

* Make whitelistcommands accept user ids

* Make PlayerPanel use GUIDs where possible

* Add functionality to most playerpanel buttons

* Implement remaining playerpanel features

* Localize everything

* Finish up

* Put command arguments in quotes

I am not sure if it's even possible to have something like a space in
them considering they are guids and usernames but sure why not

* Make playerpanel a verb

* Add Logs button to player panel

* Change Notesbutton text and make whitelistbutton a confirmtion button

* Add freeze button that does not mute the player

* Add sharedconnections counter to playerpanel

* Make the playetime format clearer

* Allow for copying of the a player's username

* Do minor cleanup

* Rearrange buttons

* Fix unfreeze button not updating

* Fix wrong localisation text

* "Fix" the same role ban counting multiple times

The way rolebans are stored is horrible.
As such if you ban someone from a departmenrt or something
role bans are individually placed for every role.
The only way I found to distinguish them is the bantime.
This is horrible but I do not want to rewrite how all the bans are
stored right now.

* Add Delete and Rejuvenate buttons to player panel

By popular demand

* Marginally improve ui

* Add logs

* review update

* Fix verb

* Fix double notes

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
nikthechampiongr
2024-08-09 08:33:25 +03:00
committed by GitHub
parent b266c9b545
commit d1a60fafe7
12 changed files with 603 additions and 4 deletions

View File

@@ -0,0 +1,36 @@
<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>

View File

@@ -0,0 +1,132 @@
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);
}
}

View File

@@ -0,0 +1,72 @@
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();
}
}

View File

@@ -0,0 +1,56 @@
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;
}
}

View File

@@ -0,0 +1,210 @@
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();
}
}

View File

@@ -208,6 +208,15 @@ namespace Content.Server.Administration.Systems
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

View File

@@ -27,7 +27,7 @@ public sealed class AddWhitelistCommand : LocalizedCommands
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)
{
@@ -76,7 +76,7 @@ public sealed class RemoveWhitelistCommand : LocalizedCommands
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)
{

View File

@@ -0,0 +1,54 @@
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;

View File

@@ -7,6 +7,7 @@ admin-player-actions-ahelp = AHelp
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

View File

@@ -0,0 +1,22 @@
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

View File

@@ -11,14 +11,14 @@ whitelist-playercount-invalid = {$min ->
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}'

View File

@@ -0,0 +1,7 @@
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>