First step in restructuring command alias handling

This decouples Command from CommandMap, and moves the burden of tracking registered
aliases to CommandMap. This allows lots of simplification, and also solves a few
weird usage message issues.

- Active (registered) aliases are now tracked via CommandMapEntry
- Commands overriding other commands' aliases now update the original command's registered alias list properly
- Command alias lists now include prefixed aliases
- Prefixed aliases are now included in command data provided to the client
- Server-side /pocketmine:help is now visible on the client
- Permission testing can now provide context that's more relevant to the command invocation - e.g. if a user doesn't have /time set permission, it'll now show a more specific message where previously it would just show that the permission for /time was denied
- User-specified label is now used for permission messages instead of command name - this is more consistent with user expectations
- /help can now see prefixed aliases
- Removed magic alias registration behaviour for VanillaCommand that I don't think anyone expected
- Aliases are now provided to CommandMap via register() parameters, instead of being retrieved from the provided Command
- Command->get/setAliases(), get/setLabel() and getName() are removed
- Command->getName() pushed down to PluginCommand, as it's only useful for CommandExecutors as a command ID and shouldn't be used anywhere else
This commit is contained in:
Dylan K. Taylor 2025-05-04 15:23:25 +01:00
parent cb4364f8fd
commit 579aecfad7
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
51 changed files with 294 additions and 369 deletions

View File

