Command API
Полное руководство по Command API: архитектура сабкоманд, аргументы, права, tab-complete, интеграция с сервисами и production-паттерны.
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] = reloadargs[1] = fast
Следствие: в onExecute всегда проверяйте длину массива до чтения аргументов.
3. LongCommandExecutor: как работает роутинг
Регистрация сабкоманды
addSubCommand(subCommand, new String[]{"reload", "rl"}, new Permission("myplugin.reload"));
Параметры:
subCommand: объект бизнес-логики.aliases: список имён, по которым команда найдётся.permission: право для запуска.
Алгоритм onCommand
- Нет
args[0]->false. - Поиск wrapper по алиасу.
- Нет wrapper ->
false. - Нет permission -> отправка permission message.
- Делегирование в
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 писать в каждом месте разными строками.
Хороший стиль
- Явная проверка аргументов.
- Центральный usage.
- Валидация через Validation API.
if (args.length < 2) {
sender.sendMessage("Usage: /nextlib reload <soft|hard>");
return;
}
7. Права и безопасность
Рекомендуемая схема permission-нейминга:
plugin.command.helpplugin.command.reloadplugin.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.
Диагностика
- Проверить startup-логи.
- Проверить вывод
/helpи права. - Проверить tab-complete видимость.
11. Дизайн больших наборов команд
Паттерн A: Package-by-domain
command/admin/*command/player/*command/economy/*
Паттерн B: Thin command layer
Командный слой только валидирует и оркестрирует.
Бизнес-логика в сервисах:
EconomyServiceQuestServiceProfileService
Паттерн 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 остаётся управляемым даже при десятках сабкоманд.