mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-10-15 19:46:20 +00:00
Rework command alias handling, step 1 (#6685)
This PR started out as an effort to decouple Command and CommandMap, but it's turned into a bit more than that. A summary of changes: ## UX - Added `cmdalias create`, `cmdalias delete` and `cmdalias list` commands - `/help` now shows prefixed names such as `pocketmine:help` - Prefixed command name (e.g. `pocketmine:help`) are now visible to Minecraft clients - Permission denied messages are now able to show more useful context when e.g. checking subcommand permissions - Multiple commands claiming an alias make the alias unusable (an error will be shown when used telling the user to pick from the namespaced names), instead of whichever plugin loaded last getting lucky ## API - Added `CommandAliasMap`, which handles mapping of aliases to namespaced command IDs - Added `CommandSender->getCommandAliasMap()` for user-specific aliases - Added `CommandMap->getAliasMap()` for global fallback aliases - `Command` no longer tracks its own registered aliases (now the job of `CommandMap`), breaking circular dependency - Aliases must now be provided to `CommandMap->register()` - Aliases can now be individually registered and unregistered without re-registering/unregistering the whole command using `CommandAliasMap` APIs - Aliases are no longer namespaced, only the main command name (e.g. `pocketmine:?` is now gone while `pocketmine:help` still exists) - `Command` now requires a `$namespace` parameter, which replaces the old `$fallbackPrefix` parameter of `register()`. It should be set to the name of the plugin. Relevant issues - #6508 - #3371 - this PR doesn't implement storage, but allows configuration of per-user aliases during server runtime
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
"pocketmine/callback-validator": "dev-rewrite",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.7.0",
|
||||
"pocketmine/locale-data": "~2.26.0",
|
||||
"pocketmine/locale-data": "~2.27.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "dev-major-next as 1.0.0",
|
||||
"pocketmine/nbt": "~1.2.0",
|
||||
|
14
composer.lock
generated
14
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "65b59e25fafa99c7a989d28f89a91c44",
|
||||
"content-hash": "c4bc1cdab4e8ce569b94bc49ce51d480",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@@ -472,16 +472,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.26.0",
|
||||
"version": "2.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "f791369ae082fc5cdf0f2b0bd683e611ff7f90f6"
|
||||
"reference": "83d0d7e5bc53522ca1f10a4720ca1839f5d4d9df"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/f791369ae082fc5cdf0f2b0bd683e611ff7f90f6",
|
||||
"reference": "f791369ae082fc5cdf0f2b0bd683e611ff7f90f6",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/83d0d7e5bc53522ca1f10a4720ca1839f5d4d9df",
|
||||
"reference": "83d0d7e5bc53522ca1f10a4720ca1839f5d4d9df",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@@ -489,9 +489,9 @@
|
||||
"description": "Language resources used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Language/issues",
|
||||
"source": "https://github.com/pmmp/Language/tree/2.26.0"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.27.0"
|
||||
},
|
||||
"time": "2025-10-07T17:26:32+00:00"
|
||||
"time": "2025-10-10T22:27:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
|
@@ -680,11 +680,8 @@ class Server{
|
||||
* @phpstan-return (Command&PluginOwned)|null
|
||||
*/
|
||||
public function getPluginCommand(string $name){
|
||||
if(($command = $this->commandMap->getCommand($name)) instanceof PluginOwned){
|
||||
return $command;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
$command = $this->commandMap->getCommand($name);
|
||||
return $command instanceof PluginOwned ? $command : null;
|
||||
}
|
||||
|
||||
public function getNameBans() : BanList{
|
||||
|
@@ -38,19 +38,19 @@ final class ClosureCommand extends Command{
|
||||
* @phpstan-param Execute $execute
|
||||
*/
|
||||
public function __construct(
|
||||
string $namespace,
|
||||
string $name,
|
||||
\Closure $execute,
|
||||
array $permissions,
|
||||
Translatable|string $description = "",
|
||||
Translatable|string|null $usageMessage = null,
|
||||
array $aliases = []
|
||||
){
|
||||
Utils::validateCallableSignature(
|
||||
fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1,
|
||||
$execute,
|
||||
);
|
||||
$this->execute = $execute;
|
||||
parent::__construct($name, $description, $usageMessage, $aliases);
|
||||
parent::__construct($namespace, $name, $description, $usageMessage);
|
||||
$this->setPermissions($permissions);
|
||||
}
|
||||
|
||||
|
@@ -33,51 +33,36 @@ use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\BroadcastLoggerForwarder;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function array_values;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function str_replace;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
abstract class Command{
|
||||
|
||||
private string $name;
|
||||
|
||||
private string $nextLabel;
|
||||
private string $label;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
private array $aliases = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var list<string>
|
||||
*/
|
||||
private array $activeAliases = [];
|
||||
|
||||
private ?CommandMap $commandMap = null;
|
||||
|
||||
protected Translatable|string $description = "";
|
||||
|
||||
protected Translatable|string $usageMessage;
|
||||
private readonly string $namespace;
|
||||
private readonly string $name;
|
||||
|
||||
/** @var string[] */
|
||||
private array $permission = [];
|
||||
private Translatable|string|null $permissionMessage = null;
|
||||
|
||||
/**
|
||||
* @param string[] $aliases
|
||||
* @phpstan-param list<string> $aliases
|
||||
*/
|
||||
public function __construct(string $name, Translatable|string $description = "", Translatable|string|null $usageMessage = null, array $aliases = []){
|
||||
$this->name = $name;
|
||||
$this->setLabel($name);
|
||||
$this->setDescription($description);
|
||||
$this->usageMessage = $usageMessage ?? ("/" . $name);
|
||||
$this->setAliases($aliases);
|
||||
public function __construct(
|
||||
string $namespace,
|
||||
string $name,
|
||||
private Translatable|string $description = "",
|
||||
private Translatable|string|null $usageMessage = null
|
||||
){
|
||||
if($namespace === ""){
|
||||
throw new \InvalidArgumentException("Command namespace cannot be empty (set it to, for example, your plugin's name)");
|
||||
}
|
||||
if($name === ""){
|
||||
throw new \InvalidArgumentException("Command name cannot be empty");
|
||||
}
|
||||
$this->namespace = strtolower(trim($namespace));
|
||||
//TODO: case handling inconsistency preserved from old code
|
||||
$this->name = trim($name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,10 +74,25 @@ abstract class Command{
|
||||
*/
|
||||
abstract public function execute(CommandSender $sender, string $commandLabel, array $args);
|
||||
|
||||
public function getName() : string{
|
||||
final public function getNamespace() : string{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local identifier of the command (without namespace or leading slash).
|
||||
* This cannot be changed after creation.
|
||||
*/
|
||||
final public function getName() : string{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the globally unique ID for the command. This typically looks like namespace:name
|
||||
*/
|
||||
final public function getId() : string{
|
||||
return "$this->namespace:$this->name";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
@@ -117,12 +117,17 @@ abstract class Command{
|
||||
$this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX));
|
||||
}
|
||||
|
||||
public function testPermission(CommandSender $target, ?string $permission = null) : bool{
|
||||
/**
|
||||
* @param string $context usually the command name, but may include extra args if useful (e.g. for subcommands)
|
||||
* @param CommandSender $target the target to check the permission for
|
||||
* @param string|null $permission the permission to check, if null, will check if the target has any of the command's permissions
|
||||
*/
|
||||
public function testPermission(string $context, CommandSender $target, ?string $permission = null) : bool{
|
||||
if($this->testPermissionSilent($target, $permission)){
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = $this->permissionMessage ?? KnownTranslationFactory::pocketmine_command_error_permission($this->name);
|
||||
$message = $this->permissionMessage ?? KnownTranslationFactory::pocketmine_command_error_permission($context);
|
||||
if($message instanceof Translatable){
|
||||
$target->sendMessage($message->prefix(TextFormat::RED));
|
||||
}elseif($message !== ""){
|
||||
@@ -143,62 +148,6 @@ abstract class Command{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getLabel() : string{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $name) : bool{
|
||||
$this->nextLabel = $name;
|
||||
if(!$this->isRegistered()){
|
||||
$this->label = $name;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the command into a Command map
|
||||
*/
|
||||
public function register(CommandMap $commandMap) : bool{
|
||||
if($this->allowChangesFrom($commandMap)){
|
||||
$this->commandMap = $commandMap;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function unregister(CommandMap $commandMap) : bool{
|
||||
if($this->allowChangesFrom($commandMap)){
|
||||
$this->commandMap = null;
|
||||
$this->activeAliases = $this->aliases;
|
||||
$this->label = $this->nextLabel;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function allowChangesFrom(CommandMap $commandMap) : bool{
|
||||
return $this->commandMap === null || $this->commandMap === $commandMap;
|
||||
}
|
||||
|
||||
public function isRegistered() : bool{
|
||||
return $this->commandMap !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @phpstan-return list<string>
|
||||
*/
|
||||
public function getAliases() : array{
|
||||
return $this->activeAliases;
|
||||
}
|
||||
|
||||
public function getPermissionMessage() : Translatable|string|null{
|
||||
return $this->permissionMessage;
|
||||
}
|
||||
@@ -207,22 +156,10 @@ abstract class Command{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getUsage() : Translatable|string{
|
||||
public function getUsage() : Translatable|string|null{
|
||||
return $this->usageMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $aliases
|
||||
* @phpstan-param list<string> $aliases
|
||||
*/
|
||||
public function setAliases(array $aliases) : void{
|
||||
$aliases = array_values($aliases); //because plugins can and will pass crap
|
||||
$this->aliases = $aliases;
|
||||
if(!$this->isRegistered()){
|
||||
$this->activeAliases = $aliases;
|
||||
}
|
||||
}
|
||||
|
||||
public function setDescription(Translatable|string $description) : void{
|
||||
$this->description = $description;
|
||||
}
|
||||
@@ -231,7 +168,7 @@ abstract class Command{
|
||||
$this->permissionMessage = $permissionMessage;
|
||||
}
|
||||
|
||||
public function setUsage(Translatable|string $usage) : void{
|
||||
public function setUsage(Translatable|string|null $usage) : void{
|
||||
$this->usageMessage = $usage;
|
||||
}
|
||||
|
||||
@@ -252,8 +189,4 @@ abstract class Command{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString() : string{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
171
src/command/CommandAliasMap.php
Normal file
171
src/command/CommandAliasMap.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\command;
|
||||
|
||||
use function array_filter;
|
||||
use function array_key_first;
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function is_array;
|
||||
|
||||
final class CommandAliasMap{
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @phpstan-var array<string, string|list<string>>
|
||||
*/
|
||||
private array $aliasToCommandMap = [];
|
||||
|
||||
/**
|
||||
* @var string[][]
|
||||
* @phpstan-var array<string, array<string, true>>
|
||||
*/
|
||||
private array $commandToAliasesMap = [];
|
||||
|
||||
private function mapSingleAlias(string $commandId, string $alias) : bool{
|
||||
$existing = $this->aliasToCommandMap[$alias] ?? null;
|
||||
if($existing !== null){
|
||||
if(!is_array($existing)){
|
||||
//old command can't use this alias anymore, since it's conflicted
|
||||
unset($this->commandToAliasesMap[$existing][$alias]);
|
||||
$existing = [$existing];
|
||||
}
|
||||
$existing[] = $commandId;
|
||||
$this->aliasToCommandMap[$alias] = $existing;
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->commandToAliasesMap[$commandId][$alias] = true;
|
||||
$this->aliasToCommandMap[$alias] = $commandId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function bindAlias(string $commandId, string $newAlias, bool $override) : void{
|
||||
if($override){
|
||||
//explicit alias registration overrides everything else, including conflicts
|
||||
$this->unbindAlias($newAlias);
|
||||
}
|
||||
|
||||
$this->mapSingleAlias($commandId, $newAlias);
|
||||
}
|
||||
|
||||
public function unbindAlias(string $alias) : bool{
|
||||
$commandIds = $this->aliasToCommandMap[$alias] ?? null;
|
||||
if($commandIds === null){
|
||||
return false;
|
||||
}
|
||||
unset($this->aliasToCommandMap[$alias]);
|
||||
if(!is_array($commandIds)){
|
||||
//this should only be set if the alias wasn't conflicted
|
||||
unset($this->commandToAliasesMap[$commandIds][$alias]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function unbindAliasesForCommand(string $commandId) : void{
|
||||
foreach($this->getAliases($commandId) as $alias){
|
||||
$aliasMap = $this->aliasToCommandMap[$alias] ?? null;
|
||||
|
||||
if($aliasMap === $commandId){
|
||||
unset($this->aliasToCommandMap[$alias]);
|
||||
}elseif(is_array($aliasMap)){
|
||||
//this may leave 1 command remaining - we purposely don't deconflict it here because successive command
|
||||
//invocations should be predictable. Leave the conflict and let the user override it if they want.
|
||||
$replacement = array_filter($aliasMap, fn(string $cid) => $cid !== $commandId);
|
||||
if(count($replacement) === 0){
|
||||
unset($this->aliasToCommandMap[$alias]);
|
||||
}else{
|
||||
$this->aliasToCommandMap[$alias] = array_values($replacement);
|
||||
}
|
||||
}else{
|
||||
throw new \LogicException("Alias map state corrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all (non-conflicted) aliases for the command.
|
||||
* @return string[]
|
||||
* @phpstan-return list<string>
|
||||
*/
|
||||
public function getAliases(string $commandId) : array{
|
||||
return array_keys($this->commandToAliasesMap[$commandId] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the command bound to the given alias.
|
||||
* If there are conflicting commands bound, an array of all the bound command IDs will be returned.
|
||||
* If the alias is not bound, null will be returned.
|
||||
*
|
||||
* @return string|string[]|null
|
||||
* @phpstan-return string|list<string>|null
|
||||
*/
|
||||
public function resolveAlias(string $alias) : string|array|null{
|
||||
return $this->aliasToCommandMap[$alias] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the names a command could be invoked by.
|
||||
* Aliases from this map override the fallback map.
|
||||
* The command ID is also included, since that can always be used to invoke a command.
|
||||
*
|
||||
* @return string[]
|
||||
* @phpstan-return non-empty-list<string>
|
||||
*/
|
||||
public function getMergedAliases(string $commandId, CommandAliasMap $fallbackMap) : array{
|
||||
$localAliases = $this->getAliases($commandId);
|
||||
|
||||
foreach($fallbackMap->getAliases($commandId) as $globalAlias){
|
||||
$userMappedCommandId = $this->resolveAlias($globalAlias);
|
||||
if($userMappedCommandId === null){
|
||||
//only include if this map doesn't have this alias at all
|
||||
$localAliases[] = $globalAlias;
|
||||
}
|
||||
}
|
||||
|
||||
$localAliases[] = $commandId;
|
||||
|
||||
return $localAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a preferred alias for the given command ID. Used for displaying errors and help tips.
|
||||
* For example, the user might not have /help bound, but might have a different alias for it that's more convenient
|
||||
* than /pocketmine:help.
|
||||
*/
|
||||
public function getPreferredAlias(string $commandId, CommandAliasMap $fallbackMap) : string{
|
||||
$aliasList = $this->getMergedAliases($commandId, $fallbackMap);
|
||||
return $aliasList[array_key_first($aliasList)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]|string[][]
|
||||
* @phpstan-return array<string, string|list<string>>
|
||||
*/
|
||||
public function getAllAliases() : array{
|
||||
return $this->aliasToCommandMap;
|
||||
}
|
||||
}
|
@@ -24,18 +24,39 @@ declare(strict_types=1);
|
||||
namespace pocketmine\command;
|
||||
|
||||
interface CommandMap{
|
||||
|
||||
/**
|
||||
* @param Command[] $commands
|
||||
* Registering a command with (command(namespace="myplugin", name="mycommand"), otherAliases=["myc"]) will bind:
|
||||
* - /myplugin:mycommand (always works, error thrown if not unique)
|
||||
* - /mycommand (only works if not conflicted, not required to be unique)
|
||||
* - /myc (only works if not conflicted, not required to be unique)
|
||||
*
|
||||
* If two commands claim the same alias, it will become conflicted, and neither command will be usable with that
|
||||
* alias unless the alias is explicitly rebound in the alias map.
|
||||
* The user will be shown an error when trying to use it, listing all namespaced names (not aliases) of the commands
|
||||
* bound to it. The user can then use one of the namespaced names to run the command they want.
|
||||
*
|
||||
* @param string[] $otherAliases
|
||||
*
|
||||
* @phpstan-param list<string> $otherAliases
|
||||
*/
|
||||
public function registerAll(string $fallbackPrefix, array $commands) : void;
|
||||
|
||||
public function register(string $fallbackPrefix, Command $command, ?string $label = null) : bool;
|
||||
public function register(Command $command, array $otherAliases = []) : void;
|
||||
|
||||
public function dispatch(CommandSender $sender, string $cmdLine) : bool;
|
||||
|
||||
public function clearCommands() : void;
|
||||
|
||||
public function getCommand(string $name) : ?Command;
|
||||
/**
|
||||
* Returns the command(s) bound to the given name or alias.
|
||||
* This will return an array if the alias is conflicted (multiple commands bound to it).
|
||||
*
|
||||
* @return Command|Command[]|null
|
||||
* @phpstan-return Command|array<int, Command>|null
|
||||
*/
|
||||
public function getCommand(string $name, ?CommandAliasMap $senderAliasMap = null) : Command|array|null;
|
||||
|
||||
/**
|
||||
* Returns the global alias map for this command map.
|
||||
* Aliases in this map will be used as a fallback when user-specific aliases don't give any results.
|
||||
*/
|
||||
public function getAliasMap() : CommandAliasMap;
|
||||
}
|
||||
|
@@ -50,4 +50,10 @@ interface CommandSender extends Permissible{
|
||||
* @phpstan-param positive-int|null $height
|
||||
*/
|
||||
public function setScreenLineHeight(?int $height) : void;
|
||||
|
||||
/**
|
||||
* Returns the user's local alias map. This may contain special user-specific aliases that differ from the global
|
||||
* command map's aliases.
|
||||
*/
|
||||
public function getCommandAliasMap() : CommandAliasMap;
|
||||
}
|
||||
|
@@ -50,10 +50,11 @@ class FormattedCommandAlias extends Command{
|
||||
* @param string[] $formatStrings
|
||||
*/
|
||||
public function __construct(
|
||||
string $alias,
|
||||
string $namespace,
|
||||
string $name,
|
||||
private array $formatStrings
|
||||
){
|
||||
parent::__construct($alias, KnownTranslationFactory::pocketmine_command_userDefined_description());
|
||||
parent::__construct($namespace, $name, KnownTranslationFactory::pocketmine_command_userDefined_description());
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@@ -95,18 +96,22 @@ class FormattedCommandAlias extends Command{
|
||||
throw new AssumptionFailedError("This should have been checked before construction");
|
||||
}
|
||||
|
||||
if(($target = $commandMap->getCommand($commandLabel)) !== null){
|
||||
$timings = Timings::getCommandDispatchTimings($target->getLabel());
|
||||
//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())));
|
||||
$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)));
|
||||
|
||||
//to match the behaviour of SimpleCommandMap::dispatch()
|
||||
|
@@ -24,17 +24,20 @@ declare(strict_types=1);
|
||||
namespace pocketmine\command;
|
||||
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\plugin\PluginOwned;
|
||||
|
||||
final class PluginCommand extends Command implements PluginOwned{
|
||||
public function __construct(
|
||||
string $namespace,
|
||||
string $name,
|
||||
private Plugin $owner,
|
||||
private CommandExecutor $executor
|
||||
private CommandExecutor $executor,
|
||||
Translatable|string $description = "",
|
||||
Translatable|string|null $usageMessage = null
|
||||
){
|
||||
parent::__construct($name);
|
||||
$this->usageMessage = "";
|
||||
parent::__construct($namespace, $name, $description, $usageMessage);
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
@@ -43,13 +46,11 @@ final class PluginCommand extends Command implements PluginOwned{
|
||||
return false;
|
||||
}
|
||||
|
||||
$success = $this->executor->onCommand($sender, $this, $commandLabel, $args);
|
||||
|
||||
if(!$success && $this->usageMessage !== ""){
|
||||
if(!$this->executor->onCommand($sender, $this, $commandLabel, $args)){
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
return $success;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getOwningPlugin() : Plugin{
|
||||
|
@@ -27,6 +27,7 @@ use pocketmine\command\defaults\BanCommand;
|
||||
use pocketmine\command\defaults\BanIpCommand;
|
||||
use pocketmine\command\defaults\BanListCommand;
|
||||
use pocketmine\command\defaults\ClearCommand;
|
||||
use pocketmine\command\defaults\CommandAliasCommand;
|
||||
use pocketmine\command\defaults\DefaultGamemodeCommand;
|
||||
use pocketmine\command\defaults\DeopCommand;
|
||||
use pocketmine\command\defaults\DifficultyCommand;
|
||||
@@ -61,7 +62,6 @@ use pocketmine\command\defaults\TimeCommand;
|
||||
use pocketmine\command\defaults\TimingsCommand;
|
||||
use pocketmine\command\defaults\TitleCommand;
|
||||
use pocketmine\command\defaults\TransferServerCommand;
|
||||
use pocketmine\command\defaults\VanillaCommand;
|
||||
use pocketmine\command\defaults\VersionCommand;
|
||||
use pocketmine\command\defaults\WhitelistCommand;
|
||||
use pocketmine\command\defaults\XpCommand;
|
||||
@@ -72,10 +72,13 @@ use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use function str_contains;
|
||||
use function strcasecmp;
|
||||
use function strtolower;
|
||||
@@ -87,121 +90,83 @@ class SimpleCommandMap implements CommandMap{
|
||||
* @var Command[]
|
||||
* @phpstan-var array<string, Command>
|
||||
*/
|
||||
protected array $knownCommands = [];
|
||||
private array $uniqueCommands = [];
|
||||
|
||||
private CommandAliasMap $aliasMap;
|
||||
|
||||
public function __construct(private Server $server){
|
||||
$this->aliasMap = new CommandAliasMap();
|
||||
$this->setDefaultCommands();
|
||||
}
|
||||
|
||||
private function setDefaultCommands() : void{
|
||||
$this->registerAll("pocketmine", [
|
||||
new BanCommand(),
|
||||
new BanIpCommand(),
|
||||
new BanListCommand(),
|
||||
new ClearCommand(),
|
||||
new DefaultGamemodeCommand(),
|
||||
new DeopCommand(),
|
||||
new DifficultyCommand(),
|
||||
new DumpMemoryCommand(),
|
||||
new EffectCommand(),
|
||||
new EnchantCommand(),
|
||||
new GamemodeCommand(),
|
||||
new GarbageCollectorCommand(),
|
||||
new GiveCommand(),
|
||||
new HelpCommand(),
|
||||
new KickCommand(),
|
||||
new KillCommand(),
|
||||
new ListCommand(),
|
||||
new MeCommand(),
|
||||
new OpCommand(),
|
||||
new PardonCommand(),
|
||||
new PardonIpCommand(),
|
||||
new ParticleCommand(),
|
||||
new PluginsCommand(),
|
||||
new SaveCommand(),
|
||||
new SaveOffCommand(),
|
||||
new SaveOnCommand(),
|
||||
new SayCommand(),
|
||||
new SeedCommand(),
|
||||
new SetWorldSpawnCommand(),
|
||||
new SpawnpointCommand(),
|
||||
new StatusCommand(),
|
||||
new StopCommand(),
|
||||
new TeleportCommand(),
|
||||
new TellCommand(),
|
||||
new TimeCommand(),
|
||||
new TimingsCommand(),
|
||||
new TitleCommand(),
|
||||
new TransferServerCommand(),
|
||||
new VersionCommand(),
|
||||
new WhitelistCommand(),
|
||||
new XpCommand(),
|
||||
]);
|
||||
$pmPrefix = "pocketmine";
|
||||
$this->register(new BanCommand($pmPrefix, "ban"));
|
||||
$this->register(new BanIpCommand($pmPrefix, "ban-ip"));
|
||||
$this->register(new BanListCommand($pmPrefix, "banlist"));
|
||||
$this->register(new ClearCommand($pmPrefix, "clear"));
|
||||
$this->register(new CommandAliasCommand($pmPrefix, "cmdalias"));
|
||||
$this->register(new DefaultGamemodeCommand($pmPrefix, "defaultgamemode"));
|
||||
$this->register(new DeopCommand($pmPrefix, "deop"));
|
||||
$this->register(new DifficultyCommand($pmPrefix, "difficulty"));
|
||||
$this->register(new DumpMemoryCommand($pmPrefix, "dumpmemory"));
|
||||
$this->register(new EffectCommand($pmPrefix, "effect"));
|
||||
$this->register(new EnchantCommand($pmPrefix, "enchant"));
|
||||
$this->register(new GamemodeCommand($pmPrefix, "gamemode"));
|
||||
$this->register(new GarbageCollectorCommand($pmPrefix, "gc"));
|
||||
$this->register(new GiveCommand($pmPrefix, "give"));
|
||||
$this->register(new HelpCommand($pmPrefix, "help"), ["?"]);
|
||||
$this->register(new KickCommand($pmPrefix, "kick"));
|
||||
$this->register(new KillCommand($pmPrefix, "kill"), ["suicide"]);
|
||||
$this->register(new ListCommand($pmPrefix, "list"));
|
||||
$this->register(new MeCommand($pmPrefix, "me"));
|
||||
$this->register(new OpCommand($pmPrefix, "op"));
|
||||
$this->register(new PardonCommand($pmPrefix, "pardon"), ["unban"]);
|
||||
$this->register(new PardonIpCommand($pmPrefix, "pardon-ip"), ["unban-ip"]);
|
||||
$this->register(new ParticleCommand($pmPrefix, "particle"));
|
||||
$this->register(new PluginsCommand($pmPrefix, "plugins"), ["pl"]);
|
||||
$this->register(new SaveCommand($pmPrefix, "save-all"));
|
||||
$this->register(new SaveOffCommand($pmPrefix, "save-off"));
|
||||
$this->register(new SaveOnCommand($pmPrefix, "save-on"));
|
||||
$this->register(new SayCommand($pmPrefix, "say"));
|
||||
$this->register(new SeedCommand($pmPrefix, "seed"));
|
||||
$this->register(new SetWorldSpawnCommand($pmPrefix, "setworldspawn"));
|
||||
$this->register(new SpawnpointCommand($pmPrefix, "spawnpoint"));
|
||||
$this->register(new StatusCommand($pmPrefix, "status"));
|
||||
$this->register(new StopCommand($pmPrefix, "stop"));
|
||||
$this->register(new TeleportCommand($pmPrefix, "tp"), ["teleport"]);
|
||||
$this->register(new TellCommand($pmPrefix, "tell"), ["w", "msg"]);
|
||||
$this->register(new TimeCommand($pmPrefix, "time"));
|
||||
$this->register(new TimingsCommand($pmPrefix, "timings"));
|
||||
$this->register(new TitleCommand($pmPrefix, "title"));
|
||||
$this->register(new TransferServerCommand($pmPrefix, "transferserver"));
|
||||
$this->register(new VersionCommand($pmPrefix, "version"), ["ver", "about"]);
|
||||
$this->register(new WhitelistCommand($pmPrefix, "whitelist"));
|
||||
$this->register(new XpCommand($pmPrefix, "xp"));
|
||||
}
|
||||
|
||||
public function registerAll(string $fallbackPrefix, array $commands) : void{
|
||||
foreach($commands as $command){
|
||||
$this->register($fallbackPrefix, $command);
|
||||
}
|
||||
}
|
||||
|
||||
public function register(string $fallbackPrefix, Command $command, ?string $label = null) : bool{
|
||||
public function register(Command $command, array $otherAliases = []) : void{
|
||||
if(count($command->getPermissions()) === 0){
|
||||
throw new \InvalidArgumentException("Commands must have a permission set");
|
||||
}
|
||||
|
||||
if($label === null){
|
||||
$label = $command->getLabel();
|
||||
}
|
||||
$label = trim($label);
|
||||
$fallbackPrefix = strtolower(trim($fallbackPrefix));
|
||||
|
||||
$registered = $this->registerAlias($command, false, $fallbackPrefix, $label);
|
||||
|
||||
$aliases = $command->getAliases();
|
||||
foreach($aliases as $index => $alias){
|
||||
if(!$this->registerAlias($command, true, $fallbackPrefix, $alias)){
|
||||
unset($aliases[$index]);
|
||||
}
|
||||
}
|
||||
$command->setAliases(array_values($aliases));
|
||||
|
||||
if(!$registered){
|
||||
$command->setLabel($fallbackPrefix . ":" . $label);
|
||||
$commandId = $command->getId();
|
||||
if(isset($this->uniqueCommands[$commandId])){
|
||||
throw new \InvalidArgumentException("A command with ID $commandId has already been registered");
|
||||
}
|
||||
|
||||
$command->register($this);
|
||||
$preferredAlias = trim($command->getName());
|
||||
$this->aliasMap->bindAlias($commandId, $preferredAlias, override: false);
|
||||
foreach($otherAliases as $alias){
|
||||
$this->aliasMap->bindAlias($commandId, $alias, override: false);
|
||||
}
|
||||
|
||||
return $registered;
|
||||
$this->uniqueCommands[$commandId] = $command;
|
||||
}
|
||||
|
||||
public function unregister(Command $command) : bool{
|
||||
foreach(Utils::promoteKeys($this->knownCommands) as $lbl => $cmd){
|
||||
if($cmd === $command){
|
||||
unset($this->knownCommands[$lbl]);
|
||||
}
|
||||
}
|
||||
|
||||
$command->unregister($this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function registerAlias(Command $command, bool $isAlias, string $fallbackPrefix, string $label) : bool{
|
||||
$this->knownCommands[$fallbackPrefix . ":" . $label] = $command;
|
||||
if(($command instanceof VanillaCommand || $isAlias) && isset($this->knownCommands[$label])){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(isset($this->knownCommands[$label]) && $this->knownCommands[$label]->getLabel() === $label){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!$isAlias){
|
||||
$command->setLabel($label);
|
||||
}
|
||||
|
||||
$this->knownCommands[$label] = $command;
|
||||
unset($this->uniqueCommands[$command->getId()]);
|
||||
$this->aliasMap->unbindAliasesForCommand($command->getId());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -210,44 +175,98 @@ class SimpleCommandMap implements CommandMap{
|
||||
$args = CommandStringHelper::parseQuoteAware($commandLine);
|
||||
|
||||
$sentCommandLabel = array_shift($args);
|
||||
if($sentCommandLabel !== null && ($target = $this->getCommand($sentCommandLabel)) !== null){
|
||||
$timings = Timings::getCommandDispatchTimings($target->getLabel());
|
||||
if($sentCommandLabel !== null && ($target = $this->getCommand($sentCommandLabel, $sender->getCommandAliasMap())) !== null){
|
||||
if(is_array($target)){
|
||||
self::handleConflicted($sender, $sentCommandLabel, $target, $this->aliasMap);
|
||||
return true;
|
||||
}
|
||||
$timings = Timings::getCommandDispatchTimings($target->getId());
|
||||
$timings->startTiming();
|
||||
|
||||
try{
|
||||
if($target->testPermission($sender)){
|
||||
if($target->testPermission($sentCommandLabel, $sender)){
|
||||
$target->execute($sender, $sentCommandLabel, $args);
|
||||
}
|
||||
}catch(InvalidCommandSyntaxException $e){
|
||||
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage())));
|
||||
//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")));
|
||||
}finally{
|
||||
$timings->stopTiming();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($sentCommandLabel ?? "", "/help")->prefix(TextFormat::RED));
|
||||
//Don't love hardcoding the command ID here, but it seems like the only way for now
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound(
|
||||
$sentCommandLabel ?? "",
|
||||
"/" . $sender->getCommandAliasMap()->getPreferredAlias("pocketmine:help", $this->aliasMap)
|
||||
)->prefix(TextFormat::RED));
|
||||
return false;
|
||||
}
|
||||
|
||||
public function clearCommands() : void{
|
||||
foreach($this->knownCommands as $command){
|
||||
$command->unregister($this);
|
||||
/**
|
||||
* TODO: probably need to find a better place to put this
|
||||
* @internal
|
||||
* @param Command[] $conflictedEntries
|
||||
* @phpstan-param array<int, Command> $conflictedEntries
|
||||
*/
|
||||
public static function handleConflicted(CommandSender $sender, string $alias, array $conflictedEntries, CommandAliasMap $fallbackAliasMap) : void{
|
||||
$candidates = [];
|
||||
$userAliasMap = $sender->getCommandAliasMap();
|
||||
foreach($conflictedEntries as $c){
|
||||
if($c->testPermissionSilent($sender)){
|
||||
$candidates[] = "/" . $c->getId();
|
||||
}
|
||||
}
|
||||
$this->knownCommands = [];
|
||||
if(count($candidates) > 0){
|
||||
//there might only be 1 permissible command here, but we still don't auto-select in this case
|
||||
//because it might cause surprising behaviour if the user's permissions change between command
|
||||
//invocations. Better to force them to use an unambiguous alias in all cases.
|
||||
$candidateNames = implode(", ", $candidates);
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_error_aliasConflict("/$alias", $candidateNames)->prefix(TextFormat::RED));
|
||||
//Don't love hardcoding the command ID here, but it seems like the only way for now
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_error_aliasConflictTip(
|
||||
"/" . $userAliasMap->getPreferredAlias("pocketmine:cmdalias", $fallbackAliasMap)
|
||||
)->prefix(TextFormat::RED));
|
||||
}else{
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($alias)->prefix(TextFormat::RED));
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCommands() : void{
|
||||
$this->aliasMap = new CommandAliasMap();
|
||||
$this->uniqueCommands = [];
|
||||
$this->setDefaultCommands();
|
||||
}
|
||||
|
||||
public function getCommand(string $name) : ?Command{
|
||||
return $this->knownCommands[$name] ?? null;
|
||||
public function getCommand(string $name, ?CommandAliasMap $senderAliasMap = null) : Command|array|null{
|
||||
if(isset($this->uniqueCommands[$name])){ //direct command ID reference
|
||||
return $this->uniqueCommands[$name];
|
||||
}
|
||||
$commandId = $senderAliasMap?->resolveAlias($name) ?? $this->aliasMap->resolveAlias($name);
|
||||
if(is_string($commandId)){
|
||||
return $this->uniqueCommands[$commandId] ?? null;
|
||||
}
|
||||
if(is_array($commandId)){
|
||||
//the user's command map may refer to commands that are no longer registered, so we need to filter these
|
||||
//from the result set
|
||||
//we don't deconflict if there's only 1 command left because we don't want re-running a command to randomly
|
||||
//have a different result if the global command map was modified - the user can explicitly rebind the
|
||||
//alias in this case
|
||||
return array_filter(array_map(
|
||||
fn(string $c) => $this->uniqueCommands[$c] ?? null,
|
||||
$commandId
|
||||
), is_object(...));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Command[]
|
||||
* @phpstan-return array<string, Command>
|
||||
*/
|
||||
public function getCommands() : array{
|
||||
return $this->knownCommands;
|
||||
public function getUniqueCommands() : array{
|
||||
return $this->uniqueCommands;
|
||||
}
|
||||
|
||||
public function registerServerAliases() : void{
|
||||
@@ -268,7 +287,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
$commandName = array_shift($args) ?? "";
|
||||
$command = $this->getCommand($commandName);
|
||||
|
||||
if($command === null){
|
||||
if(!$command instanceof Command){
|
||||
$bad[] = $commandString;
|
||||
}elseif(strcasecmp($commandName, $alias) === 0){
|
||||
$recursive[] = $commandString;
|
||||
@@ -290,11 +309,15 @@ class SimpleCommandMap implements CommandMap{
|
||||
//These registered commands have absolute priority
|
||||
$lowerAlias = strtolower($alias);
|
||||
if(count($targets) > 0){
|
||||
$this->knownCommands[$lowerAlias] = new FormattedCommandAlias($lowerAlias, $targets);
|
||||
$aliasInstance = new FormattedCommandAlias("pocketmine-config-defined", $lowerAlias, $targets);
|
||||
$this->aliasMap->bindAlias($aliasInstance->getId(), $lowerAlias, override: true);
|
||||
$this->uniqueCommands[$aliasInstance->getId()] = $aliasInstance;
|
||||
}else{
|
||||
unset($this->knownCommands[$lowerAlias]);
|
||||
//no targets blackholes the alias - this allows config to delete unwanted aliases
|
||||
$this->aliasMap->unbindAlias($lowerAlias);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function getAliasMap() : CommandAliasMap{ return $this->aliasMap; }
|
||||
}
|
||||
|
@@ -35,9 +35,10 @@ use function implode;
|
||||
|
||||
class BanCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"ban",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_ban_player_description(),
|
||||
KnownTranslationFactory::commands_ban_usage()
|
||||
);
|
||||
|
@@ -36,9 +36,10 @@ use function inet_pton;
|
||||
|
||||
class BanIpCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"ban-ip",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_ban_ip_description(),
|
||||
KnownTranslationFactory::commands_banip_usage()
|
||||
);
|
||||
|
@@ -37,9 +37,10 @@ use const SORT_STRING;
|
||||
|
||||
class BanListCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"banlist",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_banlist_description(),
|
||||
KnownTranslationFactory::commands_banlist_usage()
|
||||
);
|
||||
|
@@ -39,9 +39,10 @@ use function min;
|
||||
|
||||
class ClearCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"clear",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_clear_description(),
|
||||
KnownTranslationFactory::pocketmine_command_clear_usage()
|
||||
);
|
||||
@@ -53,7 +54,7 @@ class ClearCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$target = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_CLEAR_SELF, DefaultPermissionNames::COMMAND_CLEAR_OTHER);
|
||||
$target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_CLEAR_SELF, DefaultPermissionNames::COMMAND_CLEAR_OTHER);
|
||||
if($target === null){
|
||||
return true;
|
||||
}
|
||||
|
161
src/command/defaults/CommandAliasCommand.php
Normal file
161
src/command/defaults/CommandAliasCommand.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\command\defaults;
|
||||
|
||||
use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
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;
|
||||
use function ksort;
|
||||
|
||||
final class CommandAliasCommand extends Command{
|
||||
private const SELF_PERM = DefaultPermissionNames::COMMAND_CMDALIAS_EDIT_SELF;
|
||||
private const GLOBAL_PERM = DefaultPermissionNames::COMMAND_CMDALIAS_EDIT_GLOBAL;
|
||||
private const LIST_PERM = DefaultPermissionNames::COMMAND_CMDALIAS_LIST;
|
||||
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_cmdalias_description(),
|
||||
"/cmdalias [global] create <alias> <target> OR /cmdalias [global] delete <alias>"
|
||||
);
|
||||
$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;
|
||||
$commandMap = $sender->getServer()->getCommandMap();
|
||||
if($parsedArgs[0] === "global"){
|
||||
$editPermission = self::GLOBAL_PERM;
|
||||
$permissionCtx = $commandLabel . " global";
|
||||
array_shift($parsedArgs);
|
||||
$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;
|
||||
}
|
||||
|
||||
[$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()));
|
||||
if($auditLog){
|
||||
Command::broadcastCommandMessage($sender, $message);
|
||||
}else{
|
||||
$sender->sendMessage($message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}else{
|
||||
$sender->sendMessage($messageScope(KnownTranslationFactory::pocketmine_command_cmdalias_delete_notFound("/$alias"))->prefix(TextFormat::RED));
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
@@ -33,9 +33,10 @@ use function count;
|
||||
|
||||
class DefaultGamemodeCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"defaultgamemode",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_defaultgamemode_description(),
|
||||
KnownTranslationFactory::commands_defaultgamemode_usage()
|
||||
);
|
||||
|
@@ -35,9 +35,10 @@ use function count;
|
||||
|
||||
class DeopCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"deop",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_deop_description(),
|
||||
KnownTranslationFactory::commands_deop_usage()
|
||||
);
|
||||
|
@@ -34,9 +34,10 @@ use function count;
|
||||
|
||||
class DifficultyCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"difficulty",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_difficulty_description(),
|
||||
KnownTranslationFactory::commands_difficulty_usage()
|
||||
);
|
||||
|
@@ -31,9 +31,10 @@ use function date;
|
||||
|
||||
class DumpMemoryCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"dumpmemory",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_dumpmemory_description(),
|
||||
"/dumpmemory [path]"
|
||||
);
|
||||
|
@@ -36,9 +36,10 @@ use function strtolower;
|
||||
|
||||
class EffectCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"effect",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_effect_description(),
|
||||
KnownTranslationFactory::commands_effect_usage()
|
||||
);
|
||||
@@ -53,7 +54,7 @@ class EffectCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_EFFECT_SELF, DefaultPermissionNames::COMMAND_EFFECT_OTHER);
|
||||
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_EFFECT_SELF, DefaultPermissionNames::COMMAND_EFFECT_OTHER);
|
||||
if($player === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -34,9 +34,10 @@ use function count;
|
||||
|
||||
class EnchantCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"enchant",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_enchant_description(),
|
||||
KnownTranslationFactory::commands_enchant_usage()
|
||||
);
|
||||
@@ -51,7 +52,7 @@ class EnchantCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_ENCHANT_SELF, DefaultPermissionNames::COMMAND_ENCHANT_OTHER);
|
||||
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_ENCHANT_SELF, DefaultPermissionNames::COMMAND_ENCHANT_OTHER);
|
||||
if($player === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -33,9 +33,10 @@ use function count;
|
||||
|
||||
class GamemodeCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"gamemode",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_gamemode_description(),
|
||||
KnownTranslationFactory::commands_gamemode_usage()
|
||||
);
|
||||
@@ -56,7 +57,7 @@ class GamemodeCommand extends VanillaCommand{
|
||||
return true;
|
||||
}
|
||||
|
||||
$target = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_GAMEMODE_SELF, DefaultPermissionNames::COMMAND_GAMEMODE_OTHER);
|
||||
$target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_GAMEMODE_SELF, DefaultPermissionNames::COMMAND_GAMEMODE_OTHER);
|
||||
if($target === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -34,9 +34,10 @@ use function round;
|
||||
|
||||
class GarbageCollectorCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"gc",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_gc_description()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_GC);
|
||||
|
@@ -41,9 +41,10 @@ use function implode;
|
||||
|
||||
class GiveCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"give",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_give_description(),
|
||||
KnownTranslationFactory::pocketmine_command_give_usage()
|
||||
);
|
||||
@@ -58,7 +59,7 @@ class GiveCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_GIVE_SELF, DefaultPermissionNames::COMMAND_GIVE_OTHER);
|
||||
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_GIVE_SELF, DefaultPermissionNames::COMMAND_GIVE_OTHER);
|
||||
if($player === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -25,15 +25,20 @@ namespace pocketmine\command\defaults;
|
||||
|
||||
use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\SimpleCommandMap;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
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;
|
||||
@@ -45,12 +50,12 @@ use const SORT_NATURAL;
|
||||
|
||||
class HelpCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"help",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_help_description(),
|
||||
KnownTranslationFactory::commands_help_usage(),
|
||||
["?"]
|
||||
KnownTranslationFactory::commands_help_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_HELP);
|
||||
}
|
||||
@@ -72,15 +77,23 @@ class HelpCommand extends VanillaCommand{
|
||||
|
||||
$pageHeight = $sender->getScreenLineHeight();
|
||||
|
||||
//TODO: maybe inject this in the constructor instead of assuming the server's command map?
|
||||
$commandMap = $sender->getServer()->getCommandMap();
|
||||
$userAliasMap = $sender->getCommandAliasMap();
|
||||
if($commandName === ""){
|
||||
$commands = [];
|
||||
foreach($sender->getServer()->getCommandMap()->getCommands() as $command){
|
||||
foreach($commandMap->getUniqueCommands() as $command){
|
||||
if($command->testPermissionSilent($sender)){
|
||||
$commands[$command->getLabel()] = $command;
|
||||
$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);
|
||||
$commands = array_chunk($commands, $pageHeight, preserve_keys: true);
|
||||
$pageNumber = min(count($commands), $pageNumber);
|
||||
if($pageNumber < 1){
|
||||
$pageNumber = 1;
|
||||
@@ -88,31 +101,35 @@ class HelpCommand extends VanillaCommand{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_help_header((string) $pageNumber, (string) count($commands)));
|
||||
$lang = $sender->getLanguage();
|
||||
if(isset($commands[$pageNumber - 1])){
|
||||
foreach($commands[$pageNumber - 1] as $command){
|
||||
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 . "/" . $command->getLabel() . ": " . TextFormat::RESET . $descriptionString);
|
||||
$sender->sendMessage(TextFormat::DARK_GREEN . "/$preferredAlias: " . TextFormat::RESET . $descriptionString);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}else{
|
||||
if(($cmd = $sender->getServer()->getCommandMap()->getCommand(strtolower($commandName))) instanceof Command){
|
||||
if($cmd->testPermissionSilent($sender)){
|
||||
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 = $cmd->getDescription();
|
||||
$description = $command->getDescription();
|
||||
$descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description;
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($commandName)
|
||||
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
|
||||
->prefix(TextFormat::GOLD));
|
||||
|
||||
$usage = $cmd->getUsage();
|
||||
$usage = $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 = $cmd->getAliases();
|
||||
$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));
|
||||
@@ -120,7 +137,7 @@ class HelpCommand extends VanillaCommand{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/help")->prefix(TextFormat::RED));
|
||||
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/" . $userAliasMap->getPreferredAlias("pocketmine:help", $commandMap->getAliasMap()))->prefix(TextFormat::RED));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -37,9 +37,10 @@ use function trim;
|
||||
|
||||
class KickCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"kick",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_kick_description(),
|
||||
KnownTranslationFactory::commands_kick_usage()
|
||||
);
|
||||
|
@@ -33,12 +33,12 @@ use function count;
|
||||
|
||||
class KillCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"kill",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_kill_description(),
|
||||
KnownTranslationFactory::pocketmine_command_kill_usage(),
|
||||
["suicide"]
|
||||
KnownTranslationFactory::pocketmine_command_kill_usage()
|
||||
);
|
||||
$this->setPermissions([DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER]);
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class KillCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER);
|
||||
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_KILL_SELF, DefaultPermissionNames::COMMAND_KILL_OTHER);
|
||||
if($player === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -36,9 +36,10 @@ use const SORT_STRING;
|
||||
|
||||
class ListCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"list",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_list_description()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_LIST);
|
||||
|
@@ -34,9 +34,10 @@ use function implode;
|
||||
|
||||
class MeCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"me",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_me_description(),
|
||||
KnownTranslationFactory::commands_me_usage()
|
||||
);
|
||||
|
@@ -35,9 +35,10 @@ use function count;
|
||||
|
||||
class OpCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"op",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_op_description(),
|
||||
KnownTranslationFactory::commands_op_usage()
|
||||
);
|
||||
|
@@ -32,12 +32,12 @@ use function count;
|
||||
|
||||
class PardonCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"pardon",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_unban_player_description(),
|
||||
KnownTranslationFactory::commands_unban_usage(),
|
||||
["unban"]
|
||||
KnownTranslationFactory::commands_unban_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_UNBAN_PLAYER);
|
||||
}
|
||||
|
@@ -33,12 +33,12 @@ use function inet_pton;
|
||||
|
||||
class PardonIpCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"pardon-ip",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_unban_ip_description(),
|
||||
KnownTranslationFactory::commands_unbanip_usage(),
|
||||
["unban-ip"]
|
||||
KnownTranslationFactory::commands_unbanip_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_UNBAN_IP);
|
||||
}
|
||||
|
@@ -74,9 +74,10 @@ use function strtolower;
|
||||
|
||||
class ParticleCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"particle",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_particle_description(),
|
||||
KnownTranslationFactory::pocketmine_command_particle_usage()
|
||||
);
|
||||
|
@@ -36,12 +36,12 @@ use const SORT_STRING;
|
||||
|
||||
class PluginsCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"plugins",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_plugins_description(),
|
||||
null,
|
||||
["pl"]
|
||||
null
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_PLUGINS);
|
||||
}
|
||||
|
@@ -32,9 +32,10 @@ use function round;
|
||||
|
||||
class SaveCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"save-all",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_save_description()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_SAVE_PERFORM);
|
||||
|
@@ -30,9 +30,10 @@ use pocketmine\permission\DefaultPermissionNames;
|
||||
|
||||
class SaveOffCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"save-off",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_saveoff_description()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_SAVE_DISABLE);
|
||||
|
@@ -30,9 +30,10 @@ use pocketmine\permission\DefaultPermissionNames;
|
||||
|
||||
class SaveOnCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"save-on",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_saveon_description()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_SAVE_ENABLE);
|
||||
|
@@ -35,9 +35,10 @@ use function implode;
|
||||
|
||||
class SayCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"say",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_say_description(),
|
||||
KnownTranslationFactory::commands_say_usage()
|
||||
);
|
||||
|
@@ -30,9 +30,10 @@ use pocketmine\player\Player;
|
||||
|
||||
class SeedCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"seed",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_seed_description()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_SEED);
|
||||
|
@@ -36,9 +36,10 @@ use function count;
|
||||
|
||||
class SetWorldSpawnCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"setworldspawn",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_setworldspawn_description(),
|
||||
KnownTranslationFactory::commands_setworldspawn_usage()
|
||||
);
|
||||
|
@@ -36,9 +36,10 @@ use function round;
|
||||
|
||||
class SpawnpointCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"spawnpoint",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_spawnpoint_description(),
|
||||
KnownTranslationFactory::commands_spawnpoint_usage()
|
||||
);
|
||||
@@ -49,7 +50,7 @@ class SpawnpointCommand extends VanillaCommand{
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
$target = $this->fetchPermittedPlayerTarget($sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF, DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER);
|
||||
$target = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0] ?? null, DefaultPermissionNames::COMMAND_SPAWNPOINT_SELF, DefaultPermissionNames::COMMAND_SPAWNPOINT_OTHER);
|
||||
if($target === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -36,9 +36,10 @@ use function round;
|
||||
|
||||
class StatusCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"status",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_status_description()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_STATUS);
|
||||
|
@@ -30,9 +30,10 @@ use pocketmine\permission\DefaultPermissionNames;
|
||||
|
||||
class StopCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"stop",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_stop_description()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_STOP);
|
||||
|
@@ -39,12 +39,12 @@ use function round;
|
||||
|
||||
class TeleportCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"tp",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_tp_description(),
|
||||
KnownTranslationFactory::commands_tp_usage(),
|
||||
["teleport"]
|
||||
KnownTranslationFactory::commands_tp_usage()
|
||||
);
|
||||
$this->setPermissions([
|
||||
DefaultPermissionNames::COMMAND_TELEPORT_SELF,
|
||||
@@ -77,7 +77,7 @@ class TeleportCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$subject = $this->fetchPermittedPlayerTarget($sender, $subjectName, DefaultPermissionNames::COMMAND_TELEPORT_SELF, DefaultPermissionNames::COMMAND_TELEPORT_OTHER);
|
||||
$subject = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $subjectName, DefaultPermissionNames::COMMAND_TELEPORT_SELF, DefaultPermissionNames::COMMAND_TELEPORT_OTHER);
|
||||
if($subject === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -36,12 +36,12 @@ use function implode;
|
||||
|
||||
class TellCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"tell",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_tell_description(),
|
||||
KnownTranslationFactory::commands_message_usage(),
|
||||
["w", "msg"]
|
||||
KnownTranslationFactory::commands_message_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_TELL);
|
||||
}
|
||||
|
@@ -34,9 +34,10 @@ use function count;
|
||||
|
||||
class TimeCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"time",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_time_description(),
|
||||
KnownTranslationFactory::pocketmine_command_time_usage()
|
||||
);
|
||||
@@ -53,9 +54,10 @@ class TimeCommand extends VanillaCommand{
|
||||
if(count($args) < 1){
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
$testPermissionCtx = $commandLabel . " " . $args[0];
|
||||
|
||||
if($args[0] === "start"){
|
||||
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_START)){
|
||||
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_START)){
|
||||
return true;
|
||||
}
|
||||
foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){
|
||||
@@ -64,7 +66,7 @@ class TimeCommand extends VanillaCommand{
|
||||
Command::broadcastCommandMessage($sender, "Restarted the time");
|
||||
return true;
|
||||
}elseif($args[0] === "stop"){
|
||||
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_STOP)){
|
||||
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_STOP)){
|
||||
return true;
|
||||
}
|
||||
foreach($sender->getServer()->getWorldManager()->getWorlds() as $world){
|
||||
@@ -73,7 +75,7 @@ class TimeCommand extends VanillaCommand{
|
||||
Command::broadcastCommandMessage($sender, "Stopped the time");
|
||||
return true;
|
||||
}elseif($args[0] === "query"){
|
||||
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_QUERY)){
|
||||
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_QUERY)){
|
||||
return true;
|
||||
}
|
||||
if($sender instanceof Player){
|
||||
@@ -90,7 +92,7 @@ class TimeCommand extends VanillaCommand{
|
||||
}
|
||||
|
||||
if($args[0] === "set"){
|
||||
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_SET)){
|
||||
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_SET)){
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -123,7 +125,7 @@ class TimeCommand extends VanillaCommand{
|
||||
}
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_time_set((string) $value));
|
||||
}elseif($args[0] === "add"){
|
||||
if(!$this->testPermission($sender, DefaultPermissionNames::COMMAND_TIME_ADD)){
|
||||
if(!$this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_TIME_ADD)){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -60,9 +60,10 @@ use const PHP_EOL;
|
||||
|
||||
class TimingsCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"timings",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_timings_description(),
|
||||
KnownTranslationFactory::pocketmine_command_timings_usage()
|
||||
);
|
||||
|
@@ -33,9 +33,10 @@ use function implode;
|
||||
|
||||
class TitleCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"title",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_title_description(),
|
||||
KnownTranslationFactory::commands_title_usage()
|
||||
);
|
||||
@@ -50,7 +51,7 @@ class TitleCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[0], DefaultPermissionNames::COMMAND_TITLE_SELF, DefaultPermissionNames::COMMAND_TITLE_OTHER);
|
||||
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[0], DefaultPermissionNames::COMMAND_TITLE_SELF, DefaultPermissionNames::COMMAND_TITLE_OTHER);
|
||||
if($player === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -32,9 +32,10 @@ use function count;
|
||||
|
||||
class TransferServerCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"transferserver",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_transferserver_description(),
|
||||
KnownTranslationFactory::pocketmine_command_transferserver_usage()
|
||||
);
|
||||
|
@@ -36,7 +36,13 @@ abstract class VanillaCommand extends Command{
|
||||
public const MAX_COORD = 30000000;
|
||||
public const MIN_COORD = -30000000;
|
||||
|
||||
protected function fetchPermittedPlayerTarget(CommandSender $sender, ?string $target, string $selfPermission, string $otherPermission) : ?Player{
|
||||
protected function fetchPermittedPlayerTarget(
|
||||
string $testPermissionContext,
|
||||
CommandSender $sender,
|
||||
?string $target,
|
||||
string $selfPermission,
|
||||
string $otherPermission
|
||||
) : ?Player{
|
||||
if($target !== null){
|
||||
$player = $sender->getServer()->getPlayerByPrefix($target);
|
||||
}elseif($sender instanceof Player){
|
||||
@@ -49,9 +55,12 @@ abstract class VanillaCommand extends Command{
|
||||
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound()->prefix(TextFormat::RED));
|
||||
return null;
|
||||
}
|
||||
//TODO: using loud testPermission here will generate misleading messages
|
||||
//e.g. if the sender has self permission and tries to use the command on another player, it will give them a
|
||||
//generic message saying that they don't have permission to use the command, which is not correct
|
||||
if(
|
||||
($player === $sender && $this->testPermission($sender, $selfPermission)) ||
|
||||
($player !== $sender && $this->testPermission($sender, $otherPermission))
|
||||
($player === $sender && $this->testPermission($testPermissionContext, $sender, $selfPermission)) ||
|
||||
($player !== $sender && $this->testPermission($testPermissionContext, $sender, $otherPermission))
|
||||
){
|
||||
return $player;
|
||||
}
|
||||
|
@@ -40,12 +40,12 @@ use const PHP_VERSION;
|
||||
|
||||
class VersionCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"version",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_version_description(),
|
||||
KnownTranslationFactory::pocketmine_command_version_usage(),
|
||||
["ver", "about"]
|
||||
KnownTranslationFactory::pocketmine_command_version_usage()
|
||||
);
|
||||
$this->setPermission(DefaultPermissionNames::COMMAND_VERSION);
|
||||
}
|
||||
|
@@ -39,9 +39,10 @@ use const SORT_STRING;
|
||||
|
||||
class WhitelistCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"whitelist",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_whitelist_description(),
|
||||
KnownTranslationFactory::commands_whitelist_usage()
|
||||
);
|
||||
@@ -56,10 +57,14 @@ class WhitelistCommand extends VanillaCommand{
|
||||
}
|
||||
|
||||
public function execute(CommandSender $sender, string $commandLabel, array $args){
|
||||
if(count($args) === 0){
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
$testPermissionCtx = $commandLabel . " " . $args[0];
|
||||
if(count($args) === 1){
|
||||
switch(strtolower($args[0])){
|
||||
case "reload":
|
||||
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_RELOAD)){
|
||||
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_RELOAD)){
|
||||
$server = $sender->getServer();
|
||||
$server->getWhitelisted()->reload();
|
||||
if($server->hasWhitelist()){
|
||||
@@ -70,7 +75,7 @@ class WhitelistCommand extends VanillaCommand{
|
||||
|
||||
return true;
|
||||
case "on":
|
||||
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_ENABLE)){
|
||||
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_ENABLE)){
|
||||
$server = $sender->getServer();
|
||||
$server->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, true);
|
||||
$this->kickNonWhitelistedPlayers($server);
|
||||
@@ -79,14 +84,14 @@ class WhitelistCommand extends VanillaCommand{
|
||||
|
||||
return true;
|
||||
case "off":
|
||||
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_DISABLE)){
|
||||
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_DISABLE)){
|
||||
$sender->getServer()->getConfigGroup()->setConfigBool(ServerProperties::WHITELIST, false);
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_disabled());
|
||||
}
|
||||
|
||||
return true;
|
||||
case "list":
|
||||
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_LIST)){
|
||||
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_LIST)){
|
||||
$entries = $sender->getServer()->getWhitelisted()->getAll(true);
|
||||
sort($entries, SORT_STRING);
|
||||
$result = implode(", ", $entries);
|
||||
@@ -112,14 +117,14 @@ class WhitelistCommand extends VanillaCommand{
|
||||
}
|
||||
switch(strtolower($args[0])){
|
||||
case "add":
|
||||
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_ADD)){
|
||||
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_ADD)){
|
||||
$sender->getServer()->addWhitelist($args[1]);
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_whitelist_add_success($args[1]));
|
||||
}
|
||||
|
||||
return true;
|
||||
case "remove":
|
||||
if($this->testPermission($sender, DefaultPermissionNames::COMMAND_WHITELIST_REMOVE)){
|
||||
if($this->testPermission($testPermissionCtx, $sender, DefaultPermissionNames::COMMAND_WHITELIST_REMOVE)){
|
||||
$server = $sender->getServer();
|
||||
$server->removeWhitelist($args[1]);
|
||||
if(!$server->isWhitelisted($args[1])){
|
||||
|
@@ -38,9 +38,10 @@ use function substr;
|
||||
|
||||
class XpCommand extends VanillaCommand{
|
||||
|
||||
public function __construct(){
|
||||
public function __construct(string $namespace, string $name){
|
||||
parent::__construct(
|
||||
"xp",
|
||||
$namespace,
|
||||
$name,
|
||||
KnownTranslationFactory::pocketmine_command_xp_description(),
|
||||
KnownTranslationFactory::pocketmine_command_xp_usage()
|
||||
);
|
||||
@@ -55,7 +56,7 @@ class XpCommand extends VanillaCommand{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
$player = $this->fetchPermittedPlayerTarget($sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
|
||||
$player = $this->fetchPermittedPlayerTarget($commandLabel, $sender, $args[1] ?? null, DefaultPermissionNames::COMMAND_XP_SELF, DefaultPermissionNames::COMMAND_XP_OTHER);
|
||||
if($player === null){
|
||||
return true;
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\console;
|
||||
|
||||
use pocketmine\command\CommandAliasMap;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\lang\Language;
|
||||
use pocketmine\lang\Translatable;
|
||||
@@ -42,11 +43,14 @@ class ConsoleCommandSender implements CommandSender{
|
||||
/** @phpstan-var positive-int|null */
|
||||
protected ?int $lineHeight = null;
|
||||
|
||||
private CommandAliasMap $commandAliasMap;
|
||||
|
||||
public function __construct(
|
||||
private Server $server,
|
||||
private Language $language
|
||||
){
|
||||
$this->perm = new PermissibleBase([DefaultPermissions::ROOT_CONSOLE => true]);
|
||||
$this->commandAliasMap = new CommandAliasMap();
|
||||
}
|
||||
|
||||
public function getServer() : Server{
|
||||
@@ -81,4 +85,6 @@ class ConsoleCommandSender implements CommandSender{
|
||||
}
|
||||
$this->lineHeight = $height;
|
||||
}
|
||||
|
||||
public function getCommandAliasMap() : CommandAliasMap{ return $this->commandAliasMap; }
|
||||
}
|
||||
|
@@ -1535,6 +1535,62 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CLEAR_USAGE, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_create_success(Translatable|string $alias, Translatable|string $target) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_CREATE_SUCCESS, [
|
||||
"alias" => $alias,
|
||||
"target" => $target,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_delete_notFound(Translatable|string $alias) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_DELETE_NOTFOUND, [
|
||||
"alias" => $alias,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_delete_success(Translatable|string $alias) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_DELETE_SUCCESS, [
|
||||
"alias" => $alias,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_description() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_DESCRIPTION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_list_conflicted(Translatable|string $alias, Translatable|string $commandIds) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_LIST_CONFLICTED, [
|
||||
"alias" => $alias,
|
||||
"commandIds" => $commandIds,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_list_noneSet() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_LIST_NONESET, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_list_normal(Translatable|string $alias, Translatable|string $commandId) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_LIST_NORMAL, [
|
||||
"alias" => $alias,
|
||||
"commandId" => $commandId,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_scope_global() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_SCOPE_GLOBAL, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_scope_userSpecific() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_SCOPE_USERSPECIFIC, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_cmdalias_template(Translatable|string $message, Translatable|string $scope) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_CMDALIAS_TEMPLATE, [
|
||||
"message" => $message,
|
||||
"scope" => $scope,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_defaultgamemode_description() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_DEFAULTGAMEMODE_DESCRIPTION, []);
|
||||
}
|
||||
@@ -1559,6 +1615,19 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_ENCHANT_DESCRIPTION, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_error_aliasConflict(Translatable|string $alias, Translatable|string $commandIdList) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_ERROR_ALIASCONFLICT, [
|
||||
"alias" => $alias,
|
||||
"commandIdList" => $commandIdList,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_error_aliasConflictTip(Translatable|string $cmdAliasCommand) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_ERROR_ALIASCONFLICTTIP, [
|
||||
"cmdAliasCommand" => $cmdAliasCommand,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_command_error_permission(Translatable|string $commandName) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_ERROR_PERMISSION, [
|
||||
"commandName" => $commandName,
|
||||
@@ -2227,6 +2296,18 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_CLEAR_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_cmdalias_edit_global() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_CMDALIAS_EDIT_GLOBAL, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_cmdalias_edit_self() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_CMDALIAS_EDIT_SELF, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_cmdalias_list() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_CMDALIAS_LIST, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_permission_command_defaultgamemode() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_DEFAULTGAMEMODE, []);
|
||||
}
|
||||
|
@@ -347,12 +347,24 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_COMMAND_BANLIST_DESCRIPTION = "pocketmine.command.banlist.description";
|
||||
public const POCKETMINE_COMMAND_CLEAR_DESCRIPTION = "pocketmine.command.clear.description";
|
||||
public const POCKETMINE_COMMAND_CLEAR_USAGE = "pocketmine.command.clear.usage";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_CREATE_SUCCESS = "pocketmine.command.cmdalias.create.success";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_DELETE_NOTFOUND = "pocketmine.command.cmdalias.delete.notFound";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_DELETE_SUCCESS = "pocketmine.command.cmdalias.delete.success";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_DESCRIPTION = "pocketmine.command.cmdalias.description";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_LIST_CONFLICTED = "pocketmine.command.cmdalias.list.conflicted";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_LIST_NONESET = "pocketmine.command.cmdalias.list.noneSet";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_LIST_NORMAL = "pocketmine.command.cmdalias.list.normal";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_SCOPE_GLOBAL = "pocketmine.command.cmdalias.scope.global";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_SCOPE_USERSPECIFIC = "pocketmine.command.cmdalias.scope.userSpecific";
|
||||
public const POCKETMINE_COMMAND_CMDALIAS_TEMPLATE = "pocketmine.command.cmdalias.template";
|
||||
public const POCKETMINE_COMMAND_DEFAULTGAMEMODE_DESCRIPTION = "pocketmine.command.defaultgamemode.description";
|
||||
public const POCKETMINE_COMMAND_DEOP_DESCRIPTION = "pocketmine.command.deop.description";
|
||||
public const POCKETMINE_COMMAND_DIFFICULTY_DESCRIPTION = "pocketmine.command.difficulty.description";
|
||||
public const POCKETMINE_COMMAND_DUMPMEMORY_DESCRIPTION = "pocketmine.command.dumpmemory.description";
|
||||
public const POCKETMINE_COMMAND_EFFECT_DESCRIPTION = "pocketmine.command.effect.description";
|
||||
public const POCKETMINE_COMMAND_ENCHANT_DESCRIPTION = "pocketmine.command.enchant.description";
|
||||
public const POCKETMINE_COMMAND_ERROR_ALIASCONFLICT = "pocketmine.command.error.aliasConflict";
|
||||
public const POCKETMINE_COMMAND_ERROR_ALIASCONFLICTTIP = "pocketmine.command.error.aliasConflictTip";
|
||||
public const POCKETMINE_COMMAND_ERROR_PERMISSION = "pocketmine.command.error.permission";
|
||||
public const POCKETMINE_COMMAND_ERROR_PLAYERNOTFOUND = "pocketmine.command.error.playerNotFound";
|
||||
public const POCKETMINE_COMMAND_EXCEPTION = "pocketmine.command.exception";
|
||||
@@ -489,6 +501,9 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_PERMISSION_COMMAND_BAN_PLAYER = "pocketmine.permission.command.ban.player";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_CLEAR_OTHER = "pocketmine.permission.command.clear.other";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_CLEAR_SELF = "pocketmine.permission.command.clear.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_CMDALIAS_EDIT_GLOBAL = "pocketmine.permission.command.cmdalias.edit.global";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_CMDALIAS_EDIT_SELF = "pocketmine.permission.command.cmdalias.edit.self";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_CMDALIAS_LIST = "pocketmine.permission.command.cmdalias.list";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_DEFAULTGAMEMODE = "pocketmine.permission.command.defaultgamemode";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_DIFFICULTY = "pocketmine.permission.command.difficulty";
|
||||
public const POCKETMINE_PERMISSION_COMMAND_DUMPMEMORY = "pocketmine.permission.command.dumpmemory";
|
||||
|
@@ -117,13 +117,14 @@ use pocketmine\world\format\io\GlobalItemDataHandlers;
|
||||
use pocketmine\world\Position;
|
||||
use pocketmine\world\World;
|
||||
use pocketmine\YmlServerProperties;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function base64_encode;
|
||||
use function bin2hex;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
use function json_encode;
|
||||
use function ord;
|
||||
@@ -1095,21 +1096,24 @@ class NetworkSession{
|
||||
|
||||
public function syncAvailableCommands() : void{
|
||||
$commandData = [];
|
||||
foreach($this->server->getCommandMap()->getCommands() as $command){
|
||||
if(isset($commandData[$command->getLabel()]) || $command->getLabel() === "help" || !$command->testPermissionSilent($this->player)){
|
||||
$globalAliasMap = $this->server->getCommandMap()->getAliasMap();
|
||||
$userAliasMap = $this->player->getCommandAliasMap();
|
||||
foreach($this->server->getCommandMap()->getUniqueCommands() as $command){
|
||||
if(!$command->testPermissionSilent($this->player)){
|
||||
continue;
|
||||
}
|
||||
|
||||
$lname = strtolower($command->getLabel());
|
||||
$aliases = $command->getAliases();
|
||||
$aliasObj = null;
|
||||
if(count($aliases) > 0){
|
||||
if(!in_array($lname, $aliases, true)){
|
||||
//work around a client bug which makes the original name not show when aliases are used
|
||||
$aliases[] = $lname;
|
||||
}
|
||||
$aliasObj = new CommandEnum(ucfirst($command->getLabel()) . "Aliases", $aliases);
|
||||
$userAliases = $userAliasMap->getMergedAliases($command->getId(), $globalAliasMap);
|
||||
//the client doesn't like it when we override /help
|
||||
$aliases = array_values(array_filter($userAliases, fn(string $alias) => $alias !== "help" && $alias !== "?"));
|
||||
if(count($aliases) === 0){
|
||||
continue;
|
||||
}
|
||||
$firstNetworkAlias = $aliases[0];
|
||||
//use filtered aliases for command name discovery - this allows /help to still be shown as /pocketmine:help
|
||||
//on the client without conflicting with the client's built-in /help command
|
||||
$lname = strtolower($firstNetworkAlias);
|
||||
$aliasObj = new CommandEnum(ucfirst($firstNetworkAlias) . "Aliases", $aliases);
|
||||
|
||||
$description = $command->getDescription();
|
||||
$data = new CommandData(
|
||||
@@ -1124,7 +1128,7 @@ class NetworkSession{
|
||||
chainedSubCommandData: []
|
||||
);
|
||||
|
||||
$commandData[$command->getLabel()] = $data;
|
||||
$commandData[] = $data;
|
||||
}
|
||||
|
||||
$this->sendDataPacket(AvailableCommandsPacket::create($commandData, [], [], []));
|
||||
|
@@ -29,6 +29,9 @@ final class DefaultPermissionNames{
|
||||
public const COMMAND_BAN_IP = "pocketmine.command.ban.ip";
|
||||
public const COMMAND_BAN_LIST = "pocketmine.command.ban.list";
|
||||
public const COMMAND_BAN_PLAYER = "pocketmine.command.ban.player";
|
||||
public const COMMAND_CMDALIAS_LIST = "pocketmine.command.cmdalias.list";
|
||||
public const COMMAND_CMDALIAS_EDIT_SELF = "pocketmine.command.cmdalias.edit.self";
|
||||
public const COMMAND_CMDALIAS_EDIT_GLOBAL = "pocketmine.command.cmdalias.edit.global";
|
||||
public const COMMAND_CLEAR_OTHER = "pocketmine.command.clear.other";
|
||||
public const COMMAND_CLEAR_SELF = "pocketmine.command.clear.self";
|
||||
public const COMMAND_DEFAULTGAMEMODE = "pocketmine.command.defaultgamemode";
|
||||
|
@@ -59,6 +59,9 @@ abstract class DefaultPermissions{
|
||||
self::registerPermission(new Permission(Names::COMMAND_BAN_PLAYER, l10n::pocketmine_permission_command_ban_player()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_CLEAR_OTHER, l10n::pocketmine_permission_command_clear_other()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_CLEAR_SELF, l10n::pocketmine_permission_command_clear_self()), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_CMDALIAS_LIST, l10n::pocketmine_permission_command_cmdalias_list()), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_CMDALIAS_EDIT_SELF, l10n::pocketmine_permission_command_cmdalias_edit_self()), [$everyoneRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_CMDALIAS_EDIT_GLOBAL, l10n::pocketmine_permission_command_cmdalias_edit_global()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_DEFAULTGAMEMODE, l10n::pocketmine_permission_command_defaultgamemode()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_DIFFICULTY, l10n::pocketmine_permission_command_difficulty()), [$operatorRoot]);
|
||||
self::registerPermission(new Permission(Names::COMMAND_DUMPMEMORY, l10n::pocketmine_permission_command_dumpmemory()), [$consoleRoot]);
|
||||
|
@@ -30,6 +30,7 @@ use pocketmine\block\BlockTypeTags;
|
||||
use pocketmine\block\RespawnAnchor;
|
||||
use pocketmine\block\UnknownBlock;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\command\CommandAliasMap;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\crafting\CraftingGrid;
|
||||
use pocketmine\data\java\GameModeIdMap;
|
||||
@@ -296,6 +297,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer, Nev
|
||||
|
||||
/** @phpstan-var positive-int|null */
|
||||
protected ?int $lineHeight = null;
|
||||
private CommandAliasMap $commandAliasMap;
|
||||
|
||||
protected string $locale = "en_US";
|
||||
|
||||
protected int $startAction = -1;
|
||||
@@ -339,6 +342,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer, Nev
|
||||
$rootPermissions[DefaultPermissions::ROOT_OPERATOR] = true;
|
||||
}
|
||||
$this->perm = new PermissibleBase($rootPermissions);
|
||||
$this->commandAliasMap = new CommandAliasMap();
|
||||
|
||||
$this->chunksPerTick = $this->server->getConfigGroup()->getPropertyInt(YmlServerProperties::CHUNK_SENDING_PER_TICK, 4);
|
||||
$this->spawnThreshold = (int) (($this->server->getConfigGroup()->getPropertyInt(YmlServerProperties::CHUNK_SENDING_SPAWN_RADIUS, 4) ** 2) * M_PI);
|
||||
$this->chunkSelector = new ChunkSelector();
|
||||
@@ -598,6 +603,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer, Nev
|
||||
$this->lineHeight = $height;
|
||||
}
|
||||
|
||||
public function getCommandAliasMap() : CommandAliasMap{ return $this->commandAliasMap; }
|
||||
|
||||
public function canSee(Player $player) : bool{
|
||||
return !isset($this->hiddenPlayers[$player->getUniqueId()->getBytes()]);
|
||||
}
|
||||
|
@@ -34,7 +34,6 @@ use pocketmine\utils\Config;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function copy;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function file_exists;
|
||||
use function fopen;
|
||||
@@ -144,23 +143,12 @@ abstract class PluginBase implements Plugin, CommandExecutor{
|
||||
* Registers commands declared in the plugin manifest
|
||||
*/
|
||||
private function registerYamlCommands() : void{
|
||||
$pluginCmds = [];
|
||||
|
||||
foreach(Utils::stringifyKeys($this->description->getCommands()) as $key => $data){
|
||||
if(str_contains($key, ":")){
|
||||
$this->logger->error($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_plugin_commandError($key, $this->description->getFullName(), ":")));
|
||||
continue;
|
||||
}
|
||||
|
||||
$newCmd = new PluginCommand($key, $this, $this);
|
||||
if(($description = $data->getDescription()) !== null){
|
||||
$newCmd->setDescription($description);
|
||||
}
|
||||
|
||||
if(($usageMessage = $data->getUsageMessage()) !== null){
|
||||
$newCmd->setUsage($usageMessage);
|
||||
}
|
||||
|
||||
$aliasList = [];
|
||||
foreach($data->getAliases() as $alias){
|
||||
if(str_contains($alias, ":")){
|
||||
@@ -170,7 +158,14 @@ abstract class PluginBase implements Plugin, CommandExecutor{
|
||||
$aliasList[] = $alias;
|
||||
}
|
||||
|
||||
$newCmd->setAliases($aliasList);
|
||||
$newCmd = new PluginCommand(
|
||||
$this->description->getName(),
|
||||
$key,
|
||||
$this,
|
||||
$this,
|
||||
$data->getDescription() ?? "",
|
||||
$data->getUsageMessage()
|
||||
);
|
||||
|
||||
$newCmd->setPermission($data->getPermission());
|
||||
|
||||
@@ -178,11 +173,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
|
||||
$newCmd->setPermissionMessage($permissionDeniedMessage);
|
||||
}
|
||||
|
||||
$pluginCmds[] = $newCmd;
|
||||
}
|
||||
|
||||
if(count($pluginCmds) > 0){
|
||||
$this->server->getCommandMap()->registerAll($this->description->getName(), $pluginCmds);
|
||||
$this->server->getCommandMap()->register($newCmd, $aliasList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\utils;
|
||||
|
||||
use pocketmine\command\CommandAliasMap;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\lang\Language;
|
||||
use pocketmine\lang\Translatable;
|
||||
@@ -41,6 +42,8 @@ use const PHP_INT_MAX;
|
||||
final class BroadcastLoggerForwarder implements CommandSender{
|
||||
use PermissibleDelegateTrait;
|
||||
|
||||
private CommandAliasMap $commandAliasMap;
|
||||
|
||||
public function __construct(
|
||||
private Server $server, //annoying useless dependency
|
||||
private \Logger $logger,
|
||||
@@ -48,6 +51,7 @@ final class BroadcastLoggerForwarder implements CommandSender{
|
||||
){
|
||||
//this doesn't need any permissions
|
||||
$this->perm = new PermissibleBase([]);
|
||||
$this->commandAliasMap = new CommandAliasMap();
|
||||
}
|
||||
|
||||
public function getLanguage() : Language{
|
||||
@@ -77,4 +81,6 @@ final class BroadcastLoggerForwarder implements CommandSender{
|
||||
public function setScreenLineHeight(?int $height) : void{
|
||||
//NOOP
|
||||
}
|
||||
|
||||
public function getCommandAliasMap() : CommandAliasMap{ return $this->commandAliasMap; }
|
||||
}
|
||||
|
@@ -696,6 +696,12 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: '#^Cannot call method getCommandAliasMap\(\) on pocketmine\\player\\Player\|null\.$#'
|
||||
identifier: method.nonObject
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/NetworkSession.php
|
||||
|
||||
-
|
||||
message: '#^Cannot call method getLanguage\(\) on pocketmine\\player\\Player\|null\.$#'
|
||||
identifier: method.nonObject
|
||||
|
Reference in New Issue
Block a user