@ -38,19 +38,17 @@ final class ClosureCommand extends Command{
* @phpstan-param Execute $execute
*/
public function __construct(
string $name,
\Closure $execute,
array $permissions,
Translatable|string $description = "",
Translatable|string|null $usageMessage = null,
array $aliases = []
){
Utils::validateCallableSignature(
fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1,
$execute,
);
$this->execute = $execute;
parent::__construct($name, $description, $usageMessage, $aliases);
parent::__construct($description, $usageMessage);
$this->setPermissions($permissions);
}

View File

@ -33,52 +33,20 @@ use pocketmine\permission\PermissionManager;
use pocketmine\Server;
use pocketmine\utils\BroadcastLoggerForwarder;
use pocketmine\utils\TextFormat;
use function array_values;
use function explode;
use function implode;
use function str_replace;
use const PHP_INT_MAX;
abstract class Command{
private string $name;
private string $nextLabel;
private string $label;
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $aliases = [];
/**
* @var string[]
* @phpstan-var list<string>
*/
private array $activeAliases = [];
private ?CommandMap $commandMap = null;
protected Translatable|string $description = "";
protected Translatable|string $usageMessage;
/** @var string[] */
private array $permission = [];
private Translatable|string|null $permissionMessage = null;
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){
$this->name = $name;
$this->setLabel($name);
$this->setDescription($description);
$this->usageMessage = $usageMessage ?? ("/" . $name);
$this->setAliases($aliases);
}
public function __construct(
private Translatable|string $description = "",
private Translatable|string|null $usageMessage = null
){}
/**
* @param string[] $args
@ -89,10 +57,6 @@ abstract class Command{
*/
abstract public function execute(CommandSender $sender, string $commandLabel, array $args);
public function getName() : string{
return $this->name;
}
/**
* @return string[]
*/
@ -117,12 +81,17 @@ abstract class Command{
$this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX));
}
public function testPermission(CommandSender $target, ?string $permission = null) : bool{
/**
* @param string $context usually the command name, but may include extra args if useful (e.g. for subcommands)
* @param CommandSender $target the target to check the permission for
* @param string|null $permission the permission to check, if null, will check if the target has any of the command's permissions
*/
public function testPermission(string $context, CommandSender $target, ?string $permission = null) : bool{
if($this->testPermissionSilent($target, $permission)){
return true;
}
$message = $this->permissionMessage ?? KnownTranslationFactory::pocketmine_command_error_permission($this->name);
$message = $this->permissionMessage ?? KnownTranslationFactory::pocketmine_command_error_permission($context);
if($message instanceof Translatable){
$target->sendMessage($message->prefix(TextFormat::RED));
}elseif($message !== ""){
@ -143,62 +112,6 @@ abstract class Command{
return false;
}
public function getLabel() : string{
return $this->label;
}
public function setLabel(string $name) : bool{
$this->nextLabel = $name;
if(!$this->isRegistered()){
$this->label = $name;
return true;
}
return false;
}
/**
* Registers the command into a Command map
*/
public function register(CommandMap $commandMap) : bool{
if($this->allowChangesFrom($commandMap)){
$this->commandMap = $commandMap;
return true;
}
return false;
}
public function unregister(CommandMap $commandMap) : bool{
if($this->allowChangesFrom($commandMap)){
$this->commandMap = null;
$this->activeAliases = $this->aliases;
$this->label = $this->nextLabel;
return true;
}
return false;
}
private function allowChangesFrom(CommandMap $commandMap) : bool{
return $this->commandMap === null || $this->commandMap === $commandMap;
}
public function isRegistered() : bool{
return $this->commandMap !== null;
}
/**
* @return string[]
* @phpstan-return list<string>
*/
public function getAliases() : array{
return $this->activeAliases;
}
public function getPermissionMessage() : Translatable|string|null{
return $this->permissionMessage;
}
@ -207,22 +120,10 @@ abstract class Command{
return $this->description;
}
public function getUsage() : Translatable|string{
public function getUsage() : Translatable|string|null{
return $this->usageMessage;
}
/**
* @param string[] $aliases
* @phpstan-param list<string> $aliases
*/
public function setAliases(array $aliases) : void{
$aliases = array_values($aliases); //because plugins can and will pass crap
$this->aliases = $aliases;
if(!$this->isRegistered()){
$this->activeAliases = $aliases;
}
}
public function setDescription(Translatable|string $description) : void{
$this->description = $description;
}
@ -231,7 +132,7 @@ abstract class Command{
$this->permissionMessage = $permissionMessage;
}
public function setUsage(Translatable|string $usage) : void{
public function setUsage(Translatable|string|null $usage) : void{
$this->usageMessage = $usage;
}
@ -252,8 +153,4 @@ abstract class Command{
}
}
}
public function __toString() : string{
return $this->name;
}
}

View File

@ -24,13 +24,12 @@ declare(strict_types=1);
namespace pocketmine\command;
interface CommandMap{
/**
* @param Command[] $commands
* @param string[] $otherAliases
*
* @phpstan-param list<string> $otherAliases
*/
public function registerAll(string $fallbackPrefix, array $commands) : void;
public function register(string $fallbackPrefix, Command $command, ?string $label = null) : bool;
public function register(string $fallbackPrefix, Command $command, string $preferredAlias, array $otherAliases = []) : CommandMapEntry;
public function dispatch(CommandSender $sender, string $cmdLine) : bool;

View File

@ -0,0 +1,48 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\command;
use pocketmine\lang\Translatable;
final class CommandMapEntry{
/**
* @param string[] $aliases
* @phpstan-param non-empty-list<string> $aliases
*/
public function __construct(
public readonly Command $command,
public readonly array $aliases
){}
public function getPreferredAlias() : string{
return $this->aliases[0];
}
public function getUsage() : Translatable|string{
//TODO: usage messages ought to use user-specified alias, not command preferred
//command-preferred is confusing if the user used a different alias
return $this->command->getUsage() ?? "/" . $this->getPreferredAlias();
}
}

View File

@ -50,10 +50,9 @@ class FormattedCommandAlias extends Command{
* @param string[] $formatStrings
*/
public function __construct(
string $alias,
private array $formatStrings
){
parent::__construct($alias, KnownTranslationFactory::pocketmine_command_userDefined_description());
parent::__construct(KnownTranslationFactory::pocketmine_command_userDefined_description());
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
@ -95,12 +94,14 @@ class FormattedCommandAlias extends Command{
throw new AssumptionFailedError("This should have been checked before construction");
}
if(($target = $commandMap->getCommand($commandLabel)) !== null){
$timings = Timings::getCommandDispatchTimings($target->getLabel());
if(($target = $commandMap->getEntry($commandLabel)) !== null){
//TODO: using labels for command dispatch is problematic - what if the label changes?
//maybe this should use command class instead?
$timings = Timings::getCommandDispatchTimings($target->getPreferredAlias());
$timings->startTiming();
try{
$target->execute($sender, $commandLabel, $commandArgs);
$target->command->execute($sender, $commandLabel, $commandArgs);
}catch(InvalidCommandSyntaxException $e){
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage())));
}finally{

View File

@ -24,17 +24,23 @@ declare(strict_types=1);
namespace pocketmine\command;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\Translatable;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginOwned;
final class PluginCommand extends Command implements PluginOwned{
public function __construct(
string $name,
private string $name,
private Plugin $owner,
private CommandExecutor $executor
private CommandExecutor $executor,
Translatable|string $description = "",
Translatable|string|null $usageMessage = null
){
parent::__construct($name);
$this->usageMessage = "";
parent::__construct($description, $usageMessage);
}
public function getName() : string{
return $this->name;
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
@ -43,13 +49,11 @@ final class PluginCommand extends Command implements PluginOwned{
return false;
}
$success = $this->executor->onCommand($sender, $this, $commandLabel, $args);
if(!$success && $this->usageMessage !== ""){
if(!$this->executor->onCommand($sender, $this, $commandLabel, $args)){
throw new InvalidCommandSyntaxException();
}
return $success;
return true;
}
public function getOwningPlugin() : Plugin{

View File

@ -61,7 +61,6 @@ use pocketmine\command\defaults\TimeCommand;
use pocketmine\command\defaults\TimingsCommand;
use pocketmine\command\defaults\TitleCommand;
use pocketmine\command\defaults\TransferServerCommand;
use pocketmine\command\defaults\VanillaCommand;
use pocketmine\command\defaults\VersionCommand;
use pocketmine\command\defaults\WhitelistCommand;
use pocketmine\command\defaults\XpCommand;
@ -70,12 +69,15 @@ use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use function array_filter;
use function array_shift;
use function array_values;
use function count;
use function implode;
use function spl_object_id;
use function str_contains;
use function strcasecmp;
use function strtolower;
@ -87,122 +89,124 @@ class SimpleCommandMap implements CommandMap{
* @var Command[]
* @phpstan-var array<string, Command>
*/
protected array $knownCommands = [];
protected array $aliasToCommandMap = [];
/**
* @var CommandMapEntry[]
* @phpstan-var array<int, CommandMapEntry>
*/
private array $uniqueCommands = [];
public function __construct(private Server $server){
$this->setDefaultCommands();
}
private function setDefaultCommands() : void{
$this->registerAll("pocketmine", [
new BanCommand(),
new BanIpCommand(),
new BanListCommand(),
new ClearCommand(),
new DefaultGamemodeCommand(),
new DeopCommand(),
new DifficultyCommand(),
new DumpMemoryCommand(),
new EffectCommand(),
new EnchantCommand(),
new GamemodeCommand(),
new GarbageCollectorCommand(),
new GiveCommand(),
new HelpCommand(),
new KickCommand(),
new KillCommand(),
new ListCommand(),
new MeCommand(),
new OpCommand(),
new PardonCommand(),
new PardonIpCommand(),
new ParticleCommand(),
new PluginsCommand(),
new SaveCommand(),
new SaveOffCommand(),
new SaveOnCommand(),
new SayCommand(),
new SeedCommand(),
new SetWorldSpawnCommand(),
new SpawnpointCommand(),
new StatusCommand(),
new StopCommand(),
new TeleportCommand(),
new TellCommand(),
new TimeCommand(),
new TimingsCommand(),
new TitleCommand(),
new TransferServerCommand(),
new VersionCommand(),
new WhitelistCommand(),
new XpCommand(),
]);
$pmPrefix = "pocketmine";
$this->register($pmPrefix, new BanCommand(), "ban");
$this->register($pmPrefix, new BanIpCommand(), "ban-ip");
$this->register($pmPrefix, new BanListCommand(), "banlist");
$this->register($pmPrefix, new ClearCommand(), "clear");
$this->register($pmPrefix, new DefaultGamemodeCommand(), "defaultgamemode");
$this->register($pmPrefix, new DeopCommand(), "deop");
$this->register($pmPrefix, new DifficultyCommand(), "difficulty");
$this->register($pmPrefix, new DumpMemoryCommand(), "dumpmemory");
$this->register($pmPrefix, new EffectCommand(), "effect");
$this->register($pmPrefix, new EnchantCommand(), "enchant");
$this->register($pmPrefix, new GamemodeCommand(), "gamemode");
$this->register($pmPrefix, new GarbageCollectorCommand(), "gc");
$this->register($pmPrefix, new GiveCommand(), "give");
$this->register($pmPrefix, new HelpCommand(), "help", ["?"]);
$this->register($pmPrefix, new KickCommand(), "kick");
$this->register($pmPrefix, new KillCommand(), "kill", ["suicide"]);
$this->register($pmPrefix, new ListCommand(), "list");
$this->register($pmPrefix, new MeCommand(), "me");
$this->register($pmPrefix, new OpCommand(), "op");
$this->register($pmPrefix, new PardonCommand(), "pardon", ["unban"]);
$this->register($pmPrefix, new PardonIpCommand(), "pardon-ip", ["unban-ip"]);
$this->register($pmPrefix, new ParticleCommand(), "particle");
$this->register($pmPrefix, new PluginsCommand(), "plugins", ["pl"]);
$this->register($pmPrefix, new SaveCommand(), "save-all");
$this->register($pmPrefix, new SaveOffCommand(), "save-off");
$this->register($pmPrefix, new SaveOnCommand(), "save-on");
$this->register($pmPrefix, new SayCommand(), "say");
$this->register($pmPrefix, new SeedCommand(), "seed");
$this->register($pmPrefix, new SetWorldSpawnCommand(), "setworldspawn");
$this->register($pmPrefix, new SpawnpointCommand(), "spawnpoint");
$this->register($pmPrefix, new StatusCommand(), "status");
$this->register($pmPrefix, new StopCommand(), "stop");
$this->register($pmPrefix, new TeleportCommand(), "tp", ["teleport"]);
$this->register($pmPrefix, new TellCommand(), "tell", ["w", "msg"]);
$this->register($pmPrefix, new TimeCommand(), "time");
$this->register($pmPrefix, new TimingsCommand(), "timings");
$this->register($pmPrefix, new TitleCommand(), "title");
$this->register($pmPrefix, new TransferServerCommand(), "transferserver");
$this->register($pmPrefix, new VersionCommand(), "version", ["ver", "about"]);
$this->register($pmPrefix, new WhitelistCommand(), "whitelist");
$this->register($pmPrefix, new XpCommand(), "xp");
}
public function registerAll(string $fallbackPrefix, array $commands) : void{
foreach($commands as $command){
$this->register($fallbackPrefix, $command);
}
}
public function register(string $fallbackPrefix, Command $command, ?string $label = null) : bool{
public function register(string $fallbackPrefix, Command $command, string $preferredAlias, array $otherAliases = []) : CommandMapEntry{
if(count($command->getPermissions()) === 0){
throw new \InvalidArgumentException("Commands must have a permission set");
}
if($label === null){
$label = $command->getLabel();
}
$label = trim($label);
$preferredAlias = trim($preferredAlias);
$fallbackPrefix = strtolower(trim($fallbackPrefix));
$registered = $this->registerAlias($command, false, $fallbackPrefix, $label);
$registeredAliases = [];
//primary labels take precedence over any existing registrations
$this->mapAlias($preferredAlias, $command, $registeredAliases);
$this->mapAlias($fallbackPrefix . ":" . $preferredAlias, $command, $registeredAliases);
$aliases = $command->getAliases();
foreach($aliases as $index => $alias){
if(!$this->registerAlias($command, true, $fallbackPrefix, $alias)){
unset($aliases[$index]);
foreach($otherAliases as $alias){
$this->mapAlias($fallbackPrefix . ":" . $alias, $command, $registeredAliases);
if(!isset($this->aliasToCommandMap[$alias])){
$this->mapAlias($alias, $command, $registeredAliases);
}
}
$command->setAliases(array_values($aliases));
if(!$registered){
$command->setLabel($fallbackPrefix . ":" . $label);
$entry = new CommandMapEntry($command, $registeredAliases);
$this->uniqueCommands[spl_object_id($command)] = $entry;
return $entry;
}
/**
* @param string[] &$registeredAliases
* @phpstan-param list<string> &$registeredAliases
* @phpstan-param-out non-empty-list<string> $registeredAliases
*/
private function mapAlias(string $alias, Command $command, array &$registeredAliases) : void{
$this->unmapAlias($alias);
$this->aliasToCommandMap[$alias] = $command;
$registeredAliases[] = $alias;
}
private function unmapAlias(string $alias) : void{
$oldCommand = $this->aliasToCommandMap[$alias] ?? null;
if($oldCommand !== null){
unset($this->aliasToCommandMap[$alias]);
$oldCommandKey = spl_object_id($oldCommand);
$oldCommandEntry = $this->uniqueCommands[$oldCommandKey];
$filteredAliases = array_values(array_filter($oldCommandEntry->aliases, fn(string $oldAlias) => $oldAlias !== $alias));
if(count($filteredAliases) > 0){
$this->uniqueCommands[$oldCommandKey] = new CommandMapEntry($oldCommand, $filteredAliases);
}else{
unset($this->uniqueCommands[$oldCommandKey]);
}
}
$command->register($this);
return $registered;
}
public function unregister(Command $command) : bool{
foreach(Utils::promoteKeys($this->knownCommands) as $lbl => $cmd){
if($cmd === $command){
unset($this->knownCommands[$lbl]);
$entry = $this->uniqueCommands[spl_object_id($command)] ?? null;
if($entry !== null){
unset($this->uniqueCommands[spl_object_id($command)]);
foreach($entry->aliases as $alias){
unset($this->aliasToCommandMap[$alias]);
}
}
$command->unregister($this);
return true;
}
private function registerAlias(Command $command, bool $isAlias, string $fallbackPrefix, string $label) : bool{
$this->knownCommands[$fallbackPrefix . ":" . $label] = $command;
if(($command instanceof VanillaCommand || $isAlias) && isset($this->knownCommands[$label])){
return false;
}
if(isset($this->knownCommands[$label]) && $this->knownCommands[$label]->getLabel() === $label){
return false;
}
if(!$isAlias){
$command->setLabel($label);
}
$this->knownCommands[$label] = $command;
return true;
}
@ -210,13 +214,15 @@ class SimpleCommandMap implements CommandMap{
$args = CommandStringHelper::parseQuoteAware($commandLine);
$sentCommandLabel = array_shift($args);
if($sentCommandLabel !== null && ($target = $this->getCommand($sentCommandLabel)) !== null){
$timings = Timings::getCommandDispatchTimings($target->getLabel());
if($sentCommandLabel !== null && ($target = $this->getEntry($sentCommandLabel)) !== null){
//TODO: using labels for command dispatch is problematic - what if the label changes?
//maybe this should use command class instead?
$timings = Timings::getCommandDispatchTimings($target->getPreferredAlias());
$timings->startTiming();
try{
if($target->testPermission($sender)){
$target->execute($sender, $sentCommandLabel, $args);
if($target->command->testPermission($sentCommandLabel, $sender)){
$target->command->execute($sender, $sentCommandLabel, $args);
}
}catch(InvalidCommandSyntaxException $e){
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage())));
@ -231,23 +237,36 @@ class SimpleCommandMap implements CommandMap{
}
public function clearCommands() : void{
foreach($this->knownCommands as $command){
$command->unregister($this);
}
$this->knownCommands = [];
$this->aliasToCommandMap = [];
$this->uniqueCommands = [];
$this->setDefaultCommands();
}
public function getCommand(string $name) : ?Command{
return $this->knownCommands[$name] ?? null;
return $this->aliasToCommandMap[$name] ?? null;
}
/**
* @return Command[]
* @phpstan-return array<string, Command>
*/
public function getCommands() : array{
return $this->knownCommands;
public function getAliasToCommandMap() : array{
return $this->aliasToCommandMap;
}
/**
* @return CommandMapEntry[]
* @phpstan-return array<int, CommandMapEntry>
*/
public function getUniqueCommands() : array{
return $this->uniqueCommands;
}
public function getEntry(string $name) : ?CommandMapEntry{
$command = $this->getCommand($name);
return $command !== null ?
$this->uniqueCommands[spl_object_id($command)] ?? throw new AssumptionFailedError("This should never be unset") :
null;
}
public function registerServerAliases() : void{
@ -289,12 +308,13 @@ class SimpleCommandMap implements CommandMap{
//These registered commands have absolute priority
$lowerAlias = strtolower($alias);
$this->unmapAlias($lowerAlias);
if(count($targets) > 0){
$this->knownCommands[$lowerAlias] = new FormattedCommandAlias($lowerAlias, $targets);
}else{
unset($this->knownCommands[$lowerAlias]);
$aliasInstance = new FormattedCommandAlias($targets);
$registeredAliases = [];
$this->mapAlias($lowerAlias, $aliasInstance, $registeredAliases);
$this->uniqueCommands[spl_object_id($aliasInstance)] = new CommandMapEntry($aliasInstance, $registeredAliases);
}
}
}
}

View File

@ -37,7 +37,6 @@ class BanCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"ban",
KnownTranslationFactory::pocketmine_command_ban_player_description(),
KnownTranslationFactory::commands_ban_usage()
);

View File

@ -38,7 +38,6 @@ class BanIpCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"ban-ip",
KnownTranslationFactory::pocketmine_command_ban_ip_description(),
KnownTranslationFactory::commands_banip_usage()
);

View File

@ -39,7 +39,6 @@ class BanListCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"banlist",
KnownTranslationFactory::pocketmine_command_banlist_description(),
KnownTranslationFactory::commands_banlist_usage()
);

View File

@ -41,7 +41,6 @@ class ClearCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"clear",
KnownTranslationFactory::pocketmine_command_clear_description(),
KnownTranslationFactory::pocketmine_command_clear_usage()
);
@ -53,7 +52,7 @@ class ClearCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
$target = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_CLEAR_SELF, DefaultPermissionNames::COMMAND_CLEAR_OTHER);
$target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_CLEAR_SELF, DefaultPermissionNames::COMMAND_CLEAR_OTHER);
if($target === null){
return true;
}

View File

@ -35,7 +35,6 @@ class DefaultGamemodeCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"defaultgamemode",
KnownTranslationFactory::pocketmine_command_defaultgamemode_description(),
KnownTranslationFactory::commands_defaultgamemode_usage()
);

View File

@ -37,7 +37,6 @@ class DeopCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"deop",
KnownTranslationFactory::pocketmine_command_deop_description(),
KnownTranslationFactory::commands_deop_usage()
);

View File

@ -36,7 +36,6 @@ class DifficultyCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"difficulty",
KnownTranslationFactory::pocketmine_command_difficulty_description(),
KnownTranslationFactory::commands_difficulty_usage()
);

View File

@ -33,7 +33,6 @@ class DumpMemoryCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"dumpmemory",
KnownTranslationFactory::pocketmine_command_dumpmemory_description(),
"/dumpmemory [path]"
);

View File

@ -38,7 +38,6 @@ class EffectCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"effect",
KnownTranslationFactory::pocketmine_command_effect_description(),
KnownTranslationFactory::commands_effect_usage()
);
@ -53,7 +52,7 @@ class EffectCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_EFFECT_SELF, DefaultPermissionNames::COMMAND_EFFECT_OTHER);
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_EFFECT_SELF, DefaultPermissionNames::COMMAND_EFFECT_OTHER);
if($player === null){
return true;
}

View File

@ -36,7 +36,6 @@ class EnchantCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"enchant",
KnownTranslationFactory::pocketmine_command_enchant_description(),
KnownTranslationFactory::commands_enchant_usage()
);
@ -51,7 +50,7 @@ class EnchantCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_ENCHANT_SELF, DefaultPermissionNames::COMMAND_ENCHANT_OTHER);
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_ENCHANT_SELF, DefaultPermissionNames::COMMAND_ENCHANT_OTHER);
if($player === null){
return true;
}

View File

@ -35,7 +35,6 @@ class GamemodeCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"gamemode",
KnownTranslationFactory::pocketmine_command_gamemode_description(),
KnownTranslationFactory::commands_gamemode_usage()
);
@ -56,7 +55,7 @@ class GamemodeCommand extends VanillaCommand{
return true;
}
$target = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_GAMEMODE_SELF, DefaultPermissionNames::COMMAND_GAMEMODE_OTHER);
$target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_GAMEMODE_SELF, DefaultPermissionNames::COMMAND_GAMEMODE_OTHER);
if($target === null){
return true;
}

