From 50976c20aa619637ef3baf0da1fca683b602a822 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 12 Oct 2025 03:30:30 +0100 Subject: [PATCH] Firebombing of commands for the first time in 10 years --- composer.lock | 8 +- src/command/ClosureCommand.php | 3 +- src/command/Command.php | 160 +++++++------ src/command/FormattedCommandAlias.php | 48 +--- src/command/LegacyCommand.php | 140 +++++++++++ src/command/PluginCommand.php | 2 +- src/command/SimpleCommandMap.php | 22 +- src/command/defaults/BanCommand.php | 36 ++- src/command/defaults/BanIpCommand.php | 45 ++-- src/command/defaults/BanListCommand.php | 49 ++-- src/command/defaults/ClearCommand.php | 66 +++-- src/command/defaults/CommandAliasCommand.php | 179 +++++++------- .../defaults/DefaultGamemodeCommand.php | 31 +-- src/command/defaults/DeopCommand.php | 34 +-- src/command/defaults/DifficultyCommand.php | 48 ++-- src/command/defaults/DumpMemoryCommand.php | 17 +- src/command/defaults/EffectCommand.php | 113 ++++----- src/command/defaults/EnchantCommand.php | 61 ++--- src/command/defaults/GamemodeCommand.php | 44 ++-- .../defaults/GarbageCollectorCommand.php | 9 +- src/command/defaults/GiveCommand.php | 68 +++--- src/command/defaults/HelpCommand.php | 150 ++++++------ src/command/defaults/KickCommand.php | 29 +-- src/command/defaults/KillCommand.php | 30 +-- src/command/defaults/ListCommand.php | 10 +- src/command/defaults/MeCommand.php | 28 +-- src/command/defaults/OpCommand.php | 29 +-- src/command/defaults/PardonCommand.php | 25 +- src/command/defaults/PardonIpCommand.php | 31 ++- src/command/defaults/ParticleCommand.php | 81 ++++--- src/command/defaults/PluginsCommand.php | 10 +- src/command/defaults/SaveCommand.php | 9 +- src/command/defaults/SaveOffCommand.php | 9 +- src/command/defaults/SaveOnCommand.php | 9 +- src/command/defaults/SayCommand.php | 22 +- src/command/defaults/SeedCommand.php | 10 +- src/command/defaults/SetWorldSpawnCommand.php | 72 +++--- src/command/defaults/SpawnpointCommand.php | 73 +++--- src/command/defaults/StatusCommand.php | 10 +- src/command/defaults/StopCommand.php | 9 +- src/command/defaults/TeleportCommand.php | 176 ++++++++------ src/command/defaults/TellCommand.php | 28 +-- src/command/defaults/TimeCommand.php | 156 +++++------- src/command/defaults/TimingsCommand.php | 141 +++++------ src/command/defaults/TitleCommand.php | 116 ++++----- .../defaults/TransferServerCommand.php | 31 +-- src/command/defaults/VanillaCommand.php | 121 ---------- src/command/defaults/VersionCommand.php | 100 ++++---- src/command/defaults/WhitelistCommand.php | 159 +++++------- src/command/defaults/XpCommand.php | 86 ++++--- src/command/overload/BoolParameter.php | 48 ++++ src/command/overload/CommandOverload.php | 226 ++++++++++++++++++ src/command/overload/FloatRangeParameter.php | 61 +++++ src/command/overload/IntRangeParameter.php | 67 ++++++ src/command/overload/MappedParameter.php | 75 ++++++ src/command/overload/Parameter.php | 70 ++++++ .../overload/ParameterParseException.php | 30 +++ src/command/overload/RawParameter.php | 54 +++++ src/command/overload/RelativeFloat.php | 44 ++++ .../overload/RelativeFloatParameter.php | 54 +++++ src/command/overload/StringParameter.php | 50 ++++ src/command/utils/CommandStringHelper.php | 28 +++ src/network/mcpe/NetworkSession.php | 3 +- src/utils/Utils.php | 4 +- tests/phpstan/configs/actual-problems.neon | 2 +- tests/phpstan/configs/phpstan-bugs.neon | 6 - 66 files changed, 2279 insertions(+), 1486 deletions(-) create mode 100644 src/command/LegacyCommand.php delete mode 100644 src/command/defaults/VanillaCommand.php create mode 100644 src/command/overload/BoolParameter.php create mode 100644 src/command/overload/CommandOverload.php create mode 100644 src/command/overload/FloatRangeParameter.php create mode 100644 src/command/overload/IntRangeParameter.php create mode 100644 src/command/overload/MappedParameter.php create mode 100644 src/command/overload/Parameter.php create mode 100644 src/command/overload/ParameterParseException.php create mode 100644 src/command/overload/RawParameter.php create mode 100644 src/command/overload/RelativeFloat.php create mode 100644 src/command/overload/RelativeFloatParameter.php create mode 100644 src/command/overload/StringParameter.php diff --git a/composer.lock b/composer.lock index f3a308843..36213ee00 100644 --- a/composer.lock +++ b/composer.lock @@ -349,12 +349,12 @@ "source": { "type": "git", "url": "https://github.com/pmmp/CallbackValidator.git", - "reference": "8e0e3be58e89c2611beac9c6954a036b81b2b6fc" + "reference": "431c7aa5685d4d756bc74a78e5cd130a54d6fdf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/CallbackValidator/zipball/8e0e3be58e89c2611beac9c6954a036b81b2b6fc", - "reference": "8e0e3be58e89c2611beac9c6954a036b81b2b6fc", + "url": "https://api.github.com/repos/pmmp/CallbackValidator/zipball/431c7aa5685d4d756bc74a78e5cd130a54d6fdf8", + "reference": "431c7aa5685d4d756bc74a78e5cd130a54d6fdf8", "shasum": "" }, "require": { @@ -391,7 +391,7 @@ "issues": "https://github.com/pmmp/CallbackValidator/issues", "source": "https://github.com/pmmp/CallbackValidator/tree/rewrite" }, - "time": "2025-10-10T21:55:21+00:00" + "time": "2025-10-11T15:05:09+00:00" }, { "name": "pocketmine/color", diff --git a/src/command/ClosureCommand.php b/src/command/ClosureCommand.php index 4242348fd..5faa05483 100644 --- a/src/command/ClosureCommand.php +++ b/src/command/ClosureCommand.php @@ -29,13 +29,14 @@ use pocketmine\utils\Utils; /** * @phpstan-type Execute \Closure(CommandSender $sender, Command $command, string $commandLabel, list $args) : mixed */ -final class ClosureCommand extends Command{ +final class ClosureCommand extends LegacyCommand{ /** @phpstan-var Execute */ private \Closure $execute; /** * @param string[] $permissions * @phpstan-param Execute $execute + * @phpstan-param list $permissions */ public function __construct( string $namespace, diff --git a/src/command/Command.php b/src/command/Command.php index f64bea7a1..ba54ab4cd 100644 --- a/src/command/Command.php +++ b/src/command/Command.php @@ -26,34 +26,40 @@ declare(strict_types=1); */ namespace pocketmine\command; -use pocketmine\command\utils\CommandException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\Translatable; -use pocketmine\permission\PermissionManager; +use pocketmine\player\Player; use pocketmine\Server; use pocketmine\utils\BroadcastLoggerForwarder; use pocketmine\utils\TextFormat; -use function explode; +use function array_unique; +use function count; use function implode; use function str_replace; use function strtolower; use function trim; -use const PHP_INT_MAX; abstract class Command{ private readonly string $namespace; private readonly string $name; - /** @var string[] */ - private array $permission = []; private Translatable|string|null $permissionMessage = null; + /** + * @param CommandOverload[] $overloads + * @phpstan-param list $overloads + */ public function __construct( string $namespace, string $name, + private array $overloads, private Translatable|string $description = "", - private Translatable|string|null $usageMessage = null ){ + if(count($this->overloads) === 0){ + throw new \InvalidArgumentException("At least one overload must be provided (extend LegacyCommand for classic execute())"); + } if($namespace === ""){ throw new \InvalidArgumentException("Command namespace cannot be empty (set it to, for example, your plugin's name)"); } @@ -65,14 +71,57 @@ abstract class Command{ $this->name = trim($name); } + final public function executeOverloaded(CommandSender $sender, string $aliasUsed, string $rawArgs) : bool{ + foreach($this->overloads as $k => $overload){ + if(!$overload->senderHasAnyPermissions($sender)){ + continue; + } + try{ + $overload->invoke($sender, $aliasUsed, $rawArgs); + return true; + }catch(InvalidCommandSyntaxException $e){ + \GlobalLogger::get()->debug("Overload $k of /$aliasUsed rejected: " . $e->getMessage()); + } + } + + $usages = $this->getUsages($sender, $aliasUsed); + if(count($usages) === 0){ + $message = $this->permissionMessage ?? KnownTranslationFactory::pocketmine_command_error_permission($aliasUsed); + if($message instanceof Translatable){ + $sender->sendMessage($message->prefix(TextFormat::RED)); + }elseif($message !== ""){ + $permissions = []; + foreach($this->overloads as $overload){ + foreach($overload->getPermissions() as $permission){ + $permissions[] = $permission; + } + } + $permissions = array_unique($permissions); + $sender->sendMessage(str_replace("", implode(";", $permissions), $message)); + } + return false; + } + + foreach($usages as $usageMessage){ + $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($usageMessage))); + } + return false; + } + /** - * @param string[] $args - * @phpstan-param list $args - * - * @return mixed - * @throws CommandException + * @return Translatable[] + * @phpstan-return list */ - abstract public function execute(CommandSender $sender, string $commandLabel, array $args); + public function getUsages(CommandSender $sender, string $aliasUsed) : array{ + $usages = []; + foreach($this->overloads as $overload){ + if($overload->senderHasAnyPermissions($sender)){ + $usages[] = new Translatable("/$aliasUsed {%0}", [$overload->getUsage()]); + } + } + + return $usages; + } final public function getNamespace() : string{ return $this->namespace; @@ -93,59 +142,17 @@ abstract class Command{ return "$this->namespace:$this->name"; } - /** - * @return string[] - */ - public function getPermissions() : array{ - return $this->permission; - } - /** * @param string[] $permissions + * @phpstan-param list $permissions */ - public function setPermissions(array $permissions) : void{ - $permissionManager = PermissionManager::getInstance(); - foreach($permissions as $perm){ - if($permissionManager->getPermission($perm) === null){ - throw new \InvalidArgumentException("Cannot use non-existing permission \"$perm\""); - } - } - $this->permission = $permissions; - } - - public function setPermission(?string $permission) : void{ - $this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX)); - } - - /** - * @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; - } - + protected function sendBadPermissionMessage(string $context, CommandSender $sender, array $permissions) : void{ $message = $this->permissionMessage ?? KnownTranslationFactory::pocketmine_command_error_permission($context); if($message instanceof Translatable){ - $target->sendMessage($message->prefix(TextFormat::RED)); + $sender->sendMessage($message->prefix(TextFormat::RED)); }elseif($message !== ""){ - $target->sendMessage(str_replace("", $permission ?? implode(";", $this->permission), $message)); + $sender->sendMessage(str_replace("", implode(";", $permissions), $message)); } - - return false; - } - - public function testPermissionSilent(CommandSender $target, ?string $permission = null) : bool{ - $list = $permission !== null ? [$permission] : $this->permission; - foreach($list as $p){ - if($target->hasPermission($p)){ - return true; - } - } - - return false; } public function getPermissionMessage() : Translatable|string|null{ @@ -156,10 +163,6 @@ abstract class Command{ return $this->description; } - public function getUsage() : Translatable|string|null{ - return $this->usageMessage; - } - public function setDescription(Translatable|string $description) : void{ $this->description = $description; } @@ -168,10 +171,6 @@ abstract class Command{ $this->permissionMessage = $permissionMessage; } - public function setUsage(Translatable|string|null $usage) : void{ - $this->usageMessage = $usage; - } - public static function broadcastCommandMessage(CommandSender $source, Translatable|string $message, bool $sendToSource = true) : void{ $users = $source->getServer()->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_ADMINISTRATIVE); $result = KnownTranslationFactory::chat_type_admin($source->getName(), $message); @@ -189,4 +188,31 @@ abstract class Command{ } } } + + protected static function fetchPermittedPlayerTarget( + CommandSender $sender, + ?string $target, + string $selfPermission, + string $otherPermission + ) : ?Player{ + if($target !== null){ + $player = $sender->getServer()->getPlayerByPrefix($target); + }elseif($sender instanceof Player){ + $player = $sender; + }else{ + throw new InvalidCommandSyntaxException(); + } + + if($player === null){ + $sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED)); + return null; + } + + $permission = $player === $sender ? $selfPermission : $otherPermission; + if(!$sender->hasPermission($permission)){ + $sender->sendMessage(TextFormat::RED . "You don't have permission to use this command on others"); + return null; + } + return $player; + } } diff --git a/src/command/FormattedCommandAlias.php b/src/command/FormattedCommandAlias.php index 3af7376aa..28db49008 100644 --- a/src/command/FormattedCommandAlias.php +++ b/src/command/FormattedCommandAlias.php @@ -24,20 +24,18 @@ declare(strict_types=1); namespace pocketmine\command; use pocketmine\command\utils\CommandStringHelper; -use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\lang\KnownTranslationFactory; -use pocketmine\timings\Timings; -use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\TextFormat; -use function array_shift; +use function addcslashes; use function count; use function implode; use function preg_match; +use function str_contains; use function strlen; use function strpos; use function substr; -class FormattedCommandAlias extends Command{ +class FormattedCommandAlias extends LegacyCommand{ /** * - matches a $ * - captures an optional second $ to indicate required/optional @@ -77,7 +75,7 @@ class FormattedCommandAlias extends Command{ $processedArgs[] = $processedArg; } } - $commands[] = $processedArgs; + $commands[] = implode(" ", $processedArgs); }catch(\InvalidArgumentException $e){ $sender->sendMessage(TextFormat::RED . $e->getMessage()); return false; @@ -85,38 +83,13 @@ class FormattedCommandAlias extends Command{ } $commandMap = $sender->getServer()->getCommandMap(); - foreach($commands as $commandArgs){ - //this approximately duplicates the logic found in SimpleCommandMap::dispatch() - //this is to allow directly invoking the commands without having to rebuild a command string and parse it - //again for no reason - //TODO: a method on CommandMap to invoke a command with pre-parsed arguments would probably be a good idea - //for a future major version - $commandLabel = array_shift($commandArgs); - if($commandLabel === null){ - throw new AssumptionFailedError("This should have been checked before construction"); - } - - //formatted command aliases don't use user-specific aliases since they are globally defined in pocketmine.yml - //using user-specific aliases might break the behaviour - if(($target = $commandMap->getCommand($commandLabel)) instanceof Command){ - - $timings = Timings::getCommandDispatchTimings($target->getId()); - $timings->startTiming(); - - try{ - $target->execute($sender, $commandLabel, $commandArgs); - }catch(InvalidCommandSyntaxException $e){ - $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage() ?? "/$commandLabel"))); - }finally{ - $timings->stopTiming(); - } - }else{ - //TODO: this seems suspicious - why do we continue alias execution if one of the commands is borked? - $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->prefix(TextFormat::RED))); - + foreach($commands as $commandLine){ + $sender->getServer()->getLogger()->debug("Dispatching formatted command: $commandLine"); + if(!$commandMap->dispatch($sender, $commandLine)){ //to match the behaviour of SimpleCommandMap::dispatch() //this shouldn't normally happen, but might happen if the command was unregistered or modified after //the alias was installed + //TODO: maybe we should abort command processing if there was an error??? $result = false; } } @@ -160,6 +133,11 @@ class FormattedCommandAlias extends Command{ $index = $start + strlen($replacement); } + //we need to assemble a command string to call the target commands, so this needs to be properly quoted + if(str_contains($formatString, " ")){ + return '"' . addcslashes($formatString, '"') . '"'; + } + return $formatString; } diff --git a/src/command/LegacyCommand.php b/src/command/LegacyCommand.php new file mode 100644 index 000000000..fb6e3106e --- /dev/null +++ b/src/command/LegacyCommand.php @@ -0,0 +1,140 @@ + + */ + private array $permission = []; + + public function __construct( + string $namespace, + string $name, + Translatable|string $description = "", + private Translatable|string|null $usageMessage = null, + ){ + parent::__construct($namespace, $name, [new CommandOverload( + [new RawParameter("args", "args")], + DefaultPermissionNames::GROUP_USER, + $this->handler(...), + acceptsAliasUsed: true + )], $description); + } + + /** + * @return string[] + * @phpstan-return list + */ + public function getPermissions() : array{ + return $this->permission; + } + + /** + * @param string[] $permissions + * @phpstan-param list $permissions + */ + public function setPermissions(array $permissions) : void{ + $permissionManager = PermissionManager::getInstance(); + foreach($permissions as $perm){ + if($permissionManager->getPermission($perm) === null){ + throw new \InvalidArgumentException("Cannot use non-existing permission \"$perm\""); + } + } + $this->permission = $permissions; + } + + public function setPermission(?string $permission) : void{ + $this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX)); + } + + /** + * @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; + } + + $this->sendBadPermissionMessage($context, $target, $permission !== null ? [$permission] : $this->permission); + + return false; + } + + public function testPermissionSilent(CommandSender $target, ?string $permission = null) : bool{ + $list = $permission !== null ? [$permission] : $this->permission; + foreach($list as $p){ + if($target->hasPermission($p)){ + return true; + } + } + + return false; + } + + public function getUsages(CommandSender $sender, string $aliasUsed) : array{ + if($this->testPermissionSilent($sender)){ + return $this->usageMessage instanceof Translatable ? + [$this->usageMessage] : + [new Translatable("{%0}", [$this->usageMessage ?? "/$aliasUsed"])]; + } + + return []; + } + + private function handler(CommandSender $sender, string $aliasUsed, string $rawArgs = "") : void{ + if(!$this->testPermission($aliasUsed, $sender)){ + return; + } + $args = CommandStringHelper::parseQuoteAware($rawArgs); + $this->execute($sender, $aliasUsed, $args); + } + + /** + * @param string[] $args + * @phpstan-param list $args + * + * @return mixed + * @throws CommandException + */ + abstract protected function execute(CommandSender $sender, string $commandLabel, array $args); +} diff --git a/src/command/PluginCommand.php b/src/command/PluginCommand.php index 3d8762db2..0ec9effab 100644 --- a/src/command/PluginCommand.php +++ b/src/command/PluginCommand.php @@ -28,7 +28,7 @@ use pocketmine\lang\Translatable; use pocketmine\plugin\Plugin; use pocketmine\plugin\PluginOwned; -final class PluginCommand extends Command implements PluginOwned{ +final class PluginCommand extends LegacyCommand implements PluginOwned{ public function __construct( string $namespace, string $name, diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php index 18ae20178..092aacfd1 100644 --- a/src/command/SimpleCommandMap.php +++ b/src/command/SimpleCommandMap.php @@ -66,7 +66,6 @@ use pocketmine\command\defaults\VersionCommand; use pocketmine\command\defaults\WhitelistCommand; use pocketmine\command\defaults\XpCommand; use pocketmine\command\utils\CommandStringHelper; -use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\Server; use pocketmine\timings\Timings; @@ -76,9 +75,11 @@ use function array_filter; use function array_map; use function array_shift; use function count; +use function explode; use function implode; use function is_array; use function is_string; +use function ltrim; use function str_contains; use function strcasecmp; use function strtolower; @@ -146,7 +147,7 @@ class SimpleCommandMap implements CommandMap{ } public function register(Command $command, array $otherAliases = []) : void{ - if(count($command->getPermissions()) === 0){ + if($command instanceof LegacyCommand && count($command->getPermissions()) === 0){ throw new \InvalidArgumentException("Commands must have a permission set"); } @@ -172,10 +173,10 @@ class SimpleCommandMap implements CommandMap{ } public function dispatch(CommandSender $sender, string $commandLine) : bool{ - $args = CommandStringHelper::parseQuoteAware($commandLine); + $parts = explode(" ", ltrim($commandLine), limit: 2); + [$sentCommandLabel, $rawArgs] = count($parts) === 2 ? $parts : [$parts[0], ""]; - $sentCommandLabel = array_shift($args); - if($sentCommandLabel !== null && ($target = $this->getCommand($sentCommandLabel, $sender->getCommandAliasMap())) !== null){ + if(($target = $this->getCommand($sentCommandLabel, $sender->getCommandAliasMap())) !== null){ if(is_array($target)){ self::handleConflicted($sender, $sentCommandLabel, $target, $this->aliasMap); return true; @@ -184,12 +185,7 @@ class SimpleCommandMap implements CommandMap{ $timings->startTiming(); try{ - if($target->testPermission($sentCommandLabel, $sender)){ - $target->execute($sender, $sentCommandLabel, $args); - } - }catch(InvalidCommandSyntaxException $e){ - //TODO: localised command message should use user-provided alias, it shouldn't be hard-baked into the language strings - $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage() ?? "/$sentCommandLabel"))); + $target->executeOverloaded($sender, $sentCommandLabel, $rawArgs); }finally{ $timings->stopTiming(); } @@ -198,7 +194,7 @@ class SimpleCommandMap implements CommandMap{ //Don't love hardcoding the command ID here, but it seems like the only way for now $sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound( - $sentCommandLabel ?? "", + $sentCommandLabel, "/" . $sender->getCommandAliasMap()->getPreferredAlias("pocketmine:help", $this->aliasMap) )->prefix(TextFormat::RED)); return false; @@ -214,7 +210,7 @@ class SimpleCommandMap implements CommandMap{ $candidates = []; $userAliasMap = $sender->getCommandAliasMap(); foreach($conflictedEntries as $c){ - if($c->testPermissionSilent($sender)){ + if(count($c->getUsages($sender, $alias)) > 0){ $candidates[] = "/" . $c->getId(); } } diff --git a/src/command/defaults/BanCommand.php b/src/command/defaults/BanCommand.php index a2587b092..3f8ddcc99 100644 --- a/src/command/defaults/BanCommand.php +++ b/src/command/defaults/BanCommand.php @@ -25,42 +25,38 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RawParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; -use function array_shift; -use function count; -use function implode; -class BanCommand extends VanillaCommand{ +class BanCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload( + [ + new StringParameter("playerName", "player name"), + new RawParameter("reason", "reason") + ], + DefaultPermissionNames::COMMAND_BAN_PLAYER, + self::execute(...) + )], KnownTranslationFactory::pocketmine_command_ban_player_description(), - KnownTranslationFactory::commands_ban_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_BAN_PLAYER); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } + private static function execute(CommandSender $sender, string $playerName, string $reason) : void{ + $sender->getServer()->getNameBans()->addBan($playerName, $reason, null, $sender->getName()); - $name = array_shift($args); - $reason = implode(" ", $args); - - $sender->getServer()->getNameBans()->addBan($name, $reason, null, $sender->getName()); - - if(($player = $sender->getServer()->getPlayerExact($name)) instanceof Player){ + if(($player = $sender->getServer()->getPlayerExact($playerName)) instanceof Player){ $player->kick($reason !== "" ? KnownTranslationFactory::pocketmine_disconnect_ban($reason) : KnownTranslationFactory::pocketmine_disconnect_ban_noReason()); } - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_ban_success($player !== null ? $player->getName() : $name)); - - return true; + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_ban_success($player !== null ? $player->getName() : $playerName)); } } diff --git a/src/command/defaults/BanIpCommand.php b/src/command/defaults/BanIpCommand.php index 5f44cc276..f05235f4a 100644 --- a/src/command/defaults/BanIpCommand.php +++ b/src/command/defaults/BanIpCommand.php @@ -25,56 +25,51 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RawParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; -use function array_shift; -use function count; -use function implode; use function inet_pton; -class BanIpCommand extends VanillaCommand{ +class BanIpCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload( + [ + //TODO: maybe split this into two overloads? + new StringParameter("target", "name or IP address"), + new RawParameter("reason", "reason") + ], + DefaultPermissionNames::COMMAND_BAN_IP, + self::execute(...) + )], KnownTranslationFactory::pocketmine_command_ban_ip_description(), - KnownTranslationFactory::commands_banip_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_BAN_IP); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } + private static function execute(CommandSender $sender, string $target, string $reason) : void{ + if(inet_pton($target) !== false){ + self::processIPBan($target, $sender, $reason); - $value = array_shift($args); - $reason = implode(" ", $args); - - if(inet_pton($value) !== false){ - $this->processIPBan($value, $sender, $reason); - - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_banip_success($value)); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_banip_success($target)); }else{ - if(($player = $sender->getServer()->getPlayerByPrefix($value)) instanceof Player){ + if(($player = $sender->getServer()->getPlayerByPrefix($target)) instanceof Player){ $ip = $player->getNetworkSession()->getIp(); - $this->processIPBan($ip, $sender, $reason); + self::processIPBan($ip, $sender, $reason); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_banip_success_players($ip, $player->getName())); }else{ $sender->sendMessage(KnownTranslationFactory::commands_banip_invalid()); - - return false; } } - - return true; } - private function processIPBan(string $ip, CommandSender $sender, string $reason) : void{ + private static function processIPBan(string $ip, CommandSender $sender, string $reason) : void{ $sender->getServer()->getIPBans()->addBan($ip, $reason, null, $sender->getName()); foreach($sender->getServer()->getOnlinePlayers() as $player){ diff --git a/src/command/defaults/BanListCommand.php b/src/command/defaults/BanListCommand.php index 1ece9dced..447443048 100644 --- a/src/command/defaults/BanListCommand.php +++ b/src/command/defaults/BanListCommand.php @@ -23,59 +23,50 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\BanEntry; +use pocketmine\permission\BanList; use pocketmine\permission\DefaultPermissionNames; use function array_map; use function count; use function implode; use function sort; -use function strtolower; use const SORT_STRING; -class BanListCommand extends VanillaCommand{ +class BanListCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [ + new CommandOverload(["ips"], DefaultPermissionNames::COMMAND_BAN_LIST, self::listIPBans(...)), + new CommandOverload(["players"], DefaultPermissionNames::COMMAND_BAN_LIST, self::listNameBans(...)) + ], KnownTranslationFactory::pocketmine_command_banlist_description(), - KnownTranslationFactory::commands_banlist_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_BAN_LIST); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(isset($args[0])){ - $args[0] = strtolower($args[0]); - if($args[0] === "ips"){ - $list = $sender->getServer()->getIPBans(); - }elseif($args[0] === "players"){ - $list = $sender->getServer()->getNameBans(); - }else{ - throw new InvalidCommandSyntaxException(); - } - }else{ - $list = $sender->getServer()->getNameBans(); - $args[0] = "players"; - } - + private static function printList(BanList $list) : string{ $list = array_map(function(BanEntry $entry) : string{ return $entry->getName(); }, $list->getEntries()); sort($list, SORT_STRING); - $message = implode(", ", $list); + return implode(", ", $list); + } - if($args[0] === "ips"){ - $sender->sendMessage(KnownTranslationFactory::commands_banlist_ips((string) count($list))); - }else{ - $sender->sendMessage(KnownTranslationFactory::commands_banlist_players((string) count($list))); - } + private static function listIPBans(CommandSender $sender) : void{ + $list = $sender->getServer()->getIPBans(); + $sender->sendMessage(KnownTranslationFactory::commands_banlist_ips((string) count($list->getEntries()))); + $sender->sendMessage(self::printList($list)); + } - $sender->sendMessage($message); - - return true; + private static function listNameBans(CommandSender $sender) : void{ + $list = $sender->getServer()->getNameBans(); + $sender->sendMessage(KnownTranslationFactory::commands_banlist_players((string) count($list->getEntries()))); + $sender->sendMessage(self::printList($list)); } } diff --git a/src/command/defaults/ClearCommand.php b/src/command/defaults/ClearCommand.php index 5388e275c..4fb22111d 100644 --- a/src/command/defaults/ClearCommand.php +++ b/src/command/defaults/ClearCommand.php @@ -25,7 +25,11 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\MappedParameter; +use pocketmine\command\overload\ParameterParseException; +use pocketmine\command\overload\StringParameter; use pocketmine\inventory\Inventory; use pocketmine\item\Item; use pocketmine\item\LegacyStringToItemParser; @@ -37,42 +41,36 @@ use pocketmine\utils\TextFormat; use function count; use function min; -class ClearCommand extends VanillaCommand{ +class ClearCommand extends Command{ + + private const SELF_PERM = DefaultPermissionNames::COMMAND_CLEAR_SELF; + private const OTHER_PERM = DefaultPermissionNames::COMMAND_CLEAR_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_clear_description(), - KnownTranslationFactory::pocketmine_command_clear_usage() + [new CommandOverload([ + new StringParameter("playerName", "player name"), + new MappedParameter("targetItem", "item name", static function(string $v) : Item{ + try{ + return StringToItemParser::getInstance()->parse($v) ?? LegacyStringToItemParser::getInstance()->parse($v); + }catch(LegacyStringToItemParserException $e){ + throw new ParameterParseException("Invalid item name: $v"); + } + }), + new IntRangeParameter("maxCount", "max count", -1, 32767) + ], self::OVERLOAD_PERMS, self::execute(...))], + KnownTranslationFactory::pocketmine_command_clear_description() ); - $this->setPermissions([DefaultPermissionNames::COMMAND_CLEAR_SELF, DefaultPermissionNames::COMMAND_CLEAR_OTHER]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) > 3){ - throw new InvalidCommandSyntaxException(); - } - - $target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_CLEAR_SELF, DefaultPermissionNames::COMMAND_CLEAR_OTHER); + private static function execute(CommandSender $sender, ?string $playerName = null, ?Item $targetItem = null, int $maxCount = -1) : void{ + $target = self::fetchPermittedPlayerTarget($sender, $playerName, self::SELF_PERM, self::OTHER_PERM); if($target === null){ - return true; - } - - $targetItem = null; - $maxCount = -1; - if(isset($args[1])){ - try{ - $targetItem = StringToItemParser::getInstance()->parse($args[1]) ?? LegacyStringToItemParser::getInstance()->parse($args[1]); - - if(isset($args[2])){ - $targetItem->setCount($maxCount = $this->getInteger($sender, $args[2], -1)); - } - }catch(LegacyStringToItemParserException $e){ - //vanilla checks this at argument parsing layer, can't come up with a better alternative - $sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->prefix(TextFormat::RED)); - return true; - } + return; } /** @@ -87,27 +85,27 @@ class ClearCommand extends VanillaCommand{ // Checking player's inventory for all the items matching the criteria if($targetItem !== null && $maxCount === 0){ - $count = $this->countItems($inventories, $targetItem); + $count = self::countItems($inventories, $targetItem); if($count > 0){ $sender->sendMessage(KnownTranslationFactory::commands_clear_testing($target->getName(), (string) $count)); }else{ $sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->prefix(TextFormat::RED)); } - return true; + return; } $clearedCount = 0; if($targetItem === null){ // Clear all items from the inventories - $clearedCount += $this->countItems($inventories, null); + $clearedCount += self::countItems($inventories, null); foreach($inventories as $inventory){ $inventory->clearAll(); } }else{ // Clear the item from target's inventory irrelevant of the count if($maxCount === -1){ - $clearedCount += $this->countItems($inventories, $targetItem); + $clearedCount += self::countItems($inventories, $targetItem); foreach($inventories as $inventory){ $inventory->remove($targetItem); } @@ -135,14 +133,12 @@ class ClearCommand extends VanillaCommand{ }else{ $sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->prefix(TextFormat::RED)); } - - return true; } /** * @param Inventory[] $inventories */ - protected function countItems(array $inventories, ?Item $target) : int{ + protected static function countItems(array $inventories, ?Item $target) : int{ $count = 0; foreach($inventories as $inventory){ $contents = $target !== null ? $inventory->all($target) : $inventory->getContents(); diff --git a/src/command/defaults/CommandAliasCommand.php b/src/command/defaults/CommandAliasCommand.php index 5cc9a1d0e..1f5bb3894 100644 --- a/src/command/defaults/CommandAliasCommand.php +++ b/src/command/defaults/CommandAliasCommand.php @@ -25,14 +25,14 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\Translatable; use pocketmine\permission\DefaultPermissionNames; use pocketmine\utils\TextFormat; use pocketmine\utils\Utils; use function array_map; -use function array_shift; use function count; use function implode; use function is_array; @@ -44,118 +44,123 @@ final class CommandAliasCommand extends Command{ private const LIST_PERM = DefaultPermissionNames::COMMAND_CMDALIAS_LIST; public function __construct(string $namespace, string $name){ + $overloads = []; + foreach([false, true] as $global){ + $prefix = $global ? ["global"] : []; + $editPerm = $global ? self::GLOBAL_PERM : self::SELF_PERM; + $overloads[] = new CommandOverload( + [...$prefix, ...[ + "create", + new StringParameter("alias", "alias"), + new StringParameter("target", "target") + ]], + $editPerm, + fn(CommandSender $sender, string $alias, string $target) => $this->createAlias($sender, $alias, $target, $global) + ); + $overloads[] = new CommandOverload( + [...$prefix, ...[ + "delete", + new StringParameter("alias", "alias") + ]], + $editPerm, + fn(CommandSender $sender, string $alias) => $this->deleteAlias($sender, $alias, $global) + ); + $overloads[] = new CommandOverload( + [...$prefix, "list"], + self::LIST_PERM, + fn(CommandSender $sender) => $this->listAliases($sender, $global) + ); + } parent::__construct( $namespace, $name, + $overloads, KnownTranslationFactory::pocketmine_command_cmdalias_description(), - "/cmdalias [global] create OR /cmdalias [global] delete " ); - $this->setPermissions([self::GLOBAL_PERM, self::SELF_PERM, self::LIST_PERM]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } - $parsedArgs = $args; + private function createAlias(CommandSender $sender, string $alias, string $target, bool $global) : void{ $commandMap = $sender->getServer()->getCommandMap(); - if($parsedArgs[0] === "global"){ - $editPermission = self::GLOBAL_PERM; - $permissionCtx = $commandLabel . " global"; - array_shift($parsedArgs); + if($global){ $aliasMap = $commandMap->getAliasMap(); $messageScope = fn(Translatable $t) => KnownTranslationFactory::pocketmine_command_cmdalias_template($t, KnownTranslationFactory::pocketmine_command_cmdalias_scope_global()); $auditLog = true; }else{ - $editPermission = self::SELF_PERM; - $permissionCtx = $commandLabel; $aliasMap = $sender->getCommandAliasMap(); $messageScope = fn(Translatable $t) => KnownTranslationFactory::pocketmine_command_cmdalias_template($t, KnownTranslationFactory::pocketmine_command_cmdalias_scope_userSpecific()); $auditLog = false; } - $operation = array_shift($parsedArgs); - if($operation === "create"){ - if(count($parsedArgs) !== 2){ - throw new InvalidCommandSyntaxException(); - } - if(!$this->testPermission($permissionCtx, $sender, $editPermission)){ - return true; - } + $command = $commandMap->getCommand($target, $sender->getCommandAliasMap()); + if($command === null){ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound( + "/$target", + "/" . $sender->getCommandAliasMap()->getPreferredAlias("pocketmine:help", $sender->getServer()->getCommandMap()->getAliasMap()) + )->prefix(TextFormat::RED)); + return; + } + if(is_array($command)){ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_error_aliasConflict("/$target", implode(", ", array_map(fn(Command $c) => "/" . $c->getId(), $command)))->prefix(TextFormat::RED)); + return; + } + $aliasMap->bindAlias($command->getId(), $alias, override: true); + $message = $messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_create_success("/$alias", "/" . $command->getId())); + if($auditLog){ + Command::broadcastCommandMessage($sender, $message); + }else{ + $sender->sendMessage($message); + } + } - [$alias, $target] = $parsedArgs; - $command = $commandMap->getCommand($target, $sender->getCommandAliasMap()); - if($command === null){ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound( - "/$target", - "/" . $sender->getCommandAliasMap()->getPreferredAlias("pocketmine:help", $sender->getServer()->getCommandMap()->getAliasMap()) - )->prefix(TextFormat::RED)); - return true; - } - if(is_array($command)){ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_error_aliasConflict("/$target", implode(", ", array_map(fn(Command $c) => "/" . $c->getId(), $command)))->prefix(TextFormat::RED)); - return true; - } - $aliasMap->bindAlias($command->getId(), $alias, override: true); - $message = $messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_create_success("/$alias", "/" . $command->getId())); + private function deleteAlias(CommandSender $sender, string $alias, bool $global) : void{ + $commandMap = $sender->getServer()->getCommandMap(); + if($global){ + $aliasMap = $commandMap->getAliasMap(); + $messageScope = fn(Translatable $t) => KnownTranslationFactory::pocketmine_command_cmdalias_template($t, KnownTranslationFactory::pocketmine_command_cmdalias_scope_global()); + $auditLog = true; + }else{ + $aliasMap = $sender->getCommandAliasMap(); + $messageScope = fn(Translatable $t) => KnownTranslationFactory::pocketmine_command_cmdalias_template($t, KnownTranslationFactory::pocketmine_command_cmdalias_scope_userSpecific()); + $auditLog = false; + } + if($aliasMap->unbindAlias($alias)){ + $message = $messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_delete_success("/$alias")); if($auditLog){ Command::broadcastCommandMessage($sender, $message); }else{ $sender->sendMessage($message); } - return true; + }else{ + $sender->sendMessage($messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_delete_notFound("/$alias"))->prefix(TextFormat::RED)); } - if($operation === "delete"){ - if(count($parsedArgs) !== 1){ - throw new InvalidCommandSyntaxException(); - } - if(!$this->testPermission($permissionCtx, $sender, $editPermission)){ - return true; - } + } - $alias = $parsedArgs[0]; - - if($aliasMap->unbindAlias($alias)){ - $message = $messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_delete_success("/$alias")); - if($auditLog){ - Command::broadcastCommandMessage($sender, $message); - }else{ - $sender->sendMessage($message); - } + private function listAliases(CommandSender $sender, bool $global) : void{ + if($global){ + $aliasMap = $sender->getServer()->getCommandMap()->getAliasMap(); + $messageScope = fn(Translatable $t) => KnownTranslationFactory::pocketmine_command_cmdalias_template($t, KnownTranslationFactory::pocketmine_command_cmdalias_scope_global()); + }else{ + $aliasMap = $sender->getCommandAliasMap(); + $messageScope = fn(Translatable $t) => KnownTranslationFactory::pocketmine_command_cmdalias_template($t, KnownTranslationFactory::pocketmine_command_cmdalias_scope_userSpecific()); + } + $allAliases = $aliasMap->getAllAliases(); + if(count($allAliases) === 0){ + $sender->sendMessage($messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_list_noneSet())->prefix(TextFormat::RED)); + return; + } + ksort($allAliases); + foreach(Utils::promoteKeys($allAliases) as $alias => $commandIds){ + if(is_array($commandIds)){ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_cmdalias_list_conflicted( + TextFormat::RED . "/$alias" . TextFormat::RESET, + implode(", ", array_map(fn(string $c) => TextFormat::RED . "/$c" . TextFormat::RESET, $commandIds)) + )); }else{ - $sender->sendMessage($messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_delete_notFound("/$alias"))->prefix(TextFormat::RED)); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_cmdalias_list_normal( + TextFormat::DARK_GREEN . "/$alias" . TextFormat::RESET, + TextFormat::DARK_GREEN . "/$commandIds" . TextFormat::RESET + )); } - return true; } - if($operation === "list"){ - if(count($parsedArgs) !== 0){ - throw new InvalidCommandSyntaxException(); - } - if(!$this->testPermission($permissionCtx, $sender, self::LIST_PERM)){ - return true; - } - $allAliases = $aliasMap->getAllAliases(); - if(count($allAliases) === 0){ - $sender->sendMessage($messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_list_noneSet())->prefix(TextFormat::RED)); - return true; - } - ksort($allAliases); - foreach(Utils::promoteKeys($allAliases) as $alias => $commandIds){ - if(is_array($commandIds)){ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_cmdalias_list_conflicted( - TextFormat::RED . "/$alias" . TextFormat::RESET, - implode(", ", array_map(fn(string $c) => TextFormat::RED . "/$c" . TextFormat::RESET, $commandIds)) - )); - }else{ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_cmdalias_list_normal( - TextFormat::DARK_GREEN . "/$alias" . TextFormat::RESET, - TextFormat::DARK_GREEN . "/$commandIds" . TextFormat::RESET - )); - } - } - return true; - } - - throw new InvalidCommandSyntaxException(); } } diff --git a/src/command/defaults/DefaultGamemodeCommand.php b/src/command/defaults/DefaultGamemodeCommand.php index 3e3797a4a..7890104d5 100644 --- a/src/command/defaults/DefaultGamemodeCommand.php +++ b/src/command/defaults/DefaultGamemodeCommand.php @@ -23,40 +23,35 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\MappedParameter; +use pocketmine\command\overload\ParameterParseException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\GameMode; use pocketmine\ServerProperties; -use function count; -class DefaultGamemodeCommand extends VanillaCommand{ +class DefaultGamemodeCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([ + new MappedParameter( + "gameMode", + "game mode", + static fn(string $v) : GameMode => GameMode::fromString($v) ?? throw new ParameterParseException("Illegal gamemode value") + ) + ], DefaultPermissionNames::COMMAND_DEFAULTGAMEMODE, self::execute(...))], KnownTranslationFactory::pocketmine_command_defaultgamemode_description(), - KnownTranslationFactory::commands_defaultgamemode_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_DEFAULTGAMEMODE); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } - - $gameMode = GameMode::fromString($args[0]); - if($gameMode === null){ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_unknown($args[0])); - return true; - } - - //TODO: this probably shouldn't use the enum name directly + public function execute(CommandSender $sender, GameMode $gameMode) : void{ $sender->getServer()->getConfigGroup()->setConfigString(ServerProperties::GAME_MODE, $gameMode->name); $sender->sendMessage(KnownTranslationFactory::commands_defaultgamemode_success($gameMode->getTranslatableName())); - return true; } } diff --git a/src/command/defaults/DeopCommand.php b/src/command/defaults/DeopCommand.php index 4ec9dd91c..e5bfd1473 100644 --- a/src/command/defaults/DeopCommand.php +++ b/src/command/defaults/DeopCommand.php @@ -25,42 +25,32 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -use pocketmine\player\Player; use pocketmine\utils\TextFormat; -use function array_shift; -use function count; -class DeopCommand extends VanillaCommand{ +class DeopCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload( + [new StringParameter("playerName", "player name")], + DefaultPermissionNames::COMMAND_OP_TAKE, + self::execute(...)) + ], KnownTranslationFactory::pocketmine_command_deop_description(), - KnownTranslationFactory::commands_deop_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_OP_TAKE); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } - - $name = array_shift($args); - if(!Player::isValidUserName($name)){ - throw new InvalidCommandSyntaxException(); - } - - $sender->getServer()->removeOp($name); - if(($player = $sender->getServer()->getPlayerExact($name)) !== null){ + private static function execute(CommandSender $sender, string $playerName) : void{ + $sender->getServer()->removeOp($playerName); + if(($player = $sender->getServer()->getPlayerExact($playerName)) !== null){ $player->sendMessage(KnownTranslationFactory::commands_deop_message()->prefix(TextFormat::GRAY)); } - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_deop_success($name)); - - return true; + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_deop_success($playerName)); } } diff --git a/src/command/defaults/DifficultyCommand.php b/src/command/defaults/DifficultyCommand.php index 9beb2f4a6..f13030c69 100644 --- a/src/command/defaults/DifficultyCommand.php +++ b/src/command/defaults/DifficultyCommand.php @@ -25,49 +25,51 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\MappedParameter; +use pocketmine\command\overload\ParameterParseException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\ServerProperties; use pocketmine\world\World; -use function count; -class DifficultyCommand extends VanillaCommand{ +class DifficultyCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload( + [new MappedParameter( + "difficulty", + "difficulty", + static function(string $v) : int{ + $difficulty = World::getDifficultyFromString($v); + if($difficulty === -1){ + throw new ParameterParseException("Invalid difficulty value"); + } + return $difficulty; + } + )], + DefaultPermissionNames::COMMAND_DIFFICULTY, + self::execute(...)) + ], KnownTranslationFactory::pocketmine_command_difficulty_description(), - KnownTranslationFactory::commands_difficulty_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_DIFFICULTY); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) !== 1){ - throw new InvalidCommandSyntaxException(); - } - - $difficulty = World::getDifficultyFromString($args[0]); - + private static function execute(CommandSender $sender, int $difficulty) : void{ if($sender->getServer()->isHardcore()){ $difficulty = World::DIFFICULTY_HARD; } - if($difficulty !== -1){ - $sender->getServer()->getConfigGroup()->setConfigInt(ServerProperties::DIFFICULTY, $difficulty); + $sender->getServer()->getConfigGroup()->setConfigInt(ServerProperties::DIFFICULTY, $difficulty); - //TODO: add per-world support - foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ - $world->setDifficulty($difficulty); - } - - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_difficulty_success((string) $difficulty)); - }else{ - throw new InvalidCommandSyntaxException(); + //TODO: add per-world support + foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ + $world->setDifficulty($difficulty); } - return true; + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_difficulty_success((string) $difficulty)); } } diff --git a/src/command/defaults/DumpMemoryCommand.php b/src/command/defaults/DumpMemoryCommand.php index 9045a17cc..3c2c52827 100644 --- a/src/command/defaults/DumpMemoryCommand.php +++ b/src/command/defaults/DumpMemoryCommand.php @@ -23,26 +23,31 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RawParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use Symfony\Component\Filesystem\Path; use function date; -class DumpMemoryCommand extends VanillaCommand{ +class DumpMemoryCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload( + [new RawParameter("path", "path")], + DefaultPermissionNames::COMMAND_DUMPMEMORY, + self::execute(...) + )], KnownTranslationFactory::pocketmine_command_dumpmemory_description(), - "/dumpmemory [path]" ); - $this->setPermission(DefaultPermissionNames::COMMAND_DUMPMEMORY); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - $sender->getServer()->getMemoryManager()->dumpServerMemory($args[0] ?? (Path::join($sender->getServer()->getDataPath(), "memory_dumps", date("D_M_j-H.i.s-T_Y"))), 48, 80); - return true; + private static function execute(CommandSender $sender, string $path = "") : void{ + $sender->getServer()->getMemoryManager()->dumpServerMemory($path !== "" ? $path : (Path::join($sender->getServer()->getDataPath(), "memory_dumps", date("D_M_j-H.i.s-T_Y"))), 48, 80); } } diff --git a/src/command/defaults/EffectCommand.php b/src/command/defaults/EffectCommand.php index 6127b8627..4d7d813f1 100644 --- a/src/command/defaults/EffectCommand.php +++ b/src/command/defaults/EffectCommand.php @@ -23,81 +23,84 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\BoolParameter; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\MappedParameter; +use pocketmine\command\overload\ParameterParseException; +use pocketmine\command\overload\StringParameter; +use pocketmine\entity\effect\Effect; use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\StringToEffectParser; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\utils\Limits; -use pocketmine\utils\TextFormat; use function count; -use function strtolower; -class EffectCommand extends VanillaCommand{ +class EffectCommand extends Command{ + + private const SELF_PERM = DefaultPermissionNames::COMMAND_EFFECT_OTHER; + private const OTHER_PERM = DefaultPermissionNames::COMMAND_EFFECT_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [ + new CommandOverload([ + //TODO: this should be a target param in the future + new StringParameter("target", "target"), + "clear" + + //TODO: our permission system isn't granular enough for this right now - the permission required + //differs not by the usage, but by the target selected + ], self::OVERLOAD_PERMS, self::removeEffect(...)), + new CommandOverload([ + new StringParameter("target", "target"), + new MappedParameter("effect", "effect name", static fn(string $v) : Effect => + StringToEffectParser::getInstance()->parse($v) ?? + throw new ParameterParseException("Invalid effect name") + ), + new IntRangeParameter("duration", "duration", 0, (int) (Limits::INT32_MAX / 20)), + new IntRangeParameter("amplifier", "amplifier", 0, 255), + new BoolParameter("bubbles", "bubbles") + + //TODO: our permission system isn't granular enough for this right now - the permission required + //differs not by the usage, but by the target selected + ], self::OVERLOAD_PERMS, self::modifyEffect(...)) + ], KnownTranslationFactory::pocketmine_command_effect_description(), - KnownTranslationFactory::commands_effect_usage() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_EFFECT_SELF, - DefaultPermissionNames::COMMAND_EFFECT_OTHER - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 2){ - throw new InvalidCommandSyntaxException(); - } - - $player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_EFFECT_SELF, DefaultPermissionNames::COMMAND_EFFECT_OTHER); + private static function removeEffect(CommandSender $sender, string $target) : void{ + $player = self::fetchPermittedPlayerTarget($sender, $target, self::SELF_PERM, self::OTHER_PERM); if($player === null){ - return true; + return; } $effectManager = $player->getEffects(); + $effectManager->clear(); - if(strtolower($args[1]) === "clear"){ - $effectManager->clear(); + $sender->sendMessage(KnownTranslationFactory::commands_effect_success_removed_all($player->getDisplayName())); + } - $sender->sendMessage(KnownTranslationFactory::commands_effect_success_removed_all($player->getDisplayName())); - return true; - } - - $effect = StringToEffectParser::getInstance()->parse($args[1]); - if($effect === null){ - $sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->prefix(TextFormat::RED)); - return true; - } - - $amplification = 0; - - if(count($args) >= 3){ - if(($d = $this->getBoundedInt($sender, $args[2], 0, (int) (Limits::INT32_MAX / 20))) === null){ - return false; - } - $duration = $d * 20; //ticks - }else{ - $duration = null; - } - - if(count($args) >= 4){ - $amplification = $this->getBoundedInt($sender, $args[3], 0, 255); - if($amplification === null){ - return false; - } - } - - $visible = true; - if(count($args) >= 5){ - $v = strtolower($args[4]); - if($v === "on" || $v === "true" || $v === "t" || $v === "1"){ - $visible = false; - } + private function modifyEffect( + CommandSender $sender, + string $target, + Effect $effect, + ?int $duration = null, + int $amplifier = 0, + bool $bubbles = true + ) : void{ + $player = self::fetchPermittedPlayerTarget($sender, $target, self::SELF_PERM, self::OTHER_PERM); + if($player === null){ + return; } + $effectManager = $player->getEffects(); if($duration === 0){ if(!$effectManager->has($effect)){ @@ -106,17 +109,15 @@ class EffectCommand extends VanillaCommand{ }else{ $sender->sendMessage(KnownTranslationFactory::commands_effect_failure_notActive($effect->getName(), $player->getDisplayName())); } - return true; + return; } $effectManager->remove($effect); $sender->sendMessage(KnownTranslationFactory::commands_effect_success_removed($effect->getName(), $player->getDisplayName())); }else{ - $instance = new EffectInstance($effect, $duration, $amplification, $visible); + $instance = new EffectInstance($effect, $duration !== null ? $duration * 20 : null, $amplifier, $bubbles); $effectManager->add($instance); self::broadcastCommandMessage($sender, KnownTranslationFactory::commands_effect_success($effect->getName(), (string) $instance->getAmplifier(), $player->getDisplayName(), (string) ($instance->getDuration() / 20))); } - - return true; } } diff --git a/src/command/defaults/EnchantCommand.php b/src/command/defaults/EnchantCommand.php index 428e78826..821178306 100644 --- a/src/command/defaults/EnchantCommand.php +++ b/src/command/defaults/EnchantCommand.php @@ -23,59 +23,61 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\MappedParameter; +use pocketmine\command\overload\ParameterParseException; +use pocketmine\command\overload\StringParameter; use pocketmine\item\enchantment\EnchantingHelper; +use pocketmine\item\enchantment\Enchantment; use pocketmine\item\enchantment\EnchantmentInstance; use pocketmine\item\enchantment\StringToEnchantmentParser; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -use function count; +use pocketmine\utils\TextFormat; -class EnchantCommand extends VanillaCommand{ +class EnchantCommand extends Command{ + + private const string SELF_PERM = DefaultPermissionNames::COMMAND_ENCHANT_SELF; + private const string OTHER_PERM = DefaultPermissionNames::COMMAND_ENCHANT_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_enchant_description(), - KnownTranslationFactory::commands_enchant_usage() + [new CommandOverload([ + new StringParameter("target", "target"), + new MappedParameter("enchantment", "enchantment", static fn(string $v) : Enchantment => StringToEnchantmentParser::getInstance()->parse($v) ?? + throw new ParameterParseException("Invalid enchantment name") + ), + //sad, this one depends on previous parameters :( + new IntRangeParameter("level", "level", 1, 10) + ], self::OVERLOAD_PERMS, self::enchant(...))], + KnownTranslationFactory::pocketmine_command_enchant_description() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_ENCHANT_SELF, - DefaultPermissionNames::COMMAND_ENCHANT_OTHER - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 2){ - throw new InvalidCommandSyntaxException(); - } - - $player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_ENCHANT_SELF, DefaultPermissionNames::COMMAND_ENCHANT_OTHER); + private function enchant(CommandSender $sender, string $target, Enchantment $enchantment, int $level = 1) : void{ + $player = self::fetchPermittedPlayerTarget($sender, $target, self::SELF_PERM, self::OTHER_PERM); if($player === null){ - return true; + return; } $item = $player->getMainHandItem(); if($item->isNull()){ $sender->sendMessage(KnownTranslationFactory::commands_enchant_noItem()); - return true; + return; } - $enchantment = StringToEnchantmentParser::getInstance()->parse($args[1]); - if($enchantment === null){ - $sender->sendMessage(KnownTranslationFactory::commands_enchant_notFound($args[1])); - return true; - } - - $level = 1; - if(isset($args[2])){ - $level = $this->getBoundedInt($sender, $args[2], 1, $enchantment->getMaxLevel()); - if($level === null){ - return false; - } + $max = $enchantment->getMaxLevel(); + if($level > $max){ + $sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooBig("$level", "$max")->prefix(TextFormat::RED)); + return; } //this is necessary to deal with enchanted books, which are a different item type than regular books @@ -83,6 +85,5 @@ class EnchantCommand extends VanillaCommand{ $player->setMainHandItem($enchantedItem); self::broadcastCommandMessage($sender, KnownTranslationFactory::commands_enchant_success($player->getName())); - return true; } } diff --git a/src/command/defaults/GamemodeCommand.php b/src/command/defaults/GamemodeCommand.php index a7afbefb9..ea87c79df 100644 --- a/src/command/defaults/GamemodeCommand.php +++ b/src/command/defaults/GamemodeCommand.php @@ -25,46 +25,44 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\MappedParameter; +use pocketmine\command\overload\ParameterParseException; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\GameMode; -use function count; -class GamemodeCommand extends VanillaCommand{ +class GamemodeCommand extends Command{ + + private const string SELF_PERM = DefaultPermissionNames::COMMAND_GAMEMODE_SELF; + private const string OTHER_PERM = DefaultPermissionNames::COMMAND_GAMEMODE_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([ + new MappedParameter("gameMode", "game mode", static fn(string $v) : GameMode => + GameMode::fromString($v) ?? throw new ParameterParseException("Invalid game mode: $v") + ), + new StringParameter("target", "target") + ], self::OVERLOAD_PERMS, self::execute(...))], KnownTranslationFactory::pocketmine_command_gamemode_description(), - KnownTranslationFactory::commands_gamemode_usage() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_GAMEMODE_SELF, - DefaultPermissionNames::COMMAND_GAMEMODE_OTHER - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } - - $gameMode = GameMode::fromString($args[0]); - if($gameMode === null){ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_unknown($args[0])); - return true; - } - - $target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_GAMEMODE_SELF, DefaultPermissionNames::COMMAND_GAMEMODE_OTHER); + private static function execute(CommandSender $sender, GameMode $gameMode, ?string $target = null) : void{ + $target = self::fetchPermittedPlayerTarget($sender, $target, self::SELF_PERM, self::OTHER_PERM); if($target === null){ - return true; + return; } if($target->getGamemode() === $gameMode){ $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName())); - return true; + return; } $target->setGamemode($gameMode); @@ -78,7 +76,5 @@ class GamemodeCommand extends VanillaCommand{ Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_gamemode_success_other($gameMode->getTranslatableName(), $target->getName())); } } - - return true; } } diff --git a/src/command/defaults/GarbageCollectorCommand.php b/src/command/defaults/GarbageCollectorCommand.php index d29fbaa58..bf49b7f0b 100644 --- a/src/command/defaults/GarbageCollectorCommand.php +++ b/src/command/defaults/GarbageCollectorCommand.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\utils\TextFormat; @@ -32,18 +34,18 @@ use function memory_get_usage; use function number_format; use function round; -class GarbageCollectorCommand extends VanillaCommand{ +class GarbageCollectorCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_GC, self::execute(...))], KnownTranslationFactory::pocketmine_command_gc_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_GC); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ $chunksCollected = 0; $entitiesCollected = 0; @@ -66,6 +68,5 @@ class GarbageCollectorCommand extends VanillaCommand{ $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_cycles(TextFormat::RED . number_format($cyclesCollected))->prefix(TextFormat::GOLD)); $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_memoryFreed(TextFormat::RED . number_format(round((($memory - memory_get_usage()) / 1024) / 1024, 2), 2))->prefix(TextFormat::GOLD)); - return true; } } diff --git a/src/command/defaults/GiveCommand.php b/src/command/defaults/GiveCommand.php index a711245a4..2bc7fab54 100644 --- a/src/command/defaults/GiveCommand.php +++ b/src/command/defaults/GiveCommand.php @@ -25,7 +25,11 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\ParameterParseException; +use pocketmine\command\overload\RawParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\item\LegacyStringToItemParser; use pocketmine\item\LegacyStringToItemParserException; use pocketmine\item\StringToItemParser; @@ -34,67 +38,54 @@ use pocketmine\nbt\JsonNbtParser; use pocketmine\nbt\NbtDataException; use pocketmine\nbt\NbtException; use pocketmine\permission\DefaultPermissionNames; -use pocketmine\utils\TextFormat; -use function array_slice; -use function count; -use function implode; -class GiveCommand extends VanillaCommand{ +class GiveCommand extends Command{ + + private const string SELF_PERM = DefaultPermissionNames::COMMAND_GIVE_SELF; + private const string OTHER_PERM = DefaultPermissionNames::COMMAND_GIVE_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([ + new StringParameter("target", "target"), + new StringParameter("itemName", "item"), + new IntRangeParameter("count", "count", 1, 32767), + new RawParameter("nbt", "nbt") + ], self::OVERLOAD_PERMS, self::execute(...))], KnownTranslationFactory::pocketmine_command_give_description(), - KnownTranslationFactory::pocketmine_command_give_usage() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_GIVE_SELF, - DefaultPermissionNames::COMMAND_GIVE_OTHER - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 2){ - throw new InvalidCommandSyntaxException(); - } - - $player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_GIVE_SELF, DefaultPermissionNames::COMMAND_GIVE_OTHER); + private static function execute(CommandSender $sender, string $target, string $itemName, ?int $count = null, ?string $nbt = null) : void{ + $player = self::fetchPermittedPlayerTarget($sender, $target, self::SELF_PERM, self::OTHER_PERM); if($player === null){ - return true; + return; } try{ - $item = StringToItemParser::getInstance()->parse($args[1]) ?? LegacyStringToItemParser::getInstance()->parse($args[1]); - }catch(LegacyStringToItemParserException $e){ - $sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->prefix(TextFormat::RED)); - return true; + $item = StringToItemParser::getInstance()->parse($itemName) ?? LegacyStringToItemParser::getInstance()->parse($itemName); + }catch(LegacyStringToItemParserException){ + throw new ParameterParseException("Invalid item name $itemName"); } + $item->setCount($count ?? $item->getMaxStackSize()); - if(!isset($args[2])){ - $item->setCount($item->getMaxStackSize()); - }else{ - $count = $this->getBoundedInt($sender, $args[2], 1, 32767); - if($count === null){ - return true; - } - $item->setCount($count); - } - - if(isset($args[3])){ - $data = implode(" ", array_slice($args, 3)); + if($nbt !== null){ try{ - $tags = JsonNbtParser::parseJson($data); + $tags = JsonNbtParser::parseJson($nbt); }catch(NbtDataException $e){ $sender->sendMessage(KnownTranslationFactory::commands_give_tagError($e->getMessage())); - return true; + return; } try{ $item->setNamedTag($tags); }catch(NbtException $e){ $sender->sendMessage(KnownTranslationFactory::commands_give_tagError($e->getMessage())); - return true; + return; } } @@ -102,10 +93,9 @@ class GiveCommand extends VanillaCommand{ $player->getInventory()->addItem($item); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_give_success( - $item->getName() . " (" . $args[1] . ")", + $item->getName() . " ($itemName)", (string) $item->getCount(), $player->getName() )); - return true; } } diff --git a/src/command/defaults/HelpCommand.php b/src/command/defaults/HelpCommand.php index 818fdb9ea..927d2ff53 100644 --- a/src/command/defaults/HelpCommand.php +++ b/src/command/defaults/HelpCommand.php @@ -25,6 +25,9 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\command\SimpleCommandMap; use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\Translatable; @@ -34,12 +37,10 @@ use pocketmine\utils\TextFormat; use pocketmine\utils\Utils; use function array_chunk; use function array_key_first; -use function array_pop; use function count; use function explode; use function implode; use function is_array; -use function is_numeric; use function ksort; use function min; use function sort; @@ -48,98 +49,93 @@ use const PHP_INT_MAX; use const SORT_FLAG_CASE; use const SORT_NATURAL; -class HelpCommand extends VanillaCommand{ +class HelpCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [ + new CommandOverload([ + new IntRangeParameter("pageNumber", "page", 1, PHP_INT_MAX) + ], DefaultPermissionNames::COMMAND_HELP, self::commandListPage(...)), + new CommandOverload([ + new StringParameter("commandName", "command name") + ], DefaultPermissionNames::COMMAND_HELP, self::commandSpecificInfo(...)) + ], KnownTranslationFactory::pocketmine_command_help_description(), - KnownTranslationFactory::commands_help_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_HELP); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - $commandName = ""; - $pageNumber = 1; - }elseif(is_numeric($args[count($args) - 1])){ - $pageNumber = (int) array_pop($args); - if($pageNumber <= 0){ - $pageNumber = 1; - } - $commandName = implode(" ", $args); - }else{ - $commandName = implode(" ", $args); - $pageNumber = 1; - } - + private static function commandListPage(CommandSender $sender, int $pageNumber = 1) : void{ $pageHeight = $sender->getScreenLineHeight(); //TODO: maybe inject this in the constructor instead of assuming the server's command map? $commandMap = $sender->getServer()->getCommandMap(); $userAliasMap = $sender->getCommandAliasMap(); - if($commandName === ""){ - $commands = []; - foreach($commandMap->getUniqueCommands() as $command){ - if($command->testPermissionSilent($sender)){ - $userAliases = $userAliasMap->getMergedAliases($command->getId(), $commandMap->getAliasMap()); - $preferredAlias = $userAliases[array_key_first($userAliases)]; - if(isset($commands[$preferredAlias])){ - throw new AssumptionFailedError("Something weird happened during user/global alias resolving"); - } - $commands[$preferredAlias] = $command; + $commands = []; + foreach($commandMap->getUniqueCommands() as $command){ + if(count($command->getUsages($sender, "")) > 0){ + $userAliases = $userAliasMap->getMergedAliases($command->getId(), $commandMap->getAliasMap()); + $preferredAlias = $userAliases[array_key_first($userAliases)]; + if(isset($commands[$preferredAlias])){ + throw new AssumptionFailedError("Something weird happened during user/global alias resolving"); } + $commands[$preferredAlias] = $command; } - ksort($commands, SORT_NATURAL | SORT_FLAG_CASE); - $commands = array_chunk($commands, $pageHeight, preserve_keys: true); - $pageNumber = min(count($commands), $pageNumber); - if($pageNumber < 1){ - $pageNumber = 1; + } + ksort($commands, SORT_NATURAL | SORT_FLAG_CASE); + $commands = array_chunk($commands, $pageHeight, preserve_keys: true); + $pageNumber = min(count($commands), $pageNumber); + if($pageNumber < 1){ + $pageNumber = 1; + } + $sender->sendMessage(KnownTranslationFactory::commands_help_header((string) $pageNumber, (string) count($commands))); + $lang = $sender->getLanguage(); + if(isset($commands[$pageNumber - 1])){ + foreach(Utils::promoteKeys($commands[$pageNumber - 1]) as $preferredAlias => $command){ + $description = $command->getDescription(); + $descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description; + $sender->sendMessage(TextFormat::DARK_GREEN . "/$preferredAlias: " . TextFormat::RESET . $descriptionString); } - $sender->sendMessage(KnownTranslationFactory::commands_help_header((string) $pageNumber, (string) count($commands))); - $lang = $sender->getLanguage(); - if(isset($commands[$pageNumber - 1])){ - foreach(Utils::promoteKeys($commands[$pageNumber - 1]) as $preferredAlias => $command){ - $description = $command->getDescription(); - $descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description; - $sender->sendMessage(TextFormat::DARK_GREEN . "/$preferredAlias: " . TextFormat::RESET . $descriptionString); - } - } - - return true; - }else{ - if(($command = $commandMap->getCommand(strtolower($commandName), $userAliasMap)) !== null){ - if(is_array($command)){ - SimpleCommandMap::handleConflicted($sender, $commandName, $command, $commandMap->getAliasMap()); - return true; - } - if($command->testPermissionSilent($sender)){ - $lang = $sender->getLanguage(); - $description = $command->getDescription(); - $descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description; - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($commandName) - ->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------")); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString) - ->prefix(TextFormat::GOLD)); - - $usage = $command->getUsage() ?? "/$commandName"; - $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 = $userAliasMap->getMergedAliases($command->getId(), $commandMap->getAliasMap()); - sort($aliases, SORT_NATURAL); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases)) - ->prefix(TextFormat::GOLD)); - - return true; - } - } - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/" . $userAliasMap->getPreferredAlias("pocketmine:help", $commandMap->getAliasMap()))->prefix(TextFormat::RED)); - - return true; } } + + private static function commandSpecificInfo(CommandSender $sender, string $commandName) : void{ + //TODO: maybe inject this in the constructor instead of assuming the server's command map? + $commandMap = $sender->getServer()->getCommandMap(); + $userAliasMap = $sender->getCommandAliasMap(); + if(($command = $commandMap->getCommand(strtolower($commandName), $userAliasMap)) !== null){ + if(is_array($command)){ + SimpleCommandMap::handleConflicted($sender, $commandName, $command, $commandMap->getAliasMap()); + return; + } + + $lang = $sender->getLanguage(); + $usages = $command->getUsages($sender, $commandName); + + if(count($usages) > 0){ //only permitted usages are shown + $description = $command->getDescription(); + $descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description; + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($commandName) + ->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------")); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString) + ->prefix(TextFormat::GOLD)); + + $aliases = $userAliasMap->getMergedAliases($command->getId(), $commandMap->getAliasMap()); + sort($aliases, SORT_NATURAL); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases)) + ->prefix(TextFormat::GOLD)); + + foreach($usages as $usage){ + $usageString = $lang->translate($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)); + } + + return; + } + } + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/" . $userAliasMap->getPreferredAlias("pocketmine:help", $commandMap->getAliasMap()))->prefix(TextFormat::RED)); + } } diff --git a/src/command/defaults/KickCommand.php b/src/command/defaults/KickCommand.php index 01a1bbbfc..fe3527df2 100644 --- a/src/command/defaults/KickCommand.php +++ b/src/command/defaults/KickCommand.php @@ -25,37 +25,30 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RawParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use pocketmine\utils\TextFormat; -use function array_shift; -use function count; -use function implode; -use function trim; -class KickCommand extends VanillaCommand{ +class KickCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([ + new StringParameter("playerName", "player name"), + new RawParameter("reason", "reason") + ], DefaultPermissionNames::COMMAND_KICK, self::execute(...))], KnownTranslationFactory::pocketmine_command_kick_description(), - KnownTranslationFactory::commands_kick_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_KICK); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } - - $name = array_shift($args); - $reason = trim(implode(" ", $args)); - - if(($player = $sender->getServer()->getPlayerByPrefix($name)) instanceof Player){ + private static function execute(CommandSender $sender, string $playerName, string $reason = "") : void{ + if(($player = $sender->getServer()->getPlayerByPrefix($playerName)) instanceof Player){ $player->kick($reason !== "" ? KnownTranslationFactory::pocketmine_disconnect_kick($reason) : KnownTranslationFactory::pocketmine_disconnect_kick_noReason()); if($reason !== ""){ Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_kick_success_reason($player->getName(), $reason)); @@ -65,7 +58,5 @@ class KickCommand extends VanillaCommand{ }else{ $sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED)); } - - return true; } } diff --git a/src/command/defaults/KillCommand.php b/src/command/defaults/KillCommand.php index 91d390221..892640647 100644 --- a/src/command/defaults/KillCommand.php +++ b/src/command/defaults/KillCommand.php @@ -25,32 +25,34 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\StringParameter; use pocketmine\event\entity\EntityDamageEvent; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -use function count; -class KillCommand extends VanillaCommand{ +class KillCommand extends Command{ + + private const string SELF_PERM = DefaultPermissionNames::COMMAND_KILL_SELF; + private const string OTHER_PERM = DefaultPermissionNames::COMMAND_KILL_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_kill_description(), - KnownTranslationFactory::pocketmine_command_kill_usage() + [new CommandOverload([ + new StringParameter("target", "target") + ], self::OVERLOAD_PERMS, self::execute(...))], + KnownTranslationFactory::pocketmine_command_kill_description() ); - $this->setPermissions([DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) >= 2){ - throw new InvalidCommandSyntaxException(); - } - - $player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER); + private static function execute(CommandSender $sender, ?string $target = null) : void{ + $player = self::fetchPermittedPlayerTarget($sender, $target, self::SELF_PERM, self::OTHER_PERM); if($player === null){ - return true; + return; } $player->attack(new EntityDamageEvent($player, EntityDamageEvent::CAUSE_SUICIDE, $player->getHealth())); @@ -59,7 +61,5 @@ class KillCommand extends VanillaCommand{ }else{ Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_kill_successful($player->getName())); } - - return true; } } diff --git a/src/command/defaults/ListCommand.php b/src/command/defaults/ListCommand.php index c797be7c5..f4cc9db95 100644 --- a/src/command/defaults/ListCommand.php +++ b/src/command/defaults/ListCommand.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; @@ -34,18 +36,18 @@ use function implode; use function sort; use const SORT_STRING; -class ListCommand extends VanillaCommand{ +class ListCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_LIST, self::execute(...))], KnownTranslationFactory::pocketmine_command_list_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_LIST); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ $playerNames = array_map(function(Player $player) : string{ return $player->getName(); }, array_filter($sender->getServer()->getOnlinePlayers(), function(Player $player) use ($sender) : bool{ @@ -55,7 +57,5 @@ class ListCommand extends VanillaCommand{ $sender->sendMessage(KnownTranslationFactory::commands_players_list((string) count($playerNames), (string) $sender->getServer()->getMaxPlayers())); $sender->sendMessage(implode(", ", $playerNames)); - - return true; } } diff --git a/src/command/defaults/MeCommand.php b/src/command/defaults/MeCommand.php index 7d3cff521..dc85f5692 100644 --- a/src/command/defaults/MeCommand.php +++ b/src/command/defaults/MeCommand.php @@ -23,34 +23,34 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RawParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use pocketmine\utils\TextFormat; -use function count; -use function implode; -class MeCommand extends VanillaCommand{ +class MeCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload( + [new RawParameter("message", "message")], + DefaultPermissionNames::COMMAND_ME, + self::execute(...) + )], KnownTranslationFactory::pocketmine_command_me_description(), - KnownTranslationFactory::commands_me_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_ME); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } - - $sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::RESET . implode(" ", $args))); - - return true; + private static function execute(CommandSender $sender, string $message) : void{ + $sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote( + $sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), + TextFormat::RESET . $message + )); } } diff --git a/src/command/defaults/OpCommand.php b/src/command/defaults/OpCommand.php index 43e38de00..f05945963 100644 --- a/src/command/defaults/OpCommand.php +++ b/src/command/defaults/OpCommand.php @@ -25,41 +25,38 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\StringParameter; use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use pocketmine\utils\TextFormat; -use function array_shift; -use function count; -class OpCommand extends VanillaCommand{ +class OpCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload( + [new StringParameter("playerName", "player name")], + DefaultPermissionNames::COMMAND_OP_GIVE, + self::execute(...) + )], KnownTranslationFactory::pocketmine_command_op_description(), - KnownTranslationFactory::commands_op_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_OP_GIVE); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ + private static function execute(CommandSender $sender, string $playerName) : void{ + if(!Player::isValidUserName($playerName)){ throw new InvalidCommandSyntaxException(); } - $name = array_shift($args); - if(!Player::isValidUserName($name)){ - throw new InvalidCommandSyntaxException(); - } - - $sender->getServer()->addOp($name); - if(($player = $sender->getServer()->getPlayerExact($name)) !== null){ + $sender->getServer()->addOp($playerName); + if(($player = $sender->getServer()->getPlayerExact($playerName)) !== null){ $player->sendMessage(KnownTranslationFactory::commands_op_message()->prefix(TextFormat::GRAY)); } - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_op_success($name)); - return true; + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_op_success($playerName)); } } diff --git a/src/command/defaults/PardonCommand.php b/src/command/defaults/PardonCommand.php index 6f17c27d8..776e1f18c 100644 --- a/src/command/defaults/PardonCommand.php +++ b/src/command/defaults/PardonCommand.php @@ -25,32 +25,29 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -use function count; -class PardonCommand extends VanillaCommand{ +class PardonCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload( + [new StringParameter("playerName", "player name")], + DefaultPermissionNames::COMMAND_UNBAN_PLAYER, + self::execute(...) + )], KnownTranslationFactory::pocketmine_command_unban_player_description(), - KnownTranslationFactory::commands_unban_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_UNBAN_PLAYER); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) !== 1){ - throw new InvalidCommandSyntaxException(); - } + private static function execute(CommandSender $sender, string $playerName) : void{ + $sender->getServer()->getNameBans()->remove($playerName); - $sender->getServer()->getNameBans()->remove($args[0]); - - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_unban_success($args[0])); - - return true; + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_unban_success($playerName)); } } diff --git a/src/command/defaults/PardonIpCommand.php b/src/command/defaults/PardonIpCommand.php index 7fc850195..c9f97a188 100644 --- a/src/command/defaults/PardonIpCommand.php +++ b/src/command/defaults/PardonIpCommand.php @@ -25,37 +25,34 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -use function count; use function inet_pton; -class PardonIpCommand extends VanillaCommand{ +class PardonIpCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_unban_ip_description(), - KnownTranslationFactory::commands_unbanip_usage() + [new CommandOverload( + [new StringParameter("ip", "IP address")], + DefaultPermissionNames::COMMAND_UNBAN_IP, + self::execute(...) + )], + KnownTranslationFactory::pocketmine_command_unban_ip_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_UNBAN_IP); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) !== 1){ - throw new InvalidCommandSyntaxException(); - } - - if(inet_pton($args[0]) !== false){ - $sender->getServer()->getIPBans()->remove($args[0]); - $sender->getServer()->getNetwork()->unblockAddress($args[0]); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_unbanip_success($args[0])); + private static function execute(CommandSender $sender, string $ip) : void{ + if(inet_pton($ip) !== false){ + $sender->getServer()->getIPBans()->remove($ip); + $sender->getServer()->getNetwork()->unblockAddress($ip); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_unbanip_success($ip)); }else{ $sender->sendMessage(KnownTranslationFactory::commands_unbanip_invalid()); } - - return true; } } diff --git a/src/command/defaults/ParticleCommand.php b/src/command/defaults/ParticleCommand.php index e0ec57686..eda4a7256 100644 --- a/src/command/defaults/ParticleCommand.php +++ b/src/command/defaults/ParticleCommand.php @@ -25,14 +25,21 @@ namespace pocketmine\command\defaults; use pocketmine\block\BlockTypeIds; use pocketmine\color\Color; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\FloatRangeParameter; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\RelativeFloat; +use pocketmine\command\overload\RelativeFloatParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\item\StringToItemParser; use pocketmine\item\VanillaItems; use pocketmine\lang\KnownTranslationFactory; use pocketmine\math\Vector3; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; +use pocketmine\utils\Limits; use pocketmine\utils\Random; use pocketmine\utils\TextFormat; use pocketmine\world\particle\AngryVillagerParticle; @@ -67,59 +74,71 @@ use pocketmine\world\particle\WaterParticle; use pocketmine\world\World; use function count; use function explode; -use function max; use function microtime; use function mt_rand; use function strtolower; -class ParticleCommand extends VanillaCommand{ +class ParticleCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_particle_description(), - KnownTranslationFactory::pocketmine_command_particle_usage() + [new CommandOverload( + [ + new StringParameter("particleName", "particle name"), + new RelativeFloatParameter("x", "x"), + new RelativeFloatParameter("y", "y"), + new RelativeFloatParameter("z", "z"), + new FloatRangeParameter("xd", "xd", -1000, 1000), + new FloatRangeParameter("yd", "yd", -1000, 1000), + new FloatRangeParameter("zd", "zd", -1000, 1000), + new IntRangeParameter("count", "count", 1, 100000), + new StringParameter("data", "data") + ], + DefaultPermissionNames::COMMAND_PARTICLE, + self::execute(...) + )], + KnownTranslationFactory::pocketmine_command_particle_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_PARTICLE); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 7){ - throw new InvalidCommandSyntaxException(); - } + private static function execute( + CommandSender $sender, + string $particleName, + RelativeFloat $x, + RelativeFloat $y, + RelativeFloat $z, + float $xd = 0.0, + float $yd = 0.0, + float $zd = 0.0, + int $count = 1, + string $data = "" + ) : void{ if($sender instanceof Player){ $senderPos = $sender->getPosition(); $world = $senderPos->getWorld(); - $pos = new Vector3( - $this->getRelativeDouble($senderPos->getX(), $sender, $args[1]), - $this->getRelativeDouble($senderPos->getY(), $sender, $args[2], World::Y_MIN, World::Y_MAX), - $this->getRelativeDouble($senderPos->getZ(), $sender, $args[3]) - ); + [$senderX, $senderY, $senderZ] = [$senderPos->getX(), $senderPos->getY(), $senderPos->getZ()]; }else{ $world = $sender->getServer()->getWorldManager()->getDefaultWorld(); - $pos = new Vector3((float) $args[1], (float) $args[2], (float) $args[3]); + [$senderX, $senderY, $senderZ] = [0, 0, 0]; } - $name = strtolower($args[0]); + $pos = new Vector3( + $x->resolve($senderX, Limits::INT32_MIN, Limits::INT32_MAX), + $y->resolve($senderY, World::Y_MIN, World::Y_MAX), + $z->resolve($senderZ, Limits::INT32_MIN, Limits::INT32_MAX) + ); - $xd = (float) $args[4]; - $yd = (float) $args[5]; - $zd = (float) $args[6]; - - $count = isset($args[7]) ? max(1, (int) $args[7]) : 1; - - $data = $args[8] ?? null; - - $particle = $this->getParticle($name, $data); + $particle = self::getParticle(strtolower($particleName), $data); if($particle === null){ - $sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($name)->prefix(TextFormat::RED)); - return true; + $sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($particleName)->prefix(TextFormat::RED)); + return; } - $sender->sendMessage(KnownTranslationFactory::commands_particle_success($name, (string) $count)); + $sender->sendMessage(KnownTranslationFactory::commands_particle_success($particleName, (string) $count)); $random = new Random((int) (microtime(true) * 1000) + mt_rand()); @@ -130,11 +149,9 @@ class ParticleCommand extends VanillaCommand{ $random->nextSignedFloat() * $zd ), $particle); } - - return true; } - private function getParticle(string $name, ?string $data = null) : ?Particle{ + private static function getParticle(string $name, ?string $data = null) : ?Particle{ switch($name){ case "explode": return new ExplodeParticle(); diff --git a/src/command/defaults/PluginsCommand.php b/src/command/defaults/PluginsCommand.php index 678ed9518..88d9b956b 100644 --- a/src/command/defaults/PluginsCommand.php +++ b/src/command/defaults/PluginsCommand.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\plugin\Plugin; @@ -34,25 +36,23 @@ use function implode; use function sort; use const SORT_STRING; -class PluginsCommand extends VanillaCommand{ +class PluginsCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_PLUGINS, self::execute(...))], KnownTranslationFactory::pocketmine_command_plugins_description(), - null ); - $this->setPermission(DefaultPermissionNames::COMMAND_PLUGINS); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ $list = array_map(function(Plugin $plugin) : string{ return ($plugin->isEnabled() ? TextFormat::GREEN : TextFormat::RED) . $plugin->getDescription()->getFullName(); }, $sender->getServer()->getPluginManager()->getPlugins()); sort($list, SORT_STRING); $sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::RESET . ", ", $list))); - return true; } } diff --git a/src/command/defaults/SaveCommand.php b/src/command/defaults/SaveCommand.php index fcc046b16..690ebf7df 100644 --- a/src/command/defaults/SaveCommand.php +++ b/src/command/defaults/SaveCommand.php @@ -25,23 +25,24 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use function microtime; use function round; -class SaveCommand extends VanillaCommand{ +class SaveCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_SAVE_PERFORM, self::execute(...))], KnownTranslationFactory::pocketmine_command_save_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_SAVE_PERFORM); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_save_start()); $start = microtime(true); @@ -54,7 +55,5 @@ class SaveCommand extends VanillaCommand{ } Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_save_success((string) round(microtime(true) - $start, 3))); - - return true; } } diff --git a/src/command/defaults/SaveOffCommand.php b/src/command/defaults/SaveOffCommand.php index fd4871a4f..de55aa316 100644 --- a/src/command/defaults/SaveOffCommand.php +++ b/src/command/defaults/SaveOffCommand.php @@ -25,25 +25,24 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -class SaveOffCommand extends VanillaCommand{ +class SaveOffCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_SAVE_DISABLE, self::execute(...))], KnownTranslationFactory::pocketmine_command_saveoff_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_SAVE_DISABLE); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ $sender->getServer()->getWorldManager()->setAutoSave(false); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_save_disabled()); - - return true; } } diff --git a/src/command/defaults/SaveOnCommand.php b/src/command/defaults/SaveOnCommand.php index 17ed8f49c..97b0cff01 100644 --- a/src/command/defaults/SaveOnCommand.php +++ b/src/command/defaults/SaveOnCommand.php @@ -25,25 +25,24 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -class SaveOnCommand extends VanillaCommand{ +class SaveOnCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_SAVE_ENABLE, self::execute(...))], KnownTranslationFactory::pocketmine_command_saveon_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_SAVE_ENABLE); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ $sender->getServer()->getWorldManager()->setAutoSave(true); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_save_enabled()); - - return true; } } diff --git a/src/command/defaults/SayCommand.php b/src/command/defaults/SayCommand.php index c421b29ca..4990e5f1a 100644 --- a/src/command/defaults/SayCommand.php +++ b/src/command/defaults/SayCommand.php @@ -23,37 +23,33 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RawParameter; use pocketmine\console\ConsoleCommandSender; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use pocketmine\utils\TextFormat; -use function count; -use function implode; -class SayCommand extends VanillaCommand{ +class SayCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([ + new RawParameter("message", "message") + ], DefaultPermissionNames::COMMAND_SAY, self::execute(...))], KnownTranslationFactory::pocketmine_command_say_description(), - KnownTranslationFactory::commands_say_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_SAY); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - throw new InvalidCommandSyntaxException(); - } - + private static function execute(CommandSender $sender, string $message) : void{ $sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_announcement( $sender instanceof Player ? $sender->getDisplayName() : ($sender instanceof ConsoleCommandSender ? "Server" : $sender->getName()), - implode(" ", $args) + $message )->prefix(TextFormat::LIGHT_PURPLE)); - return true; } } diff --git a/src/command/defaults/SeedCommand.php b/src/command/defaults/SeedCommand.php index 8cf71f2fb..26b276dd0 100644 --- a/src/command/defaults/SeedCommand.php +++ b/src/command/defaults/SeedCommand.php @@ -23,30 +23,30 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; -class SeedCommand extends VanillaCommand{ +class SeedCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_SEED, self::execute(...))], KnownTranslationFactory::pocketmine_command_seed_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_SEED); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ if($sender instanceof Player){ $seed = $sender->getPosition()->getWorld()->getSeed(); }else{ $seed = $sender->getServer()->getWorldManager()->getDefaultWorld()->getSeed(); } $sender->sendMessage(KnownTranslationFactory::commands_seed_success((string) $seed)); - - return true; } } diff --git a/src/command/defaults/SetWorldSpawnCommand.php b/src/command/defaults/SetWorldSpawnCommand.php index 4255ac465..efef7cb32 100644 --- a/src/command/defaults/SetWorldSpawnCommand.php +++ b/src/command/defaults/SetWorldSpawnCommand.php @@ -25,59 +25,65 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RelativeFloat; +use pocketmine\command\overload\RelativeFloatParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\math\Vector3; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; +use pocketmine\utils\Limits; use pocketmine\utils\TextFormat; use pocketmine\world\World; -use function count; -class SetWorldSpawnCommand extends VanillaCommand{ +class SetWorldSpawnCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_setworldspawn_description(), - KnownTranslationFactory::commands_setworldspawn_usage() + [ + new CommandOverload([], DefaultPermissionNames::COMMAND_SETWORLDSPAWN, self::setSpawnHere(...)), + new CommandOverload([ + new RelativeFloatParameter("x", "x"), + new RelativeFloatParameter("y", "y"), + new RelativeFloatParameter("z", "z") + ], DefaultPermissionNames::COMMAND_SETWORLDSPAWN, self::setSpawnCoordinates(...)) + ], + KnownTranslationFactory::pocketmine_command_setworldspawn_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_SETWORLDSPAWN); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - if($sender instanceof Player){ - $location = $sender->getPosition(); - $world = $location->getWorld(); - $pos = $location->asVector3()->floor(); - }else{ - $sender->sendMessage(TextFormat::RED . "You can only perform this command as a player"); - - return true; - } - }elseif(count($args) === 3){ - if($sender instanceof Player){ - $base = $sender->getPosition(); - $world = $base->getWorld(); - }else{ - $base = new Vector3(0.0, 0.0, 0.0); - $world = $sender->getServer()->getWorldManager()->getDefaultWorld(); - } - $pos = (new Vector3( - $this->getRelativeDouble($base->x, $sender, $args[0]), - $this->getRelativeDouble($base->y, $sender, $args[1], World::Y_MIN, World::Y_MAX), - $this->getRelativeDouble($base->z, $sender, $args[2]), - ))->floor(); - }else{ - throw new InvalidCommandSyntaxException(); + private static function setSpawnHere(CommandSender $sender) : void{ + if(!$sender instanceof Player){ + $sender->sendMessage(TextFormat::RED . "You can only perform this command as a player"); + return; } + $location = $sender->getPosition(); + $world = $location->getWorld(); + $pos = $location->asVector3()->floor(); $world->setSpawnLocation($pos); Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_setworldspawn_success((string) $pos->x, (string) $pos->y, (string) $pos->z)); + } - return true; + private static function setSpawnCoordinates(CommandSender $sender, RelativeFloat $x, RelativeFloat $y, RelativeFloat $z) : void{ + if($sender instanceof Player){ + $base = $sender->getPosition(); + $world = $base->getWorld(); + }else{ + $base = new Vector3(0.0, 0.0, 0.0); + $world = $sender->getServer()->getWorldManager()->getDefaultWorld(); + } + $pos = (new Vector3( + $x->resolve($base->x, Limits::INT32_MIN, Limits::INT32_MAX), + $y->resolve($base->y, World::Y_MIN, World::Y_MAX), + $z->resolve($base->z, Limits::INT32_MIN, Limits::INT32_MAX) + ))->floor(); + + $world->setSpawnLocation($pos); + + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_setworldspawn_success((string) $pos->x, (string) $pos->y, (string) $pos->z)); } } diff --git a/src/command/defaults/SpawnpointCommand.php b/src/command/defaults/SpawnpointCommand.php index c544083f4..81b26acda 100644 --- a/src/command/defaults/SpawnpointCommand.php +++ b/src/command/defaults/SpawnpointCommand.php @@ -25,56 +25,71 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RelativeFloat; +use pocketmine\command\overload\RelativeFloatParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; +use pocketmine\utils\Limits; use pocketmine\world\Position; use pocketmine\world\World; -use function count; use function round; -class SpawnpointCommand extends VanillaCommand{ +class SpawnpointCommand extends Command{ + + private const string SELF_PERM = DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF; + private const string OTHER_PERM = DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_spawnpoint_description(), - KnownTranslationFactory::commands_spawnpoint_usage() + [ + new CommandOverload([ + new StringParameter("target", "target") + ], self::OVERLOAD_PERMS, self::setSpawnHere(...)), + new CommandOverload([ + new StringParameter("target", "target"), + new RelativeFloatParameter("x", "x"), + new RelativeFloatParameter("y", "y"), + new RelativeFloatParameter("z", "z") + ], self::OVERLOAD_PERMS, self::setSpawnCoords(...)) + ], + KnownTranslationFactory::pocketmine_command_spawnpoint_description() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF, - DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - $target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF, DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER); + private static function setSpawnHere(CommandSender $sender, ?string $target = null) : void{ + $target = self::fetchPermittedPlayerTarget($sender, $target, self::SELF_PERM, self::OTHER_PERM); if($target === null){ - return true; + return; } - if(count($args) === 4){ - $world = $target->getWorld(); - $pos = $sender instanceof Player ? $sender->getPosition() : $world->getSpawnLocation(); - $x = $this->getRelativeDouble($pos->x, $sender, $args[1]); - $y = $this->getRelativeDouble($pos->y, $sender, $args[2], World::Y_MIN, World::Y_MAX); - $z = $this->getRelativeDouble($pos->z, $sender, $args[3]); - $target->setSpawn(new Position($x, $y, $z, $world)); + $cpos = $target->getPosition(); + $pos = Position::fromObject($cpos->floor(), $cpos->getWorld()); + $target->setSpawn($pos); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_spawnpoint_success($target->getName(), (string) round($x, 2), (string) round($y, 2), (string) round($z, 2))); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_spawnpoint_success($target->getName(), (string) round($pos->x, 2), (string) round($pos->y, 2), (string) round($pos->z, 2))); + } - return true; - }elseif(count($args) <= 1 && $sender instanceof Player){ - $cpos = $sender->getPosition(); - $pos = Position::fromObject($cpos->floor(), $cpos->getWorld()); - $target->setSpawn($pos); - - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_spawnpoint_success($target->getName(), (string) round($pos->x, 2), (string) round($pos->y, 2), (string) round($pos->z, 2))); - return true; + private static function setSpawnCoords(CommandSender $sender, string $target, RelativeFloat $x, RelativeFloat $y, RelativeFloat $z) : void{ + $target = self::fetchPermittedPlayerTarget($sender, $target, self::SELF_PERM, self::OTHER_PERM); + if($target === null){ + return; } - throw new InvalidCommandSyntaxException(); + $world = $target->getWorld(); + $pos = $sender instanceof Player ? $sender->getPosition() : $world->getSpawnLocation(); + $x = $x->resolve($pos->x, Limits::INT32_MIN, Limits::INT32_MAX); + $y = $y->resolve($pos->y, World::Y_MIN, World::Y_MAX); + $z = $z->resolve($pos->z, Limits::INT32_MIN, Limits::INT32_MAX); + $target->setSpawn(new Position($x, $y, $z, $world)); + + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_spawnpoint_success($target->getName(), (string) round($x, 2), (string) round($y, 2), (string) round($z, 2))); + } } diff --git a/src/command/defaults/StatusCommand.php b/src/command/defaults/StatusCommand.php index b8b8d499e..473e52ee3 100644 --- a/src/command/defaults/StatusCommand.php +++ b/src/command/defaults/StatusCommand.php @@ -23,7 +23,9 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\utils\Process; @@ -34,18 +36,18 @@ use function microtime; use function number_format; use function round; -class StatusCommand extends VanillaCommand{ +class StatusCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_STATUS, self::execute(...))], KnownTranslationFactory::pocketmine_command_status_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_STATUS); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ $mUsage = Process::getAdvancedMemoryUsage(); $server = $sender->getServer(); @@ -113,7 +115,5 @@ class StatusCommand extends VanillaCommand{ "Time $timeColor" . round($world->getTickRateTime(), 2) . "ms" ); } - - return true; } } diff --git a/src/command/defaults/StopCommand.php b/src/command/defaults/StopCommand.php index bf017bf91..b2fc135e0 100644 --- a/src/command/defaults/StopCommand.php +++ b/src/command/defaults/StopCommand.php @@ -25,25 +25,24 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -class StopCommand extends VanillaCommand{ +class StopCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([], DefaultPermissionNames::COMMAND_STOP, self::execute(...))], KnownTranslationFactory::pocketmine_command_stop_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_STOP); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ + private static function execute(CommandSender $sender) : void{ Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_stop_start()); $sender->getServer()->shutdown(); - - return true; } } diff --git a/src/command/defaults/TeleportCommand.php b/src/command/defaults/TeleportCommand.php index ba9b49dca..303d769d1 100644 --- a/src/command/defaults/TeleportCommand.php +++ b/src/command/defaults/TeleportCommand.php @@ -25,100 +25,138 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\FloatRangeParameter; +use pocketmine\command\overload\RelativeFloat; +use pocketmine\command\overload\RelativeFloatParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\entity\Location; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; -use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\Limits; use pocketmine\utils\TextFormat; use pocketmine\world\World; -use function array_shift; -use function count; use function round; -class TeleportCommand extends VanillaCommand{ +class TeleportCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_tp_description(), - KnownTranslationFactory::commands_tp_usage() + [ + new CommandOverload([ + new RelativeFloatParameter("xIn", "x"), + new RelativeFloatParameter("yIn", "y"), + new RelativeFloatParameter("zIn", "z"), + new FloatRangeParameter("yaw", "yaw", 0, 360), + new FloatRangeParameter("pitch", "pitch", -90, 90) + ], DefaultPermissionNames::COMMAND_TELEPORT_SELF, self::tpSelfCoords(...)), + new CommandOverload([ + new StringParameter("teleportedPlayerName", "player to teleport"), + new RelativeFloatParameter("xIn", "x"), + new RelativeFloatParameter("yIn", "y"), + new RelativeFloatParameter("zIn", "z"), + new FloatRangeParameter("yaw", "yaw", 0, 360), + new FloatRangeParameter("pitch", "pitch", -90, 90) + ], DefaultPermissionNames::COMMAND_TELEPORT_OTHER, self::tpOtherCoords(...)), + new CommandOverload([ + new StringParameter("destinationPlayerName", "destination player") + ], DefaultPermissionNames::COMMAND_TELEPORT_SELF, self::tpSelfToPlayer(...)), + new CommandOverload([ + new StringParameter("teleportedPlayerName", "player to teleport"), + new StringParameter("destinationPlayerName", "destination player") + ], DefaultPermissionNames::COMMAND_TELEPORT_OTHER, self::tpOtherToPlayer(...)) + ], + KnownTranslationFactory::pocketmine_command_tp_description() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_TELEPORT_SELF, - DefaultPermissionNames::COMMAND_TELEPORT_OTHER - ]); } - private function findPlayer(CommandSender $sender, string $playerName) : ?Player{ - $subject = $sender->getServer()->getPlayerByPrefix($playerName); - if($subject === null){ - $sender->sendMessage(TextFormat::RED . "Can't find player " . $playerName); - return null; - } - return $subject; + private static function tpCoords( + CommandSender $sender, + Player $subject, + RelativeFloat $xIn, + RelativeFloat $yIn, + RelativeFloat $zIn, + float $yaw, + float $pitch + ) : void{ + $base = $subject->getLocation(); + + $x = $xIn->resolve($base->x, Limits::INT32_MIN, Limits::INT32_MAX); + $y = $yIn->resolve($base->y, World::Y_MIN, World::Y_MAX); + $z = $zIn->resolve($base->z, Limits::INT32_MIN, Limits::INT32_MAX); + $targetLocation = new Location($x, $y, $z, $base->getWorld(), $yaw, $pitch); + + $subject->teleport($targetLocation); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_tp_success_coordinates( + $subject->getName(), + (string) round($targetLocation->x, 2), + (string) round($targetLocation->y, 2), + (string) round($targetLocation->z, 2) + )); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - switch(count($args)){ - case 1: // /tp targetPlayer - case 3: // /tp x y z - case 5: // /tp x y z yaw pitch - TODO: 5 args could be target x y z yaw :( - $subjectName = null; //self - break; - case 2: // /tp player1 player2 - case 4: // /tp player1 x y z - TODO: 4 args could be x y z yaw :( - case 6: // /tp player1 x y z yaw pitch - $subjectName = array_shift($args); - break; - default: - throw new InvalidCommandSyntaxException(); + private static function tpSelfCoords( + CommandSender $sender, + RelativeFloat $xIn, + RelativeFloat $yIn, + RelativeFloat $zIn, + float $yaw = 0.0, + float $pitch = 0.0 + ) : void{ + if(!$sender instanceof Player){ + throw new InvalidCommandSyntaxException("This syntax can only be used as a player"); } - $subject = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $subjectName, DefaultPermissionNames::COMMAND_TELEPORT_SELF, DefaultPermissionNames::COMMAND_TELEPORT_OTHER); + self::tpCoords($sender, $sender, $xIn, $yIn, $zIn, $yaw, $pitch); + } + + private static function tpOtherCoords( + CommandSender $sender, + string $teleportedPlayerName, + RelativeFloat $xIn, + RelativeFloat $yIn, + RelativeFloat $zIn, + float $yaw = 0.0, + float $pitch = 0.0 + ) : void{ + $subject = self::fetchPermittedPlayerTarget($sender, $teleportedPlayerName, DefaultPermissionNames::COMMAND_TELEPORT_SELF, DefaultPermissionNames::COMMAND_TELEPORT_OTHER); if($subject === null){ - return true; + return; } - switch(count($args)){ - case 1: - $targetPlayer = $this->findPlayer($sender, $args[0]); - if($targetPlayer === null){ - return true; - } + self::tpCoords($sender, $subject, $xIn, $yIn, $zIn, $yaw, $pitch); + } - $subject->teleport($targetPlayer->getLocation()); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_tp_success($subject->getName(), $targetPlayer->getName())); - - return true; - case 3: - case 5: - $base = $subject->getLocation(); - if(count($args) === 5){ - $yaw = (float) $args[3]; - $pitch = (float) $args[4]; - }else{ - $yaw = $base->yaw; - $pitch = $base->pitch; - } - - $x = $this->getRelativeDouble($base->x, $sender, $args[0]); - $y = $this->getRelativeDouble($base->y, $sender, $args[1], World::Y_MIN, World::Y_MAX); - $z = $this->getRelativeDouble($base->z, $sender, $args[2]); - $targetLocation = new Location($x, $y, $z, $base->getWorld(), $yaw, $pitch); - - $subject->teleport($targetLocation); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_tp_success_coordinates( - $subject->getName(), - (string) round($targetLocation->x, 2), - (string) round($targetLocation->y, 2), - (string) round($targetLocation->z, 2) - )); - return true; - default: - throw new AssumptionFailedError("This branch should be unreachable (for now)"); + private static function tpToPlayer(CommandSender $sender, Player $teleportedPlayer, string $destinationPlayerName) : void{ + $destination = $sender->getServer()->getPlayerByPrefix($destinationPlayerName); + if($destination === null){ + //TODO: this isn't really a syntax error, but we don't have strings for it currently + $sender->sendMessage(TextFormat::RED . "Cannot find destination player: $destinationPlayerName"); + return; } + + $teleportedPlayer->teleport($destination->getLocation()); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_tp_success($teleportedPlayer->getName(), $destination->getName())); + } + + private static function tpSelfToPlayer(CommandSender $sender, string $destinationPlayer) : void{ + if(!$sender instanceof Player){ + throw new InvalidCommandSyntaxException("This syntax can only be used as a player"); + } + + self::tpToPlayer($sender, $sender, $destinationPlayer); + } + + private static function tpOtherToPlayer(CommandSender $sender, string $teleportedPlayerName, string $destinationPlayerName) : void{ + $subject = self::fetchPermittedPlayerTarget($sender, $teleportedPlayerName, DefaultPermissionNames::COMMAND_TELEPORT_SELF, DefaultPermissionNames::COMMAND_TELEPORT_OTHER); + if($subject === null){ + return; + } + + self::tpToPlayer($sender, $subject, $destinationPlayerName); } } diff --git a/src/command/defaults/TellCommand.php b/src/command/defaults/TellCommand.php index 93b99181f..ec70d25b0 100644 --- a/src/command/defaults/TellCommand.php +++ b/src/command/defaults/TellCommand.php @@ -25,41 +25,37 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\RawParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; use pocketmine\utils\TextFormat; -use function array_shift; -use function count; -use function implode; -class TellCommand extends VanillaCommand{ +class TellCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [new CommandOverload([ + new StringParameter("recipientName", "recipient player"), + new RawParameter("message", "message") + ], DefaultPermissionNames::COMMAND_TELL, self::execute(...))], KnownTranslationFactory::pocketmine_command_tell_description(), - KnownTranslationFactory::commands_message_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_TELL); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 2){ - throw new InvalidCommandSyntaxException(); - } - - $player = $sender->getServer()->getPlayerByPrefix(array_shift($args)); + private static function execute(CommandSender $sender, string $recipientName, string $message) : void{ + $player = $sender->getServer()->getPlayerByPrefix($recipientName); if($player === $sender){ $sender->sendMessage(KnownTranslationFactory::commands_message_sameTarget()->prefix(TextFormat::RED)); - return true; + return; } if($player instanceof Player){ - $message = implode(" ", $args); $sender->sendMessage(KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message)->prefix(TextFormat::GRAY . TextFormat::ITALIC)); $name = $sender instanceof Player ? $sender->getDisplayName() : $sender->getName(); $player->sendMessage(KnownTranslationFactory::commands_message_display_incoming($name, $message)->prefix(TextFormat::GRAY . TextFormat::ITALIC)); @@ -67,7 +63,5 @@ class TellCommand extends VanillaCommand{ }else{ $sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()); } - - return true; } } diff --git a/src/command/defaults/TimeCommand.php b/src/command/defaults/TimeCommand.php index 3565450a3..7bc671f75 100644 --- a/src/command/defaults/TimeCommand.php +++ b/src/command/defaults/TimeCommand.php @@ -25,119 +25,87 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\MappedParameter; +use pocketmine\command\overload\ParameterParseException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; +use pocketmine\utils\Limits; use pocketmine\world\World; -use function count; -class TimeCommand extends VanillaCommand{ +class TimeCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_time_description(), - KnownTranslationFactory::pocketmine_command_time_usage() + [ + new CommandOverload(["start"], DefaultPermissionNames::COMMAND_TIME_START, self::startTime(...)), + new CommandOverload(["stop"], DefaultPermissionNames::COMMAND_TIME_STOP, self::stopTime(...)), + new CommandOverload(["query"], DefaultPermissionNames::COMMAND_TIME_QUERY, self::queryTime(...)), + new CommandOverload([ + "set", + new MappedParameter("time", "time name", static fn(string $v) : int => match($v){ + "day" => World::TIME_DAY, + "noon" => World::TIME_NOON, + "sunset" => World::TIME_SUNSET, + "night" => World::TIME_NIGHT, + "midnight" => World::TIME_MIDNIGHT, + "sunrise" => World::TIME_SUNRISE, + //numeric times are handled in a separate overload, for clarity's sake + default => throw new ParameterParseException("Invalid time name: $v") + }) + ], DefaultPermissionNames::COMMAND_TIME_SET, self::setTime(...)), + new CommandOverload([ + "set", + new IntRangeParameter("time", "timestamp", 0, Limits::INT32_MAX) + ], DefaultPermissionNames::COMMAND_TIME_SET, self::setTime(...)), + new CommandOverload([ + "add", + new IntRangeParameter("ticks", "ticks", 0, Limits::INT32_MAX), + ], DefaultPermissionNames::COMMAND_TIME_ADD, self::addTime(...)) + ], + KnownTranslationFactory::pocketmine_command_time_description() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_TIME_ADD, - DefaultPermissionNames::COMMAND_TIME_SET, - DefaultPermissionNames::COMMAND_TIME_START, - DefaultPermissionNames::COMMAND_TIME_STOP, - DefaultPermissionNames::COMMAND_TIME_QUERY - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 1){ - throw new InvalidCommandSyntaxException(); + private static function startTime(CommandSender $sender) : void{ + foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ + $world->startTime(); } - $testPermissionCtx = $commandLabel . " " . $args[0]; + //TODO: l10n + Command::broadcastCommandMessage($sender, "Restarted the time"); + } - if($args[0] === "start"){ - if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_START)){ - return true; - } - foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ - $world->startTime(); - } - Command::broadcastCommandMessage($sender, "Restarted the time"); - return true; - }elseif($args[0] === "stop"){ - if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_STOP)){ - return true; - } - foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ - $world->stopTime(); - } - Command::broadcastCommandMessage($sender, "Stopped the time"); - return true; - }elseif($args[0] === "query"){ - if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_QUERY)){ - return true; - } - if($sender instanceof Player){ - $world = $sender->getWorld(); - }else{ - $world = $sender->getServer()->getWorldManager()->getDefaultWorld(); - } - $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_time_query((string) $world->getTime()))); - return true; + private static function stopTime(CommandSender $sender) : void{ + foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ + $world->stopTime(); } + Command::broadcastCommandMessage($sender, "Stopped the time"); + } - if(count($args) < 2){ - throw new InvalidCommandSyntaxException(); - } - - if($args[0] === "set"){ - if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_SET)){ - return true; - } - - switch($args[1]){ - case "day": - $value = World::TIME_DAY; - break; - case "noon": - $value = World::TIME_NOON; - break; - case "sunset": - $value = World::TIME_SUNSET; - break; - case "night": - $value = World::TIME_NIGHT; - break; - case "midnight": - $value = World::TIME_MIDNIGHT; - break; - case "sunrise": - $value = World::TIME_SUNRISE; - break; - default: - $value = $this->getInteger($sender, $args[1], 0); - break; - } - - foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ - $world->setTime($value); - } - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_time_set((string) $value)); - }elseif($args[0] === "add"){ - if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_ADD)){ - return true; - } - - $value = $this->getInteger($sender, $args[1], 0); - foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ - $world->setTime($world->getTime() + $value); - } - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_time_added((string) $value)); + private static function queryTime(CommandSender $sender) : void{ + if($sender instanceof Player){ + $world = $sender->getWorld(); }else{ - throw new InvalidCommandSyntaxException(); + $world = $sender->getServer()->getWorldManager()->getDefaultWorld(); } + $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_time_query((string) $world->getTime()))); + } - return true; + private static function setTime(CommandSender $sender, int $time) : void{ + foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ + $world->setTime($time); + } + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_time_set((string) $time)); + } + + private static function addTime(CommandSender $sender, int $ticks) : void{ + foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){ + $world->setTime($world->getTime() + $ticks); + } + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_time_added((string) $ticks)); } } diff --git a/src/command/defaults/TimingsCommand.php b/src/command/defaults/TimingsCommand.php index 2498d6203..2753af2af 100644 --- a/src/command/defaults/TimingsCommand.php +++ b/src/command/defaults/TimingsCommand.php @@ -25,7 +25,7 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; @@ -38,7 +38,6 @@ use pocketmine\utils\InternetException; use pocketmine\utils\InternetRequestResult; use pocketmine\YmlServerProperties; use Symfony\Component\Filesystem\Path; -use function count; use function fclose; use function file_exists; use function fopen; @@ -50,7 +49,6 @@ use function is_int; use function is_string; use function json_decode; use function mkdir; -use function strtolower; use const CURLOPT_AUTOREFERER; use const CURLOPT_FOLLOWLOCATION; use const CURLOPT_HTTPHEADER; @@ -58,95 +56,79 @@ use const CURLOPT_POST; use const CURLOPT_POSTFIELDS; use const PHP_EOL; -class TimingsCommand extends VanillaCommand{ +class TimingsCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, + [ + new CommandOverload(["on"], DefaultPermissionNames::COMMAND_TIMINGS, self::enableTimings(...)), + new CommandOverload(["off"], DefaultPermissionNames::COMMAND_TIMINGS, self::disableTimings(...)), + new CommandOverload(["reset"], DefaultPermissionNames::COMMAND_TIMINGS, self::resetTimings(...)), + new CommandOverload(["paste"], DefaultPermissionNames::COMMAND_TIMINGS, self::requestTimingsUpload(...)), + new CommandOverload(["report"], DefaultPermissionNames::COMMAND_TIMINGS, self::requestTimingsFile(...)) + ], KnownTranslationFactory::pocketmine_command_timings_description(), - KnownTranslationFactory::pocketmine_command_timings_usage() ); - $this->setPermission(DefaultPermissionNames::COMMAND_TIMINGS); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) !== 1){ - throw new InvalidCommandSyntaxException(); + private static function enableTimings(CommandSender $sender) : void{ + if(TimingsHandler::isEnabled()){ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_timings_alreadyEnabled()); + return; } + TimingsHandler::setEnabled(); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_enable()); + } - $mode = strtolower($args[0]); - - if($mode === "on"){ - if(TimingsHandler::isEnabled()){ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_timings_alreadyEnabled()); - return true; - } - TimingsHandler::setEnabled(); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_enable()); - - return true; - }elseif($mode === "off"){ - TimingsHandler::setEnabled(false); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_disable()); - return true; - } + private static function disableTimings(CommandSender $sender) : void{ + TimingsHandler::setEnabled(false); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_disable()); + } + private static function resetTimings(CommandSender $sender) : void{ if(!TimingsHandler::isEnabled()){ $sender->sendMessage(KnownTranslationFactory::pocketmine_command_timings_timingsDisabled()); - - return true; + return; } - $paste = $mode === "paste"; + TimingsHandler::reload(); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset()); + } - if($mode === "reset"){ - TimingsHandler::reload(); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset()); - }elseif($mode === "merged" || $mode === "report" || $paste){ - $timingsPromise = TimingsHandler::requestPrintTimings(); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect()); - $timingsPromise->onCompletion( - fn(array $lines) => $paste ? $this->uploadReport($lines, $sender) : $this->createReportFile($lines, $sender), - fn() => throw new AssumptionFailedError("This promise is not expected to be rejected") - ); - }else{ - throw new InvalidCommandSyntaxException(); + private static function requestTimingsUpload(CommandSender $sender) : void{ + if(!TimingsHandler::isEnabled()){ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_timings_timingsDisabled()); + return; } - return true; + $timingsPromise = TimingsHandler::requestPrintTimings(); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect()); + $timingsPromise->onCompletion( + fn(array $lines) => self::uploadReport($lines, $sender), + fn() => throw new AssumptionFailedError("This promise is not expected to be rejected") + ); + } + + private static function requestTimingsFile(CommandSender $sender) : void{ + if(!TimingsHandler::isEnabled()){ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_timings_timingsDisabled()); + return; + } + $timingsPromise = TimingsHandler::requestPrintTimings(); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect()); + $timingsPromise->onCompletion( + fn(array $lines) => self::createReportFile($lines, $sender), + fn() => throw new AssumptionFailedError("This promise is not expected to be rejected") + ); } /** * @param string[] $lines * @phpstan-param list $lines */ - private function createReportFile(array $lines, CommandSender $sender) : void{ - $index = 0; - $timingFolder = Path::join($sender->getServer()->getDataPath(), "timings"); - - if(!file_exists($timingFolder)){ - mkdir($timingFolder, 0777); - } - $timings = Path::join($timingFolder, "timings.txt"); - while(file_exists($timings)){ - $timings = Path::join($timingFolder, "timings" . (++$index) . ".txt"); - } - - $fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b")); - foreach($lines as $line){ - fwrite($fileTimings, $line . PHP_EOL); - } - fclose($fileTimings); - - Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings)); - } - - /** - * @param string[] $lines - * @phpstan-param list $lines - */ - private function uploadReport(array $lines, CommandSender $sender) : void{ + private static function uploadReport(array $lines, CommandSender $sender) : void{ $data = [ "browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(), "data" => implode("\n", $lines), @@ -197,4 +179,29 @@ class TimingsCommand extends VanillaCommand{ } )); } + + /** + * @param string[] $lines + * @phpstan-param list $lines + */ + private static function createReportFile(array $lines, CommandSender $sender) : void{ + $index = 0; + $timingFolder = Path::join($sender->getServer()->getDataPath(), "timings"); + + if(!file_exists($timingFolder)){ + mkdir($timingFolder, 0777); + } + $timings = Path::join($timingFolder, "timings.txt"); + while(file_exists($timings)){ + $timings = Path::join($timingFolder, "timings" . (++$index) . ".txt"); + } + + $fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b")); + foreach($lines as $line){ + fwrite($fileTimings, $line . PHP_EOL); + } + fclose($fileTimings); + + Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings)); + } } diff --git a/src/command/defaults/TitleCommand.php b/src/command/defaults/TitleCommand.php index 0297b55be..f33a7d65b 100644 --- a/src/command/defaults/TitleCommand.php +++ b/src/command/defaults/TitleCommand.php @@ -23,80 +23,82 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\RawParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; -use function array_slice; -use function count; -use function implode; +use pocketmine\player\Player; +use pocketmine\utils\Limits; -class TitleCommand extends VanillaCommand{ +class TitleCommand extends Command{ + + private const string SELF_PERM = DefaultPermissionNames::COMMAND_TITLE_SELF; + private const string OTHER_PERM = DefaultPermissionNames::COMMAND_TITLE_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ + $playerParameter = new StringParameter("playerName", "player name"); + $textParameter = new RawParameter("text", "text"); parent::__construct( $namespace, $name, + [ + new CommandOverload([$playerParameter, "clear"], self::OVERLOAD_PERMS, self::clearTitles(...)), + new CommandOverload([$playerParameter, "reset"], self::OVERLOAD_PERMS, self::resetTitles(...)), + new CommandOverload([$playerParameter, "title", $textParameter], self::OVERLOAD_PERMS, self::sendTitle(...)), + new CommandOverload([$playerParameter, "subtitle", $textParameter], self::OVERLOAD_PERMS, self::sendSubTitle(...)), + new CommandOverload([$playerParameter, "actionbar", $textParameter], self::OVERLOAD_PERMS, self::sendActionBar(...)), + new CommandOverload([ + $playerParameter, + "times", + new IntRangeParameter("fadeInTicks", "fade-in ticks", 0, Limits::INT32_MAX), + new IntRangeParameter("stayTicks", "stay ticks", 0, Limits::INT32_MAX), + new IntRangeParameter("fadeOutTicks", "fade-out ticks", 0, Limits::INT32_MAX) + ], self::OVERLOAD_PERMS, self::setTitleDuration(...)) + ], KnownTranslationFactory::pocketmine_command_title_description(), - KnownTranslationFactory::commands_title_usage() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_TITLE_SELF, - DefaultPermissionNames::COMMAND_TITLE_OTHER - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 2){ - throw new InvalidCommandSyntaxException(); - } - - $player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_TITLE_SELF, DefaultPermissionNames::COMMAND_TITLE_OTHER); + /** + * @phpstan-param \Closure(Player) : void $action + */ + private static function doTitleAction(CommandSender $sender, string $playerName, \Closure $action) : void{ + $player = self::fetchPermittedPlayerTarget($sender, $playerName, self::SELF_PERM, self::OTHER_PERM); if($player === null){ - return true; - } - - switch($args[1]){ - case "clear": - $player->removeTitles(); - break; - case "reset": - $player->resetTitles(); - break; - case "title": - if(count($args) < 3){ - throw new InvalidCommandSyntaxException(); - } - - $player->sendTitle(implode(" ", array_slice($args, 2))); - break; - case "subtitle": - if(count($args) < 3){ - throw new InvalidCommandSyntaxException(); - } - - $player->sendSubTitle(implode(" ", array_slice($args, 2))); - break; - case "actionbar": - if(count($args) < 3){ - throw new InvalidCommandSyntaxException(); - } - - $player->sendActionBarMessage(implode(" ", array_slice($args, 2))); - break; - case "times": - if(count($args) < 5){ - throw new InvalidCommandSyntaxException(); - } - - $player->setTitleDuration($this->getInteger($sender, $args[2]), $this->getInteger($sender, $args[3]), $this->getInteger($sender, $args[4])); - break; - default: - throw new InvalidCommandSyntaxException(); + return; } + $action($player); $sender->sendMessage(KnownTranslationFactory::commands_title_success()); + } - return true; + private static function clearTitles(CommandSender $sender, string $playerName) : void{ + self::doTitleAction($sender, $playerName, static fn(Player $p) => $p->removeTitles()); + } + + private static function resetTitles(CommandSender $sender, string $playerName) : void{ + self::doTitleAction($sender, $playerName, static fn(Player $p) => $p->resetTitles()); + } + + private static function sendTitle(CommandSender $sender, string $playerName, string $text) : void{ + self::doTitleAction($sender, $playerName, static fn(Player $p) => $p->sendTitle($text)); + } + + private static function sendSubTitle(CommandSender $sender, string $playerName, string $text) : void{ + self::doTitleAction($sender, $playerName, static fn(Player $p) => $p->sendSubTitle($text)); + } + + private static function sendActionBar(CommandSender $sender, string $playerName, string $text) : void{ + self::doTitleAction($sender, $playerName, static fn(Player $p) => $p->sendActionBarMessage($text)); + } + + private static function setTitleDuration(CommandSender $sender, string $playerName, int $fadeInTicks, int $stayTicks, int $fadeOutTicks) : void{ + self::doTitleAction($sender, $playerName, static fn(Player $p) => $p->setTitleDuration($fadeInTicks, $stayTicks, $fadeOutTicks)); } } diff --git a/src/command/defaults/TransferServerCommand.php b/src/command/defaults/TransferServerCommand.php index 7a36fd2a3..6a8c737fa 100644 --- a/src/command/defaults/TransferServerCommand.php +++ b/src/command/defaults/TransferServerCommand.php @@ -23,36 +23,37 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; use pocketmine\player\Player; -use function count; +use pocketmine\utils\TextFormat; -class TransferServerCommand extends VanillaCommand{ +class TransferServerCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_transferserver_description(), - KnownTranslationFactory::pocketmine_command_transferserver_usage() + [new CommandOverload([ + new StringParameter("serverAddress", "server address"), + new IntRangeParameter("serverPort", "server port", 1, 65535) + ], DefaultPermissionNames::COMMAND_TRANSFERSERVER, self::execute(...))], + KnownTranslationFactory::pocketmine_command_transferserver_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_TRANSFERSERVER); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 1){ - throw new InvalidCommandSyntaxException(); - }elseif(!($sender instanceof Player)){ - $sender->sendMessage("This command must be executed as a player"); + private static function execute(CommandSender $sender, string $serverAddress, int $serverPort = 19132) : void{ + if(!($sender instanceof Player)){ + $sender->sendMessage(TextFormat::RED . "This command must be executed as a player"); - return false; + return; } - $sender->transfer($args[0], (int) ($args[1] ?? 19132)); - - return true; + $sender->transfer($serverAddress, $serverPort); } } diff --git a/src/command/defaults/VanillaCommand.php b/src/command/defaults/VanillaCommand.php deleted file mode 100644 index 9efead379..000000000 --- a/src/command/defaults/VanillaCommand.php +++ /dev/null @@ -1,121 +0,0 @@ -getServer()->getPlayerByPrefix($target); - }elseif($sender instanceof Player){ - $player = $sender; - }else{ - throw new InvalidCommandSyntaxException(); - } - - if($player === null){ - $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($testPermissionContext, $sender, $selfPermission)) || - ($player !== $sender && $this->testPermission($testPermissionContext, $sender, $otherPermission)) - ){ - return $player; - } - return null; - } - - protected function getInteger(CommandSender $sender, string $value, int $min = self::MIN_COORD, int $max = self::MAX_COORD) : int{ - $i = (int) $value; - - if($i < $min){ - $i = $min; - }elseif($i > $max){ - $i = $max; - } - - return $i; - } - - protected function getRelativeDouble(float $original, CommandSender $sender, string $input, float $min = self::MIN_COORD, float $max = self::MAX_COORD) : float{ - if($input[0] === "~"){ - $value = $this->getDouble($sender, substr($input, 1)); - - return $original + $value; - } - - return $this->getDouble($sender, $input, $min, $max); - } - - protected function getDouble(CommandSender $sender, string $value, float $min = self::MIN_COORD, float $max = self::MAX_COORD) : float{ - $i = (double) $value; - - if($i < $min){ - $i = $min; - }elseif($i > $max){ - $i = $max; - } - - return $i; - } - - protected function getBoundedInt(CommandSender $sender, string $input, int $min, int $max) : ?int{ - if(!is_numeric($input)){ - throw new InvalidCommandSyntaxException(); - } - - $v = (int) $input; - if($v > $max){ - $sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooBig($input, (string) $max)->prefix(TextFormat::RED)); - return null; - } - if($v < $min){ - $sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooSmall($input, (string) $min)->prefix(TextFormat::RED)); - return null; - } - - return $v; - } -} diff --git a/src/command/defaults/VersionCommand.php b/src/command/defaults/VersionCommand.php index 1c306f0ea..02af5795b 100644 --- a/src/command/defaults/VersionCommand.php +++ b/src/command/defaults/VersionCommand.php @@ -23,7 +23,10 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\StringParameter; use pocketmine\lang\KnownTranslationFactory; use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\permission\DefaultPermissionNames; @@ -38,74 +41,77 @@ use function stripos; use function strtolower; use const PHP_VERSION; -class VersionCommand extends VanillaCommand{ +class VersionCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_version_description(), - KnownTranslationFactory::pocketmine_command_version_usage() + [ + //technically this could be one overload, but two lets us do two separate handlers + new CommandOverload([], DefaultPermissionNames::COMMAND_VERSION, self::showServerVersion(...)), + + new CommandOverload([ + new StringParameter("pluginName", "plugin name") + ], DefaultPermissionNames::COMMAND_VERSION, self::showPluginVersion(...)), + ], + KnownTranslationFactory::pocketmine_command_version_description() ); - $this->setPermission(DefaultPermissionNames::COMMAND_VERSION); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareName( - TextFormat::GREEN . VersionInfo::NAME . TextFormat::RESET - )); - $versionColor = VersionInfo::IS_DEVELOPMENT_BUILD ? TextFormat::YELLOW : TextFormat::GREEN; - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareVersion( - $versionColor . VersionInfo::VERSION()->getFullVersion() . TextFormat::RESET, - TextFormat::GREEN . VersionInfo::GIT_HASH() . TextFormat::RESET - )); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_minecraftVersion( - TextFormat::GREEN . ProtocolInfo::MINECRAFT_VERSION_NETWORK . TextFormat::RESET, - TextFormat::GREEN . ProtocolInfo::CURRENT_PROTOCOL . TextFormat::RESET - )); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(TextFormat::GREEN . PHP_VERSION . TextFormat::RESET)); + private static function showServerVersion(CommandSender $sender) : void{ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareName( + TextFormat::GREEN . VersionInfo::NAME . TextFormat::RESET + )); + $versionColor = VersionInfo::IS_DEVELOPMENT_BUILD ? TextFormat::YELLOW : TextFormat::GREEN; + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareVersion( + $versionColor . VersionInfo::VERSION()->getFullVersion() . TextFormat::RESET, + TextFormat::GREEN . VersionInfo::GIT_HASH() . TextFormat::RESET + )); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_minecraftVersion( + TextFormat::GREEN . ProtocolInfo::MINECRAFT_VERSION_NETWORK . TextFormat::RESET, + TextFormat::GREEN . ProtocolInfo::CURRENT_PROTOCOL . TextFormat::RESET + )); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(TextFormat::GREEN . PHP_VERSION . TextFormat::RESET)); - $jitMode = Utils::getOpcacheJitMode(); - if($jitMode !== null){ - if($jitMode !== 0){ - $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitEnabled(sprintf("CRTO: %d", $jitMode)); - }else{ - $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitDisabled(); - } + $jitMode = Utils::getOpcacheJitMode(); + if($jitMode !== null){ + if($jitMode !== 0){ + $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitEnabled(sprintf("CRTO: %d", $jitMode)); }else{ - $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitNotSupported(); + $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitDisabled(); } - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpJitStatus($jitStatus->format(TextFormat::GREEN, TextFormat::RESET))); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_operatingSystem(TextFormat::GREEN . Utils::getOS() . TextFormat::RESET)); }else{ - $pluginName = implode(" ", $args); - $exactPlugin = $sender->getServer()->getPluginManager()->getPlugin($pluginName); + $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitNotSupported(); + } + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpJitStatus($jitStatus->format(TextFormat::GREEN, TextFormat::RESET))); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_operatingSystem(TextFormat::GREEN . Utils::getOS() . TextFormat::RESET)); + } - if($exactPlugin instanceof Plugin){ - $this->describeToSender($exactPlugin, $sender); + private static function showPluginVersion(CommandSender $sender, string $pluginName) : void{ + $exactPlugin = $sender->getServer()->getPluginManager()->getPlugin($pluginName); - return true; - } + if($exactPlugin instanceof Plugin){ + self::describeToSender($exactPlugin, $sender); - $found = false; - $pluginName = strtolower($pluginName); - foreach($sender->getServer()->getPluginManager()->getPlugins() as $plugin){ - if(stripos($plugin->getName(), $pluginName) !== false){ - $this->describeToSender($plugin, $sender); - $found = true; - } - } + return; + } - if(!$found){ - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_noSuchPlugin()); + $found = false; + $pluginName = strtolower($pluginName); + foreach($sender->getServer()->getPluginManager()->getPlugins() as $plugin){ + if(stripos($plugin->getName(), $pluginName) !== false){ + self::describeToSender($plugin, $sender); + $found = true; } } - return true; + if(!$found){ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_noSuchPlugin()); + } } - private function describeToSender(Plugin $plugin, CommandSender $sender) : void{ + private static function describeToSender(Plugin $plugin, CommandSender $sender) : void{ $desc = $plugin->getDescription(); $sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::RESET . " version " . TextFormat::DARK_GREEN . $desc->getVersion()); diff --git a/src/command/defaults/WhitelistCommand.php b/src/command/defaults/WhitelistCommand.php index d18a2f1a5..a333d3a7c 100644 --- a/src/command/defaults/WhitelistCommand.php +++ b/src/command/defaults/WhitelistCommand.php @@ -25,6 +25,8 @@ namespace pocketmine\command\defaults; use pocketmine\command\Command; use pocketmine\command\CommandSender; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\StringParameter; use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; @@ -34,113 +36,84 @@ use pocketmine\ServerProperties; use function count; use function implode; use function sort; -use function strtolower; use const SORT_STRING; -class WhitelistCommand extends VanillaCommand{ +class WhitelistCommand extends Command{ public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_whitelist_description(), - KnownTranslationFactory::commands_whitelist_usage() + [ + new CommandOverload(["reload"], DefaultPermissionNames::COMMAND_WHITELIST_RELOAD, self::reloadWhitelist(...)), + new CommandOverload(["enable"], DefaultPermissionNames::COMMAND_WHITELIST_ENABLE, self::enableWhitelist(...)), + new CommandOverload(["disable"], DefaultPermissionNames::COMMAND_WHITELIST_DISABLE, self::disableWhitelist(...)), + new CommandOverload(["list"], DefaultPermissionNames::COMMAND_WHITELIST_LIST, self::listWhitelist(...)), + new CommandOverload([ + "add", + new StringParameter("playerName", "player name") + ], DefaultPermissionNames::COMMAND_WHITELIST_ADD, self::addWhitelistedPlayer(...)), + new CommandOverload([ + "remove", + new StringParameter("playerName", "player name"), + ], DefaultPermissionNames::COMMAND_WHITELIST_REMOVE, self::removeWhitelistedPlayer(...)) + ], + KnownTranslationFactory::pocketmine_command_whitelist_description() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_WHITELIST_RELOAD, - DefaultPermissionNames::COMMAND_WHITELIST_ENABLE, - DefaultPermissionNames::COMMAND_WHITELIST_DISABLE, - DefaultPermissionNames::COMMAND_WHITELIST_LIST, - DefaultPermissionNames::COMMAND_WHITELIST_ADD, - DefaultPermissionNames::COMMAND_WHITELIST_REMOVE - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) === 0){ + private static function reloadWhitelist(CommandSender $sender) : void{ + $server = $sender->getServer(); + $server->getWhitelisted()->reload(); + if($server->hasWhitelist()){ + self::kickNonWhitelistedPlayers($server); + } + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_reloaded()); + } + + private static function enableWhitelist(CommandSender $sender) : void{ + $server = $sender->getServer(); + $server->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, true); + self::kickNonWhitelistedPlayers($server); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_enabled()); + } + + private static function disableWhitelist(CommandSender $sender) : void{ + $sender->getServer()->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, false); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_disabled()); + } + + private static function listWhitelist(CommandSender $sender) : void{ + $entries = $sender->getServer()->getWhitelisted()->getAll(true); + sort($entries, SORT_STRING); + $result = implode(", ", $entries); + $count = (string) count($entries); + + $sender->sendMessage(KnownTranslationFactory::commands_whitelist_list($count, $count)); + $sender->sendMessage($result); + } + + private static function addWhitelistedPlayer(CommandSender $sender, string $playerName) : void{ + if(!Player::isValidUserName($playerName)){ throw new InvalidCommandSyntaxException(); } - $testPermissionCtx = $commandLabel . " " . $args[0]; - if(count($args) === 1){ - switch(strtolower($args[0])){ - case "reload": - if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_RELOAD)){ - $server = $sender->getServer(); - $server->getWhitelisted()->reload(); - if($server->hasWhitelist()){ - $this->kickNonWhitelistedPlayers($server); - } - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_reloaded()); - } - - return true; - case "on": - if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_ENABLE)){ - $server = $sender->getServer(); - $server->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, true); - $this->kickNonWhitelistedPlayers($server); - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_enabled()); - } - - return true; - case "off": - 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($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_LIST)){ - $entries = $sender->getServer()->getWhitelisted()->getAll(true); - sort($entries, SORT_STRING); - $result = implode(", ", $entries); - $count = (string) count($entries); - - $sender->sendMessage(KnownTranslationFactory::commands_whitelist_list($count, $count)); - $sender->sendMessage($result); - } - - return true; - - case "add": - $sender->sendMessage(KnownTranslationFactory::commands_generic_usage(KnownTranslationFactory::commands_whitelist_add_usage())); - return true; - - case "remove": - $sender->sendMessage(KnownTranslationFactory::commands_generic_usage(KnownTranslationFactory::commands_whitelist_remove_usage())); - return true; - } - }elseif(count($args) === 2){ - if(!Player::isValidUserName($args[1])){ - throw new InvalidCommandSyntaxException(); - } - switch(strtolower($args[0])){ - case "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($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_REMOVE)){ - $server = $sender->getServer(); - $server->removeWhitelist($args[1]); - if(!$server->isWhitelisted($args[1])){ - $server->getPlayerExact($args[1])?->kick(KnownTranslationFactory::pocketmine_disconnect_kick(KnownTranslationFactory::pocketmine_disconnect_whitelisted())); - } - Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_remove_success($args[1])); - } - - return true; - } - } - - throw new InvalidCommandSyntaxException(); + $sender->getServer()->addWhitelist($playerName); + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_add_success($playerName)); } - private function kickNonWhitelistedPlayers(Server $server) : void{ + private static function removeWhitelistedPlayer(CommandSender $sender, string $playerName) : void{ + if(!Player::isValidUserName($playerName)){ + throw new InvalidCommandSyntaxException(); + } + $server = $sender->getServer(); + $server->removeWhitelist($playerName); + if(!$server->isWhitelisted($playerName)){ + $server->getPlayerExact($playerName)?->kick(KnownTranslationFactory::pocketmine_disconnect_kick(KnownTranslationFactory::pocketmine_disconnect_whitelisted())); + } + Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_remove_success($playerName)); + } + + private static function kickNonWhitelistedPlayers(Server $server) : void{ $message = KnownTranslationFactory::pocketmine_disconnect_kick(KnownTranslationFactory::pocketmine_disconnect_whitelisted()); foreach($server->getOnlinePlayers() as $player){ if(!$server->isWhitelisted($player->getName())){ diff --git a/src/command/defaults/XpCommand.php b/src/command/defaults/XpCommand.php index 33ac7fab3..72dfca6bc 100644 --- a/src/command/defaults/XpCommand.php +++ b/src/command/defaults/XpCommand.php @@ -23,8 +23,11 @@ declare(strict_types=1); namespace pocketmine\command\defaults; +use pocketmine\command\Command; use pocketmine\command\CommandSender; -use pocketmine\command\utils\InvalidCommandSyntaxException; +use pocketmine\command\overload\CommandOverload; +use pocketmine\command\overload\IntRangeParameter; +use pocketmine\command\overload\StringParameter; use pocketmine\entity\Attribute; use pocketmine\lang\KnownTranslationFactory; use pocketmine\permission\DefaultPermissionNames; @@ -32,59 +35,66 @@ use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\TextFormat; use function abs; -use function count; -use function str_ends_with; -use function substr; +use function max; +use function min; -class XpCommand extends VanillaCommand{ +class XpCommand extends Command{ + + private const SELF_PERM = DefaultPermissionNames::COMMAND_XP_SELF; + private const OTHER_PERM = DefaultPermissionNames::COMMAND_XP_OTHER; + + private const OVERLOAD_PERMS = [self::SELF_PERM, self::OTHER_PERM]; public function __construct(string $namespace, string $name){ parent::__construct( $namespace, $name, - KnownTranslationFactory::pocketmine_command_xp_description(), - KnownTranslationFactory::pocketmine_command_xp_usage() + [ + new CommandOverload([ + new IntRangeParameter("xp", "xp", 0, Limits::INT32_MAX), + new StringParameter("playerName", "player name"), + ], self::OVERLOAD_PERMS, self::addXp(...)), + new CommandOverload([ + new IntRangeParameter("xpLevels", "xp levels", Limits::INT32_MIN, Limits::INT32_MAX, "L"), + new StringParameter("playerName", "player name"), + ], self::OVERLOAD_PERMS, self::addXpLevels(...)) + ], + KnownTranslationFactory::pocketmine_command_xp_description() ); - $this->setPermissions([ - DefaultPermissionNames::COMMAND_XP_SELF, - DefaultPermissionNames::COMMAND_XP_OTHER - ]); } - public function execute(CommandSender $sender, string $commandLabel, array $args){ - if(count($args) < 1){ - throw new InvalidCommandSyntaxException(); + private static function addXp(CommandSender $sender, int $xp, ?string $playerName = null) : void{ + $player = self::fetchPermittedPlayerTarget($sender, $playerName, self::SELF_PERM, self::OTHER_PERM); + if($player === null){ + return; } - $player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER); + if($xp < 0){ + $sender->sendMessage(KnownTranslationFactory::commands_xp_failure_widthdrawXp()->prefix(TextFormat::RED)); + }else{ + $player->getXpManager()->addXp($xp, false); + $sender->sendMessage(KnownTranslationFactory::commands_xp_success((string) $xp, $player->getName())); + } + } + + private static function addXpLevels(CommandSender $sender, int $xpLevels, ?string $playerName = null) : void{ + $player = self::fetchPermittedPlayerTarget($sender, $playerName, self::SELF_PERM, self::OTHER_PERM); if($player === null){ - return true; + return; } $xpManager = $player->getXpManager(); - if(str_ends_with($args[0], "L")){ - $xpLevelAttr = $player->getAttributeMap()->get(Attribute::EXPERIENCE_LEVEL) ?? throw new AssumptionFailedError(); - $maxXpLevel = (int) $xpLevelAttr->getMaxValue(); - $currentXpLevel = $xpManager->getXpLevel(); - $xpLevels = $this->getInteger($sender, substr($args[0], 0, -1), -$currentXpLevel, $maxXpLevel - $currentXpLevel); - if($xpLevels >= 0){ - $xpManager->addXpLevels($xpLevels, false); - $sender->sendMessage(KnownTranslationFactory::commands_xp_success_levels((string) $xpLevels, $player->getName())); - }else{ - $xpLevels = abs($xpLevels); - $xpManager->subtractXpLevels($xpLevels); - $sender->sendMessage(KnownTranslationFactory::commands_xp_success_negative_levels((string) $xpLevels, $player->getName())); - } + $xpLevelAttr = $player->getAttributeMap()->get(Attribute::EXPERIENCE_LEVEL) ?? throw new AssumptionFailedError(); + $maxXpLevel = (int) $xpLevelAttr->getMaxValue(); + $currentXpLevel = $xpManager->getXpLevel(); + $xpLevels = max(-$currentXpLevel, min($maxXpLevel - $currentXpLevel, $xpLevels)); + if($xpLevels >= 0){ + $xpManager->addXpLevels($xpLevels, false); + $sender->sendMessage(KnownTranslationFactory::commands_xp_success_levels((string) $xpLevels, $player->getName())); }else{ - $xp = $this->getInteger($sender, $args[0], max: Limits::INT32_MAX); - if($xp < 0){ - $sender->sendMessage(KnownTranslationFactory::commands_xp_failure_widthdrawXp()->prefix(TextFormat::RED)); - }else{ - $xpManager->addXp($xp, false); - $sender->sendMessage(KnownTranslationFactory::commands_xp_success((string) $xp, $player->getName())); - } + $xpLevels = abs($xpLevels); + $xpManager->subtractXpLevels($xpLevels); + $sender->sendMessage(KnownTranslationFactory::commands_xp_success_negative_levels((string) $xpLevels, $player->getName())); } - - return true; } } diff --git a/src/command/overload/BoolParameter.php b/src/command/overload/BoolParameter.php new file mode 100644 index 000000000..d52ea8988 --- /dev/null +++ b/src/command/overload/BoolParameter.php @@ -0,0 +1,48 @@ + + */ +final class BoolParameter extends Parameter{ + + public function __construct(string $codeName, Translatable|string $printableName){ + parent::__construct($codeName, $printableName, new NamedType(BuiltInType::BOOL)); + } + + public function parse(string $buffer, int &$offset) : bool{ + $token = CommandStringHelper::parseQuoteAwareSingle($buffer, $offset); + return match($token){ + "on", "true", "1" => true, + "off", "false", "0" => false, + default => throw new ParameterParseException("Invalid value for bool parameter: $token") + }; + } +} diff --git a/src/command/overload/CommandOverload.php b/src/command/overload/CommandOverload.php new file mode 100644 index 000000000..6060d2194 --- /dev/null +++ b/src/command/overload/CommandOverload.php @@ -0,0 +1,226 @@ + + */ + private array $permissions; + + /** + * @param Parameter[]|string[] $parameters + * @param string|string[] $permission + * @phpstan-param list|string> $parameters + * @phpstan-param string|list $permission + * @phpstan-param anyClosure $handler + */ + public function __construct( + private array $parameters, + string|array $permission, + private \Closure $handler, + private bool $acceptsAliasUsed = false + ){ + $permissions = is_string($permission) ? [$permission] : $permission; + if(count($permissions) === 0){ + throw new \InvalidArgumentException("At least one permission must be provided"); + } + $permissionManager = PermissionManager::getInstance(); + foreach($permissions as $perm){ + if($permissionManager->getPermission($perm) === null){ + throw new \InvalidArgumentException("Cannot use non-existing permission \"$perm\""); + } + } + $this->permissions = $permissions; + + //TODO: auto infer parameter infos if they aren't provided? + //TODO: allow the type of CommandSender to be constrained - this can be useful for player-only commands etc + $alwaysPresentArgs = self::alwaysPresentArgs($this->acceptsAliasUsed); + + $passableParameters = array_filter($this->parameters, is_object(...)); + $expectedPrototype = new Prototype( + new ReturnInfo(new NamedType(BuiltInType::VOID), byReference: false), + ...$alwaysPresentArgs, + ...array_map(fn(Parameter $p) => new ParameterInfo( + $p->getCodeName(), + $p->getCodeType(), + byReference: false, + isOptional: false, + isVariadic: false, + ), $passableParameters) + ); + $actualPrototype = Prototype::fromClosure($this->handler); + Utils::validateCallableSignature($expectedPrototype, $actualPrototype); + + $this->requiredInputCount = $actualPrototype->getRequiredParameterCount() - count($alwaysPresentArgs); + } + + /** + * @return ParameterInfo[] + */ + private static function alwaysPresentArgs(bool $acceptsAliasUsed) : array{ + $result = [new ParameterInfo("sender", new NamedType(CommandSender::class), byReference: false, isOptional: false, isVariadic: false)]; + if($acceptsAliasUsed){ + $result[] = new ParameterInfo("aliasUsed", new NamedType(BuiltInType::STRING), byReference: false, isOptional: false, isVariadic: false); + } + return $result; + } + + /** + * @return string[] + * @phpstan-return list + */ + public function getPermissions() : array{ return $this->permissions; } + + public function senderHasAnyPermissions(CommandSender $sender) : bool{ + foreach($this->permissions as $permission){ + if($sender->hasPermission($permission)){ + return true; + } + } + + return false; + } + + public function getUsage() : Translatable{ + $templates = []; + $args = []; + $pos = 0; + foreach($this->parameters as $parameter){ + if(is_string($parameter)){ + //literal token + $templates[] = $parameter; + continue; + } + //TODO: printable type info would be nice + if($pos < $this->requiredInputCount){ + $template = "<{%$pos}>"; + }else{ + $template = "[{%$pos}]"; + } + $suffix = $parameter->getSuffix(); + $template .= $suffix; + $templates[] = $template; + + $args[] = $parameter->getPrintableName(); + $pos++; + } + + return new Translatable(implode(" ", $templates), $args); + } + + private static function skipWhitespace(string $commandLine, int &$offset) : int{ + if(preg_match('/\G\s+/', $commandLine, $matches, offset: $offset) > 0){ + $offset += strlen($matches[0]); + return strlen($matches[0]); + } + return 0; + } + + /** + * @throws InvalidCommandSyntaxException + */ + public function invoke(CommandSender $sender, string $aliasUsed, string $commandLine) : void{ + $offset = 0; + $args = []; + //skip preceding whitespace + + self::skipWhitespace($commandLine, $offset); + if($offset < strlen($commandLine)){ + foreach($this->parameters as $parameter){ + if(is_string($parameter)){ + if(strpos($commandLine, $parameter, $offset) === $offset){ + $offset += strlen($parameter); + }else{ + throw new ParameterParseException("Literal \"$parameter\" expected"); + } + }else{ + try{ + $args[] = $parameter->parse($commandLine, $offset); + }catch(ParameterParseException $e){ + throw new ParameterParseException( + "Failed parsing argument \$" . $parameter->getCodeName() . ": " . $e->getMessage(), + previous: $e + ); + } + } + if(self::skipWhitespace($commandLine, $offset) === 0){ + if($offset === strlen($commandLine)){ + //no more tokens, rest of the parameters must be optional + break; + }else{ + if(is_string($parameter)){ + throw new AssumptionFailedError(); + } + var_dump(substr($commandLine, $offset)); + throw new ParameterParseException("Parameter " . get_class($parameter) . " for \$" . $parameter->getCodeName() . " didn't stop on a whitespace character"); + } + } + } + } + if($offset !== strlen($commandLine)){ + throw new InvalidCommandSyntaxException("Too many arguments provided for overload"); + } + if(count($args) < $this->requiredInputCount){ + throw new InvalidCommandSyntaxException("Not enough arguments provided for overload"); + } + //Reflection magic here :) + //TODO: maybe we don't want to invoke this directly, but hand the args back to the caller? + //this would allow resolving by more than just overload order + if($this->acceptsAliasUsed){ + // @phpstan-ignore-next-line + ($this->handler)($sender, $aliasUsed, ...$args); + }else{ + // @phpstan-ignore-next-line + ($this->handler)($sender, ...$args); + } + } +} diff --git a/src/command/overload/FloatRangeParameter.php b/src/command/overload/FloatRangeParameter.php new file mode 100644 index 000000000..90ab816ba --- /dev/null +++ b/src/command/overload/FloatRangeParameter.php @@ -0,0 +1,61 @@ + + */ +final class FloatRangeParameter extends Parameter{ + + public function __construct( + string $codeName, + Translatable|string $printableName, + private float $min, + private float $max + ){ + parent::__construct($codeName, $printableName, new NamedType(BuiltInType::FLOAT)); + } + + public function parse(string $buffer, int &$offset) : float{ + if(preg_match('/\G(-?\d+\.?\d*)/', $buffer, $matches, offset: $offset) > 0){ + $offset += strlen($matches[0]); + $value = (float) $matches[0]; + if($value < $this->min || $value > $this->max){ + //TODO: we should probably use localised messages for this, but they probably won't be seen by the user + //anyway since we'll try all the overloads before giving up + throw new ParameterParseException("Value must be in the range $this->min ... $this->max"); + } + + return $value; + } + + throw new ParameterParseException("Expected a float in the range $this->min ... $this->max"); + } +} diff --git a/src/command/overload/IntRangeParameter.php b/src/command/overload/IntRangeParameter.php new file mode 100644 index 000000000..20b032541 --- /dev/null +++ b/src/command/overload/IntRangeParameter.php @@ -0,0 +1,67 @@ + + */ +final class IntRangeParameter extends Parameter{ + + public function __construct( + string $codeName, + Translatable|string $printableName, + private int $min, + private int $max, + private string $suffix = "" + ){ + parent::__construct($codeName, $printableName, new NamedType(BuiltInType::INT)); + } + + public function parse(string $buffer, int &$offset) : int{ + if(preg_match('/\G-?\d+' . preg_quote($this->suffix, '/') . '/', $buffer, $matches, offset: $offset) > 0){ + $offset += strlen($matches[0]); + $int = (int) $matches[0]; + if($int < $this->min || $int > $this->max){ + //TODO: we should probably use localised messages for this, but they probably won't be seen by the user + //anyway since we'll try all the overloads before giving up + throw new ParameterParseException("Value must be in the range $this->min ... $this->max"); + } + + return $int; + } + + throw new ParameterParseException("Expected an integer in the range $this->min ... $this->max"); + } + + public function getSuffix() : string{ + return $this->suffix; + } +} diff --git a/src/command/overload/MappedParameter.php b/src/command/overload/MappedParameter.php new file mode 100644 index 000000000..1f2bed05a --- /dev/null +++ b/src/command/overload/MappedParameter.php @@ -0,0 +1,75 @@ + + */ +final class MappedParameter extends Parameter{ + + /** + * @phpstan-param \Closure(string): TValue $mapper + */ + public function __construct( + string $codeName, + Translatable|string $printableName, + private \Closure $mapper + ){ + $givenPrototype = Prototype::fromClosure($this->mapper); + $type = $givenPrototype->getReturnInfo()->type; + if($type === null){ + throw new \InvalidArgumentException("Mapper callback must have a return type set"); + } + $expectedPrototype = new Prototype( + new ReturnInfo($type, byReference: false), + new ParameterInfo("value", new NamedType(BuiltInType::STRING), byReference: false, isOptional: false, isVariadic: false) + ); + Utils::validateCallableSignature($expectedPrototype, $givenPrototype); + + parent::__construct( + $codeName, + $printableName, + $type + ); + } + + public function parse(string $buffer, int &$offset) : mixed{ + $lookupKey = CommandStringHelper::parseQuoteAwareSingle($buffer, $offset) ?? throw new ParameterParseException("Unable to parse an argument from the buffer"); + + return ($this->mapper)($lookupKey); + } +} diff --git a/src/command/overload/Parameter.php b/src/command/overload/Parameter.php new file mode 100644 index 000000000..43a4df229 --- /dev/null +++ b/src/command/overload/Parameter.php @@ -0,0 +1,70 @@ +codeName; + } + + public function getPrintableName() : Translatable|string{ + return $this->printableName; + } + + public function getCodeType() : BaseType{ + return $this->codeType; + } + + /** + * Returns whether this command will consume all remaining inputs. + */ + public function consumesAllRemainingInputs() : bool{ + return false; + } + + /** + * The given string will be stripped of whitespace at the start + * + * @phpstan-return TValue + * @throws ParameterParseException + */ + abstract public function parse(string $buffer, int &$offset) : mixed; + + public function getSuffix() : string{ + return ""; + } +} diff --git a/src/command/overload/ParameterParseException.php b/src/command/overload/ParameterParseException.php new file mode 100644 index 000000000..9b4e5d580 --- /dev/null +++ b/src/command/overload/ParameterParseException.php @@ -0,0 +1,30 @@ + + */ +final class RawParameter extends Parameter{ + + public function __construct(string $codeName, Translatable|string $printableName){ + parent::__construct( + $codeName, + $printableName, + new NamedType(BuiltInType::STRING) + ); + } + + public function consumesAllRemainingInputs() : bool{ + return true; + } + + public function parse(string $buffer, int &$offset) : string{ + $value = substr($buffer, $offset); + $offset += strlen($value); + return $value; + } +} diff --git a/src/command/overload/RelativeFloat.php b/src/command/overload/RelativeFloat.php new file mode 100644 index 000000000..12135a84f --- /dev/null +++ b/src/command/overload/RelativeFloat.php @@ -0,0 +1,44 @@ +value; } + + public function isRelative() : bool{ return $this->relative; } + + public function resolve(float $base, float $min, float $max) : float{ + //TODO: this should probably bail on out of bounds values + return min($max, max($min, $this->relative ? $base + $this->value : $this->value)); + } +} diff --git a/src/command/overload/RelativeFloatParameter.php b/src/command/overload/RelativeFloatParameter.php new file mode 100644 index 000000000..fb56b5706 --- /dev/null +++ b/src/command/overload/RelativeFloatParameter.php @@ -0,0 +1,54 @@ + + */ +final class RelativeFloatParameter extends Parameter{ + + public function __construct(string $codeName, Translatable|string $printableName){ + parent::__construct($codeName, $printableName, new NamedType(RelativeFloat::class)); + } + + public function parse(string $buffer, int &$offset) : RelativeFloat{ + if(preg_match('/\G(~)?(-?\d+\.?\d*)?/', $buffer, $matches, offset: $offset) > 0){ + $relativeRaw = $matches[1] ?? ""; + $valueRaw = $matches[2] ?? ""; + if($valueRaw !== "" || $relativeRaw !== ""){ + $offset += strlen($matches[0]); + $relative = $relativeRaw === "~"; + $value = (float) $valueRaw; + return new RelativeFloat($value, $relative); + } + } + + throw new ParameterParseException("Expected a float, possibly preceded by a ~ symbol"); + } +} diff --git a/src/command/overload/StringParameter.php b/src/command/overload/StringParameter.php new file mode 100644 index 000000000..54ce68a72 --- /dev/null +++ b/src/command/overload/StringParameter.php @@ -0,0 +1,50 @@ + + */ +final class StringParameter extends Parameter{ + + public function __construct( + string $codeName, + Translatable|string $printableName, + ){ + parent::__construct( + $codeName, + $printableName, + new NamedType(BuiltInType::STRING) + ); + } + + public function parse(string $buffer, int &$offset) : string{ + return CommandStringHelper::parseQuoteAwareSingle($buffer, $offset) ?? throw new ParameterParseException(); + } +} diff --git a/src/command/utils/CommandStringHelper.php b/src/command/utils/CommandStringHelper.php index 76d70a9bb..8c7194d2c 100644 --- a/src/command/utils/CommandStringHelper.php +++ b/src/command/utils/CommandStringHelper.php @@ -25,8 +25,10 @@ namespace pocketmine\command\utils; use pocketmine\utils\AssumptionFailedError; use function preg_last_error_msg; +use function preg_match; use function preg_match_all; use function preg_replace; +use function strlen; final class CommandStringHelper{ @@ -60,4 +62,30 @@ final class CommandStringHelper{ return $args; } + + /** + * Splits by the same logic as {@link self::parseQuoteAware()}, but doesn't strip quotes from the parts or remove + * escapes. Useful if you need to join the parts back into a new command string. + * + * @return string[] + */ + public static function splitQuoteAware(string $commandLine) : array{ + preg_match_all('/"((?:\\\\.|[^\\\\"])*)"|(\S+)/u', $commandLine, $matches); + return $matches[0]; + } + + public static function parseQuoteAwareSingle(string $commandLine, int &$offset = 0) : ?string{ + //quoted or bare string, like the old CommandStringHelper + if(preg_match('/\G(?:"((?:\\\\.|[^\\\\"])*)"|(\S+))/u', $commandLine, $matches, offset: $offset) > 0){ + $offset += strlen($matches[0]); + for($i = 1; $i <= 2; ++$i){ + if($matches[$i] !== ""){ + $match = $matches[$i]; + return preg_replace('/\\\\([\\\\"])/u', '$1', $match) ?? throw new AssumptionFailedError(preg_last_error_msg()); + } + } + } + + return null; + } } diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index da54ee25d..447a1798d 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -1099,7 +1099,8 @@ class NetworkSession{ $globalAliasMap = $this->server->getCommandMap()->getAliasMap(); $userAliasMap = $this->player->getCommandAliasMap(); foreach($this->server->getCommandMap()->getUniqueCommands() as $command){ - if(!$command->testPermissionSilent($this->player)){ + if(count($command->getUsages($this->player, "")) === 0){ + //no permitted overloads continue; } diff --git a/src/utils/Utils.php b/src/utils/Utils.php index cc2505573..4076df946 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -559,9 +559,9 @@ final class Utils{ * @phpstan-param anyClosure $signature * @phpstan-param anyClosure $subject */ - public static function validateCallableSignature(Prototype|\Closure $signature, \Closure $subject) : void{ + public static function validateCallableSignature(Prototype|\Closure $signature, Prototype|\Closure $subject) : void{ $signaturePrototype = $signature instanceof Prototype ? $signature : Prototype::fromClosure($signature); - $subjectPrototype = Prototype::fromClosure($subject); + $subjectPrototype = $subject instanceof Prototype ? $subject : Prototype::fromClosure($subject); if(!$signaturePrototype->isSatisfiedBy($subjectPrototype)){ throw new \TypeError("Declaration of callable `$subjectPrototype` must be compatible with `$signaturePrototype`"); } diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 20d89ad3b..4baa054a5 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -781,7 +781,7 @@ parameters: path: ../../../src/network/mcpe/NetworkSession.php - - message: '#^Parameter \#1 \$target of method pocketmine\\command\\Command\:\:testPermissionSilent\(\) expects pocketmine\\command\\CommandSender, pocketmine\\player\\Player\|null given\.$#' + message: '#^Parameter \#1 \$sender of method pocketmine\\command\\Command\:\:getUsages\(\) expects pocketmine\\command\\CommandSender, pocketmine\\player\\Player\|null given\.$#' identifier: argument.type count: 1 path: ../../../src/network/mcpe/NetworkSession.php diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 65064ab6c..d5a791718 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -156,12 +156,6 @@ parameters: count: 1 path: ../../../src/block/VanillaBlocks.php - - - message: '#^Strict comparison using \=\=\= between \*NEVER\* and 5 will always evaluate to false\.$#' - identifier: identical.alwaysFalse - count: 1 - path: ../../../src/command/defaults/TeleportCommand.php - - message: '#^Method pocketmine\\crafting\\ShapedRecipe\:\:getIngredientMap\(\) should return list\\> but returns array\, non\-empty\-array\, pocketmine\\crafting\\RecipeIngredient\|null\>\>\.$#' identifier: return.type