Перейти к основному содержимому

Руководство по командам

В 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.

plugin.yml
commands:
hello:
description: Send a hello message
usage: "/hello <name>"
aliases: ["hi"]
permission: example.command.hello
permission-message: "You need <permission> to use /hello"
CommandDemoPlugin.java
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 текущего плагина.

CommandDemoPlugin.java
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());
}
}
HelloCommandExecutor.java
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.

CommandDemoPlugin.java
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():

CommandDemoPlugin.java
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() после готовности карты команд:

TargetInfoCommand.java
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.