View File

@ -36,7 +36,6 @@ class GarbageCollectorCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"gc",
KnownTranslationFactory::pocketmine_command_gc_description()
);
$this->setPermission(DefaultPermissionNames::COMMAND_GC);

View File

@ -43,7 +43,6 @@ class GiveCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"give",
KnownTranslationFactory::pocketmine_command_give_description(),
KnownTranslationFactory::pocketmine_command_give_usage()
);
@ -58,7 +57,7 @@ class GiveCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_GIVE_SELF, DefaultPermissionNames::COMMAND_GIVE_OTHER);
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_GIVE_SELF, DefaultPermissionNames::COMMAND_GIVE_OTHER);
if($player === null){
return true;
}

View File

@ -47,10 +47,8 @@ class HelpCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"help",
KnownTranslationFactory::pocketmine_command_help_description(),
KnownTranslationFactory::commands_help_usage(),
["?"]
KnownTranslationFactory::commands_help_usage()
);
$this->setPermission(DefaultPermissionNames::COMMAND_HELP);
}
@ -72,11 +70,13 @@ class HelpCommand extends VanillaCommand{
$pageHeight = $sender->getScreenLineHeight();
//TODO: maybe inject this in the constructor instead of assuming the server's command map?
$commandMap = $sender->getServer()->getCommandMap();
if($commandName === ""){
$commands = [];
foreach($sender->getServer()->getCommandMap()->getCommands() as $command){
if($command->testPermissionSilent($sender)){
$commands[$command->getLabel()] = $command;
foreach($commandMap->getUniqueCommands() as $commandEntry){
if($commandEntry->command->testPermissionSilent($sender)){
$commands[$commandEntry->getPreferredAlias()] = $commandEntry;
}
}
ksort($commands, SORT_NATURAL | SORT_FLAG_CASE);
@ -88,31 +88,31 @@ class HelpCommand extends VanillaCommand{
$sender->sendMessage(KnownTranslationFactory::commands_help_header((string) $pageNumber, (string) count($commands)));
$lang = $sender->getLanguage();
if(isset($commands[$pageNumber - 1])){
foreach($commands[$pageNumber - 1] as $command){
$description = $command->getDescription();
foreach($commands[$pageNumber - 1] as $commandEntry){
$description = $commandEntry->command->getDescription();
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getLabel() . ": " . TextFormat::RESET . $descriptionString);
$sender->sendMessage(TextFormat::DARK_GREEN . "/" . $commandEntry->getPreferredAlias() . ": " . TextFormat::RESET . $descriptionString);
}
}
return true;
}else{
if(($cmd = $sender->getServer()->getCommandMap()->getCommand(strtolower($commandName))) instanceof Command){
if($cmd->testPermissionSilent($sender)){
if(($commandEntry = $commandMap->getEntry(strtolower($commandName))) !== null){
if($commandEntry->command->testPermissionSilent($sender)){
$lang = $sender->getLanguage();
$description = $cmd->getDescription();
$description = $commandEntry->command->getDescription();
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($commandEntry->getPreferredAlias())
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
->prefix(TextFormat::GOLD));
$usage = $cmd->getUsage();
$usage = $commandEntry->getUsage();
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX)))
->prefix(TextFormat::GOLD));
$aliases = $cmd->getAliases();
$aliases = $commandEntry->aliases;
sort($aliases, SORT_NATURAL);
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases))
->prefix(TextFormat::GOLD));

View File

@ -39,7 +39,6 @@ class KickCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"kick",
KnownTranslationFactory::pocketmine_command_kick_description(),
KnownTranslationFactory::commands_kick_usage()
);

