Skip to content

Command API

Полное руководство по Command API: архитектура сабкоманд, аргументы, права, tab-complete, интеграция с сервисами и production-паттерны.

Обновлено: 01 янв. 1980 г.Чтение: ~3 мин

Command API

Command API в NextLib помогает построить команды как модульную систему, а не монолитный if-else в одном классе.

1. Модель модуля

Три ключевых элемента:

  • SubCommand — отдельная команда с собственной логикой.
  • LongCommandExecutor — роутинг по алиасам + проверка permission + tab-complete.
  • CommandRegistry — регистрация в Bukkit-команду из plugin.yml.

2. SubCommand: контракт

public interface SubCommand {
    void onExecute(CommandSender sender, String[] args);
    List<String> onTabComplete(CommandSender sender, String[] args);
}

Что значит args

Для /nextlib reload fast:

  • args[0] = reload
  • args[1] = fast

Следствие: в onExecute всегда проверяйте длину массива до чтения аргументов.

3. LongCommandExecutor: как работает роутинг

Регистрация сабкоманды

addSubCommand(subCommand, new String[]{"reload", "rl"}, new Permission("myplugin.reload"));

Параметры:

  • subCommand: объект бизнес-логики.
  • aliases: список имён, по которым команда найдётся.
  • permission: право для запуска.

Алгоритм onCommand

  1. Нет args[0] -> false.
  2. Поиск wrapper по алиасу.
  3. Нет wrapper -> false.
  4. Нет permission -> отправка permission message.
  5. Делегирование в onExecute.

Алгоритм onTabComplete

  • при args.length == 1 возвращаются первые алиасы всех сабкоманд;
  • дальше делегирование конкретной сабкоманде.

4. CommandRegistry

CommandRegistry registry = new CommandRegistry(plugin);
registry.registerCommand("nextlib", new MainCommandExecutor());

registerCommand безопасно:

  • проверяет наличие команды в plugin.yml,
  • если команды нет, пишет warning вместо падения плагина,
  • выставляет executor/tabCompleter.

5. Полный каркас production-команды

public final class MainCommand extends LongCommandExecutor {
    public MainCommand(MyServices services) {
        addSubCommand(new ReloadSubCommand(services.reloadService()), new String[]{"reload", "rl"}, new Permission("myplugin.command.reload"));
        addSubCommand(new StatsSubCommand(services.statsService()), new String[]{"stats"}, new Permission("myplugin.command.stats"));
        addSubCommand(new HelpSubCommand(), new String[]{"help", "?"}, new Permission("myplugin.command.help"));
    }
}

6. Разбор аргументов: правильная стратегия

Плохой стиль

  • читать args[2] без проверки длины,
  • кидать stacktrace игроку,
  • usage писать в каждом месте разными строками.

Хороший стиль

  1. Явная проверка аргументов.
  2. Центральный usage.
  3. Валидация через Validation API.
if (args.length < 2) {
    sender.sendMessage("Usage: /nextlib reload <soft|hard>");
    return;
}

7. Права и безопасность

Рекомендуемая схема permission-нейминга:

  • plugin.command.help
  • plugin.command.reload
  • plugin.command.admin.*

Для CommandSender:

if (!(sender instanceof Player player)) {
    sender.sendMessage("Only players can run this command");
    return;
}

Не assume, что отправитель всегда игрок.

8. Tab-complete как часть UX

Базовый фильтр по префиксу

String prefix = args[1].toLowerCase(Locale.ROOT);
return List.of("soft", "hard").stream()
    .filter(v -> v.startsWith(prefix))
    .toList();

Что учитывать в реальных проектах

  • permission пользователя,
  • контекст (онлайн игроки, доступные миры, id квестов),
  • производительность (не делать тяжёлых запросов).

9. Интеграция с Reload/Observability

ReloadReport report = reloadManager.reloadAll();
metrics.increment("command.reload.executed");
logger.info("reload_command", Map.of("ok", report.successful(), "fail", report.failed()));

Команда становится операционным инструментом, а не «чёрным ящиком».

10. Ошибки и обработка

Распространённые сбои

  • команда не зарегистрирована в plugin.yml;
  • опечатка в имени команды при registerCommand;
  • сабкоманда не добавлена в executor;
  • неверный permission.

Диагностика

  1. Проверить startup-логи.
  2. Проверить вывод /help и права.
  3. Проверить tab-complete видимость.

11. Дизайн больших наборов команд

Паттерн A: Package-by-domain

  • command/admin/*
  • command/player/*
  • command/economy/*

Паттерн B: Thin command layer

Командный слой только валидирует и оркестрирует.

Бизнес-логика в сервисах:

  • EconomyService
  • QuestService
  • ProfileService

Паттерн C: I18n-first

Все сообщения через i18n-ключи.

12. Антипаттерны

  • Огромный switch в одном классе на 500 строк.
  • Бизнес-логика и SQL прямо в onExecute.
  • Таб-комплит без фильтрации и прав.
  • Отсутствие usage/help для пользователя.

13. Большой FAQ

Что возвращать из onCommand: true или false?

false обычно означает «неверное использование», чтобы Bukkit мог показать usage. true означает, что команда обработана.

Можно ли делать async внутри команды?

Да, для тяжёлых задач (например БД/сеть), но UI/игровые действия возвращайте на main thread.

Как красиво сделать /plugin help?

Соберите список доступных сабкоманд на основе permission и отдавайте только то, что игрок реально может использовать.

14. Чеклист командного слоя

  • Все команды есть в plugin.yml.
  • Все сабкоманды имеют usage.
  • Все аргументы валидируются.
  • Все ошибки понятны пользователю.
  • Метрики команд собираются.
  • Критичные действия логируются.

С такой дисциплиной Command API остаётся управляемым даже при десятках сабкоманд.