Руководство по командам
В Nukkit-MOT доступно более одного API команд. Правильный выбор зависит от того, нужна ли вам только стандартная команда плагина или же типизированный разбор, перегрузки и селекторы.
Эта страница написана по текущему исходному коду Nukkit-MOT, в особенности по PluginManager, PluginCommand, PluginBase, SimpleCommandMap, Command, ParamTree и EntitySelectorAPI.
Выбор способа регистрации
| Способ | Подходит для | Точка регистрации |
|---|---|---|
plugin.yml + onCommand(...) | Стандартные команды плагина | plugin.yml |
plugin.yml + setExecutor(...) | Вынос логики команд из основного класса плагина | plugin.yml + onEnable() |
registerSimpleCommands(this) | Небольшие служебные или административные команды с «сырыми» строками | onEnable() |
Пользовательская Command + enableParamTree() | Типизированный разбор, перегрузки и селекторы целей | onEnable() |
1. plugin.yml + onCommand(...)
Во время загрузки плагина PluginManager.parseYamlCommands() читает секцию commands и создаёт PluginCommand для каждой записи. Исполнителем по умолчанию у PluginCommand является сам плагин, поэтому обычной отправной точкой будет переопределение onCommand(...) в вашем PluginBase.
commands:
hello:
description: Send a hello message
usage: "/hello <name>"
aliases: ["hi"]
permission: example.command.hello
permission-message: "You need <permission> to use /hello"
package com.example.commanddemo;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandSender;
import cn.nukkit.plugin.PluginBase;
public final class CommandDemoPlugin extends PluginBase {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!command.getName().equalsIgnoreCase("hello")) {
return false;
}
if (args.length != 1) {
return false;
}
sender.sendMessage("Hello, " + args[0] + "!");
return true;
}
}
Если onCommand(...) возвращает false и у команды задан непустой usage, Nukkit-MOT автоматически отправляет сообщение об использовании.
2. Вынос логики в отдельный CommandExecutor
Если у вашего плагина несколько команд, сохраняйте основной класс компактным и подключайте исполнитель в onEnable(). PluginBase.getCommand(name) возвращает только команды, объявленные в plugin.yml текущего плагина.
package com.example.commanddemo;
import cn.nukkit.command.PluginCommand;
import cn.nukkit.plugin.PluginBase;
public final class CommandDemoPlugin extends PluginBase {
@Override
@SuppressWarnings("unchecked")
public void onEnable() {
PluginCommand<CommandDemoPlugin> hello = (PluginCommand<CommandDemoPlugin>) this.getCommand("hello");
if (hello == null) {
throw new IllegalStateException("Command 'hello' is missing from plugin.yml");
}
hello.setExecutor(new HelloCommandExecutor());
}
}
package com.example.commanddemo;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandExecutor;
import cn.nukkit.command.CommandSender;
public final class HelloCommandExecutor implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length != 1) {
return false;
}
sender.sendMessage("Hello, " + args[0] + "!");
return true;
}
}
Этот способ по-прежнему использует метаданные plugin.yml для описания, псевдонимов, права и использования.
3. Простые команды на основе аннотаций
SimpleCommandMap.registerSimpleCommands(Object object) сканирует методы, помеченные аннотацией @cn.nukkit.command.simple.Command, и регистрирует их как экземпляры SimpleCommand. Этот путь не зависит от plugin.yml.
package com.example.commanddemo;
import cn.nukkit.Player;
import cn.nukkit.command.CommandSender;
import cn.nukkit.command.data.CommandParamType;
import cn.nukkit.command.simple.Arguments;
import cn.nukkit.command.simple.CommandParameters;
import cn.nukkit.command.simple.CommandPermission;
import cn.nukkit.command.simple.ForbidConsole;
import cn.nukkit.command.simple.Parameter;
import cn.nukkit.command.simple.Parameters;
import cn.nukkit.plugin.PluginBase;
public final class CommandDemoPlugin extends PluginBase {
@Override
public void onEnable() {
this.getServer().getCommandMap().registerSimpleCommands(this);
}
@cn.nukkit.command.simple.Command(
name = "whereis",
description = "Show a player's position",
usageMessage = "/whereis <player>",
aliases = {"locateplayer"}
)
@Arguments(min = 1, max = 1)
@CommandPermission("example.command.whereis")
@ForbidConsole
@CommandParameters(parameters = {
@Parameters(name = "default", parameters = {
@Parameter(name = "target", type = CommandParamType.TARGET)
})
})
public boolean whereIsCommand(CommandSender sender, String commandLabel, String[] args) {
Player target = this.getServer().getPlayerExact(args[0]);
if (target == null) {
sender.sendMessage("Player not found.");
return true;
}
sender.sendMessage(
target.getName() + " is at "
+ target.getFloorX() + ", "
+ target.getFloorY() + ", "
+ target.getFloorZ()
);
return true;
}
}
Примечания:
- Сигнатура метода должна быть
boolean method(CommandSender sender, String commandLabel, String[] args). @Arguments,@CommandPermissionи@ForbidConsoleприменяютсяSimpleCommand.@CommandParametersлишь заполняет метаданные команды. Метод по-прежнему получает «сырой»String[] args; он не включает автоматическиParamTreeили типизированный разбор на стороне сервера.- Избегайте повторного использования одного и того же имени команды в
plugin.ymlиregisterSimpleCommands(...), если вы намеренно не добиваетесь конфликта.
4. Пользовательская Command с ParamTree
Используйте настоящий подкласс Command, когда вам нужны:
- типизированные параметры
- несколько перегрузок
- селекторы целей, такие как
@aи@p - вывод команды через
CommandLogger
plugin.yml и в вручную регистрируемой пользовательской CommandКоманды из plugin.yml превращаются в экземпляры PluginCommand методом PluginManager.parseYamlCommands() во время загрузки плагина, а затем регистрируются через SimpleCommandMap.registerAll(...). Пользовательская Command, которую вы регистрируете позже в onEnable(), всё равно попадает в тот же SimpleCommandMap.knownCommands.
Логика в исходном коде SimpleCommandMap.registerAlias(...) — это не простое правило «побеждает последняя регистрация»:
- Если вручную зарегистрированная основная метка конфликтует с существующей основной меткой, новая команда не получает «чистое» имя команды и откатывается к
fallbackPrefix:command - Если вручную зарегистрированная основная метка конфликтует с существующим псевдонимом, запись «чистого» псевдонима перезаписывается через
knownCommands.put(label, command). Кроме того,registerAlias(...)сначала записываетfallbackPrefix:label, поэтому при участии того же префикса откатывания запись псевдонима с префиксом также может быть перехвачена
Поэтому проблема не сводится к простому «переопределению». Обычно происходит разделение записей команд: когда основная метка конфликтует с другой основной меткой, «чистое» имя команды, как правило, по-прежнему указывает на старый PluginCommand; когда основная метка конфликтует с псевдонимом, эта запись псевдонима может быть перепривязана к новому объекту команды. Если вы пишете настоящую пользовательскую Command, особенно с ParamTree, не объявляйте также ту же запись в plugin.yml.
Зарегистрируйте команду вручную в onEnable():
package com.example.commanddemo;
import cn.nukkit.plugin.PluginBase;
public final class CommandDemoPlugin extends PluginBase {
@Override
public void onEnable() {
this.getServer().getCommandMap().register("commanddemo", new TargetInfoCommand());
}
}
Затем определите перегрузки с помощью commandParameters и вызовите enableParamTree() после готовности карты команд:
package com.example.commanddemo;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandSender;
import cn.nukkit.command.data.CommandParamType;
import cn.nukkit.command.data.CommandParameter;
import cn.nukkit.command.tree.ParamList;
import cn.nukkit.command.utils.CommandLogger;
import cn.nukkit.entity.Entity;
import java.util.List;
import java.util.Map;
public final class TargetInfoCommand extends Command {
public TargetInfoCommand() {
super("targetinfo", "Inspect the target selector result", "/targetinfo <target>");
this.setPermission("example.command.targetinfo");
this.commandParameters.clear();
this.commandParameters.put("default", new CommandParameter[]{
CommandParameter.newType("target", CommandParamType.TARGET)
});
this.enableParamTree();
}
@Override
public int execute(CommandSender sender, String commandLabel, Map.Entry<String, ParamList> result, CommandLogger log) {
List<Entity> targets = result.getValue().getResult(0);
if (targets.isEmpty()) {
log.addNoTargetMatch().output();
return 0;
}
Entity first = targets.get(0);
log.addSuccess(
"Matched " + targets.size() + " target(s). First: "
+ first.getName() + " @ "
+ first.getFloorX() + ", "
+ first.getFloorY() + ", "
+ first.getFloorZ()
).output();
return targets.size();
}
}
Важные правила:
- Вызывайте
enableParamTree()только после полной настройкиcommandParameters. - Когда у команды есть дерево параметров, диспетчеризация больше не вызывает
execute(CommandSender, String, String[]). Вместо этого переопределитеexecute(CommandSender, String, Map.Entry<String, ParamList>, CommandLogger). - Если вы включите дерево параметров, но не переопределите новый
execute(...), команда завершится ошибкой, а сервер запишет ошибку в журнал.
Селекторы, поддерживаемые CommandParamType.TARGET
Когда перегрузка использует связанные с целями узлы параметров, текущий EntitySelectorAPI принимает:
- селекторы:
@a,@e,@p,@r,@s,@initiator - общие аргументы:
x,y,z,dx,dy,dz,c,r,rm,name,tag,l,lm,m,type,rx,rxm,ry,rym,scores
Это означает, что команда вида /targetinfo @a[tag=builder,c=3] может быть разобрана той же системой селекторов, которую используют встроенные команды.
Частые ошибки
- Секция
commands:вplugin.ymlлишь регистрирует метаданные и объектыPluginCommand. Она сама по себе не реализует логику вашей команды. this.getCommand("name") == nullобычно означает, что команда отсутствует вplugin.ymlвашего плагина или принадлежит другому плагину.- Команды из
plugin.yml,registerSimpleCommands(...)и ручные вызовыregister(...)в итоге попадают в один и тот жеSimpleCommandMap. При конфликте основной метки или псевдонима «чистое» имя команды может остаться занятым или запись псевдонима может быть перезаписана, поэтому выполнится не та команда, которую вы ожидали. - Возврат
false— не универсальная стратегия обработки ошибок. В основном он означает «показать использование, если оно доступно». - Простые команды на основе аннотаций удобны, но это по-прежнему команды с «сырыми» аргументами. Для настоящего типизированного разбора используйте пользовательскую
Commandвместе сParamTree.