View File

@ -35,10 +35,8 @@ class KillCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"kill",
KnownTranslationFactory::pocketmine_command_kill_description(),
KnownTranslationFactory::pocketmine_command_kill_usage(),
["suicide"]
KnownTranslationFactory::pocketmine_command_kill_usage()
);
$this->setPermissions([DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER]);
}
@ -48,7 +46,7 @@ class KillCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
$player = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER);
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER);
if($player === null){
return true;
}

View File

@ -38,7 +38,6 @@ class ListCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"list",
KnownTranslationFactory::pocketmine_command_list_description()
);
$this->setPermission(DefaultPermissionNames::COMMAND_LIST);

View File

@ -36,7 +36,6 @@ class MeCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"me",
KnownTranslationFactory::pocketmine_command_me_description(),
KnownTranslationFactory::commands_me_usage()
);

View File

@ -37,7 +37,6 @@ class OpCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"op",
KnownTranslationFactory::pocketmine_command_op_description(),
KnownTranslationFactory::commands_op_usage()
);

View File

@ -34,10 +34,8 @@ class PardonCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"pardon",
KnownTranslationFactory::pocketmine_command_unban_player_description(),
KnownTranslationFactory::commands_unban_usage(),
["unban"]
KnownTranslationFactory::commands_unban_usage()
);
$this->setPermission(DefaultPermissionNames::COMMAND_UNBAN_PLAYER);
}

View File

@ -35,10 +35,8 @@ class PardonIpCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"pardon-ip",
KnownTranslationFactory::pocketmine_command_unban_ip_description(),
KnownTranslationFactory::commands_unbanip_usage(),
["unban-ip"]
KnownTranslationFactory::commands_unbanip_usage()
);
$this->setPermission(DefaultPermissionNames::COMMAND_UNBAN_IP);
}

View File

@ -76,7 +76,6 @@ class ParticleCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"particle",
KnownTranslationFactory::pocketmine_command_particle_description(),
KnownTranslationFactory::pocketmine_command_particle_usage()
);

View File

@ -38,10 +38,8 @@ class PluginsCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"plugins",
KnownTranslationFactory::pocketmine_command_plugins_description(),
null,
["pl"]
null
);
$this->setPermission(DefaultPermissionNames::COMMAND_PLUGINS);
}

View File

@ -34,7 +34,6 @@ class SaveCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"save-all",
KnownTranslationFactory::pocketmine_command_save_description()
);
$this->setPermission(DefaultPermissionNames::COMMAND_SAVE_PERFORM);

View File

@ -32,7 +32,6 @@ class SaveOffCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"save-off",
KnownTranslationFactory::pocketmine_command_saveoff_description()
);
$this->setPermission(DefaultPermissionNames::COMMAND_SAVE_DISABLE);

View File

@ -32,7 +32,6 @@ class SaveOnCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"save-on",
KnownTranslationFactory::pocketmine_command_saveon_description()
);
$this->setPermission(DefaultPermissionNames::COMMAND_SAVE_ENABLE);

View File

@ -37,7 +37,6 @@ class SayCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"say",
KnownTranslationFactory::pocketmine_command_say_description(),
KnownTranslationFactory::commands_say_usage()
);

View File

@ -32,7 +32,6 @@ class SeedCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"seed",
KnownTranslationFactory::pocketmine_command_seed_description()
);
$this->setPermission(DefaultPermissionNames::COMMAND_SEED);

View File

@ -38,7 +38,6 @@ class SetWorldSpawnCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"setworldspawn",
KnownTranslationFactory::pocketmine_command_setworldspawn_description(),
KnownTranslationFactory::commands_setworldspawn_usage()
);

View File

@ -38,7 +38,6 @@ class SpawnpointCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"spawnpoint",
KnownTranslationFactory::pocketmine_command_spawnpoint_description(),
KnownTranslationFactory::commands_spawnpoint_usage()
);
@ -49,7 +48,7 @@ class SpawnpointCommand extends VanillaCommand{
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
$target = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF, DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER);
$target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF, DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER);
if($target === null){
return true;
}

View File

@ -38,7 +38,6 @@ class StatusCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"status",
KnownTranslationFactory::pocketmine_command_status_description()
);
$this->setPermission(DefaultPermissionNames::COMMAND_STATUS);

View File

@ -32,7 +32,6 @@ class StopCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"stop",
KnownTranslationFactory::pocketmine_command_stop_description()
);
$this->setPermission(DefaultPermissionNames::COMMAND_STOP);

View File

@ -41,10 +41,8 @@ class TeleportCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"tp",
KnownTranslationFactory::pocketmine_command_tp_description(),
KnownTranslationFactory::commands_tp_usage(),
["teleport"]
KnownTranslationFactory::commands_tp_usage()
);
$this->setPermissions([
DefaultPermissionNames::COMMAND_TELEPORT_SELF,
@ -77,7 +75,7 @@ class TeleportCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
$subject = $this->fetchPermittedPlayerTarget($sender, $subjectName, DefaultPermissionNames::COMMAND_TELEPORT_SELF, DefaultPermissionNames::COMMAND_TELEPORT_OTHER);
$subject = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $subjectName, DefaultPermissionNames::COMMAND_TELEPORT_SELF, DefaultPermissionNames::COMMAND_TELEPORT_OTHER);
if($subject === null){
return true;
}

View File

@ -38,10 +38,8 @@ class TellCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"tell",
KnownTranslationFactory::pocketmine_command_tell_description(),
KnownTranslationFactory::commands_message_usage(),
["w", "msg"]
KnownTranslationFactory::commands_message_usage()
);
$this->setPermission(DefaultPermissionNames::COMMAND_TELL);
}

View File

@ -36,7 +36,6 @@ class TimeCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"time",
KnownTranslationFactory::pocketmine_command_time_description(),
KnownTranslationFactory::pocketmine_command_time_usage()
);
@ -53,9 +52,10 @@ class TimeCommand extends VanillaCommand{
if(count($args) < 1){
throw new InvalidCommandSyntaxException();
}
$testPermissionCtx = $commandLabel . " " . $args[0];
if($args[0] === "start"){
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_START)){
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_START)){
return true;
}
foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){
@ -64,7 +64,7 @@ class TimeCommand extends VanillaCommand{
Command::broadcastCommandMessage($sender, "Restarted the time");
return true;
}elseif($args[0] === "stop"){
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_STOP)){
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_STOP)){
return true;
}
foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){
@ -73,7 +73,7 @@ class TimeCommand extends VanillaCommand{
Command::broadcastCommandMessage($sender, "Stopped the time");
return true;
}elseif($args[0] === "query"){
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_QUERY)){
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_QUERY)){
return true;
}
if($sender instanceof Player){
@ -90,7 +90,7 @@ class TimeCommand extends VanillaCommand{
}
if($args[0] === "set"){
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_SET)){
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_SET)){
return true;
}
@ -123,7 +123,7 @@ class TimeCommand extends VanillaCommand{
}
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_time_set((string) $value));
}elseif($args[0] === "add"){
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_ADD)){
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_ADD)){
return true;
}

View File

@ -62,7 +62,6 @@ class TimingsCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"timings",
KnownTranslationFactory::pocketmine_command_timings_description(),
KnownTranslationFactory::pocketmine_command_timings_usage()
);

View File

@ -35,7 +35,6 @@ class TitleCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"title",
KnownTranslationFactory::pocketmine_command_title_description(),
KnownTranslationFactory::commands_title_usage()
);
@ -50,7 +49,7 @@ class TitleCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_TITLE_SELF, DefaultPermissionNames::COMMAND_TITLE_OTHER);
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_TITLE_SELF, DefaultPermissionNames::COMMAND_TITLE_OTHER);
if($player === null){
return true;
}

View File

@ -34,7 +34,6 @@ class TransferServerCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"transferserver",
KnownTranslationFactory::pocketmine_command_transferserver_description(),
KnownTranslationFactory::pocketmine_command_transferserver_usage()
);

View File

@ -36,7 +36,13 @@ abstract class VanillaCommand extends Command{
public const MAX_COORD = 30000000;
public const MIN_COORD = -30000000;
protected function fetchPermittedPlayerTarget(CommandSender $sender, ?string $target, string $selfPermission, string $otherPermission) : ?Player{
protected function fetchPermittedPlayerTarget(
string $testPermissionContext,
CommandSender $sender,
?string $target,
string $selfPermission,
string $otherPermission
) : ?Player{
if($target !== null){
$player = $sender->getServer()->getPlayerByPrefix($target);
}elseif($sender instanceof Player){
@ -49,9 +55,12 @@ abstract class VanillaCommand extends Command{
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
return null;
}
//TODO: using loud testPermission here will generate misleading messages
//e.g. if the sender has self permission and tries to use the command on another player, it will give them a
//generic message saying that they don't have permission to use the command, which is not correct
if(
($player === $sender && $this->testPermission($sender, $selfPermission)) ||
($player !== $sender && $this->testPermission($sender, $otherPermission))
($player === $sender && $this->testPermission($testPermissionContext, $sender, $selfPermission)) ||
($player !== $sender && $this->testPermission($testPermissionContext, $sender, $otherPermission))
){
return $player;
}

View File

@ -42,10 +42,8 @@ class VersionCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"version",
KnownTranslationFactory::pocketmine_command_version_description(),
KnownTranslationFactory::pocketmine_command_version_usage(),
["ver", "about"]
KnownTranslationFactory::pocketmine_command_version_usage()
);
$this->setPermission(DefaultPermissionNames::COMMAND_VERSION);
}

View File

@ -41,7 +41,6 @@ class WhitelistCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"whitelist",
KnownTranslationFactory::pocketmine_command_whitelist_description(),
KnownTranslationFactory::commands_whitelist_usage()
);
@ -56,10 +55,14 @@ class WhitelistCommand extends VanillaCommand{
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
if(count($args) === 0){
throw new InvalidCommandSyntaxException();
}
$testPermissionCtx = $commandLabel . " " . $args[0];
if(count($args) === 1){
switch(strtolower($args[0])){
case "reload":
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_RELOAD)){
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_RELOAD)){
$server = $sender->getServer();
$server->getWhitelisted()->reload();
if($server->hasWhitelist()){
@ -70,7 +73,7 @@ class WhitelistCommand extends VanillaCommand{
return true;
case "on":
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_ENABLE)){
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_ENABLE)){
$server = $sender->getServer();
$server->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, true);
$this->kickNonWhitelistedPlayers($server);
@ -79,14 +82,14 @@ class WhitelistCommand extends VanillaCommand{
return true;
case "off":
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_DISABLE)){
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_DISABLE)){
$sender->getServer()->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, false);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_disabled());
}
return true;
case "list":
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_LIST)){
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_LIST)){
$entries = $sender->getServer()->getWhitelisted()->getAll(true);
sort($entries, SORT_STRING);
$result = implode(", ", $entries);
@ -112,14 +115,14 @@ class WhitelistCommand extends VanillaCommand{
}
switch(strtolower($args[0])){
case "add":
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_ADD)){
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_ADD)){
$sender->getServer()->addWhitelist($args[1]);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_add_success($args[1]));
}
return true;
case "remove":
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_REMOVE)){
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_REMOVE)){
$server = $sender->getServer();
$server->removeWhitelist($args[1]);
if(!$server->isWhitelisted($args[1])){

View File

@ -40,7 +40,6 @@ class XpCommand extends VanillaCommand{
public function __construct(){
parent::__construct(
"xp",
KnownTranslationFactory::pocketmine_command_xp_description(),
KnownTranslationFactory::pocketmine_command_xp_usage()
);
@ -55,7 +54,7 @@ class XpCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
$player = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
if($player === null){
return true;
}

View File

@ -117,13 +117,14 @@ use pocketmine\world\format\io\GlobalItemDataHandlers;
use pocketmine\world\Position;
use pocketmine\world\World;
use pocketmine\YmlServerProperties;
use function array_filter;
use function array_map;
use function array_values;
use function base64_encode;
use function bin2hex;
use function count;
use function get_class;
use function implode;
use function in_array;
use function is_string;
use function json_encode;
use function ord;
@ -1088,23 +1089,23 @@ class NetworkSession{
public function syncAvailableCommands() : void{
$commandData = [];
foreach($this->server->getCommandMap()->getCommands() as $command){
if(isset($commandData[$command->getLabel()]) || $command->getLabel() === "help" || !$command->testPermissionSilent($this->player)){
foreach($this->server->getCommandMap()->getUniqueCommands() as $commandEntry){
if(!$commandEntry->command->testPermissionSilent($this->player)){
continue;
}
$lname = strtolower($command->getLabel());
$aliases = $command->getAliases();
$aliasObj = null;
if(count($aliases) > 0){
if(!in_array($lname, $aliases, true)){
//work around a client bug which makes the original name not show when aliases are used
$aliases[] = $lname;
}
$aliasObj = new CommandEnum(ucfirst($command->getLabel()) . "Aliases", $aliases);
//the client doesn't like it when we override /help
$aliases = array_values(array_filter($commandEntry->aliases, fn(string $alias) => $alias !== "help" && $alias !== "?"));
if(count($aliases) === 0){
continue;
}
$firstNetworkAlias = $aliases[0];
//use filtered aliases for command name discovery - this allows /help to still be shown as /pocketmine:help
//on the client without conflicting with the client's built-in /help command
$lname = strtolower($firstNetworkAlias);
$aliasObj = new CommandEnum(ucfirst($firstNetworkAlias) . "Aliases", $aliases);
$description = $command->getDescription();
$description = $commandEntry->command->getDescription();
$data = new CommandData(
$lname, //TODO: commands containing uppercase letters in the name crash 1.9.0 client
$description instanceof Translatable ? $this->player->getLanguage()->translate($description) : $description,
@ -1117,7 +1118,7 @@ class NetworkSession{
chainedSubCommandData: []
);
$commandData[$command->getLabel()] = $data;
$commandData[] = $data;
}
$this->sendDataPacket(AvailableCommandsPacket::create($commandData, [], [], []));

View File

@ -34,7 +34,6 @@ use pocketmine\utils\Config;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
use function copy;
use function count;
use function dirname;
use function file_exists;
use function fopen;
@ -146,23 +145,12 @@ abstract class PluginBase implements Plugin, CommandExecutor{
* Registers commands declared in the plugin manifest
*/
private function registerYamlCommands() : void{
$pluginCmds = [];
foreach(Utils::stringifyKeys($this->description->getCommands()) as $key => $data){
if(str_contains($key, ":")){
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->description->getFullName(), ":")));
continue;
}
$newCmd = new PluginCommand($key, $this, $this);
if(($description = $data->getDescription()) !== null){
$newCmd->setDescription($description);
}
if(($usageMessage = $data->getUsageMessage()) !== null){
$newCmd->setUsage($usageMessage);
}
$aliasList = [];
foreach($data->getAliases() as $alias){
if(str_contains($alias, ":")){
@ -172,7 +160,13 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$aliasList[] = $alias;
}
$newCmd->setAliases($aliasList);
$newCmd = new PluginCommand(
$key,
$this,
$this,
$data->getDescription() ?? "",
$data->getUsageMessage() ?? ""
);
$newCmd->setPermission($data->getPermission());
@ -180,11 +174,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
$newCmd->setPermissionMessage($permissionDeniedMessage);
}
$pluginCmds[] = $newCmd;
}
if(count($pluginCmds) > 0){
$this->server->getCommandMap()->registerAll($this->description->getName(), $pluginCmds);
$this->server->getCommandMap()->register($this->description->getName(), $newCmd, $key, $aliasList);
}
}