Создание команд 26.1.2
Создавайте команды со сложными аргументами и действиями.
Создание команд позволяет разработчику мода добавлять функционал, который может быть использован при вызове команд. Это руководство научит вас регистрировать команды и общую структуру команд Brigadier.
INFO
Brigadier — парсер и диспетчер команд с открытым исходным кодом, написанный Mojang для Minecraft. Это библиотека для команд, с которой вы создаёте дерево команд и аргументов.
Интерфейс Command
com.mojang.brigadier.Command — это функциональный интерфейс, который запускает конкретный код и вызывает ошибку CommandSyntaxException в определённых случаях. Он имеет аргумент типа S, который определяет тип источника команды. Источник команды предоставляет контекст, в котором была запущена команда. В Minecraft, источником команды чаще всего является CommandSourceStack, который может представлять сервер, командный блок, удалённое соединение (RCON), игрока или сущность.
Единственный метод в Command, run(CommandContext<S>), принимает CommandContext<S> в качестве единственного аргумента и возвращает целое число. Контекст команды содержит источник вашей команды как S и позволяет получить аргументы, посмотреть на спарсенные узлы команды и увидеть вводные данные, используемые в этой команде.
Как и другие функциональные интерфейсы, этот обычно используется как лямбда или ссылка на метод:
java
Command<CommandSourceStack> command = context -> {
return 0;
};1
2
3
2
3
Целое число можно считать результатом команды. Обычно значения меньше или равные нулю означают, что команда не выполнена и ничего не сделает. Положительные значения означают, что команда успешно выполнилась и что-то выполнила. Brigadier предоставляет константу для обозначения успеха: Command#SINGLE_SUCCESS.
Что может делать CommandSourceStack?
CommandSourceStack предоставляет дополнительный контекст при выполнении команды. Это включает в себя возможность получить сущность, выполнившую команду, мир, в котором была запущена команда, или сервер, на котором была запущена команда.
Вы можете получить источник команды из командного контекста при вызове getSource() в экземпляре CommandContext.
java
Command<CommandSourceStack> command = context -> {
CommandSourceStack source = context.getSource();
return 0;
};1
2
3
4
2
3
4
Регистрация простейшей команды
Команды регистрируются внутри CommandRegistrationCallback, предоставляемом Fabric API.
INFO
Для информации о регистрации обратных вызовов прочитайте руководство по событиям.
Событие должно быть зарегистрировано в вашем инициализаторе мода.
Обратный вызов имеет три параметра:
CommandDispatcher<CommandSourceStack> dispatcher— Используется для регистрации, парсинга и выполнения команд.S— это тип источника команд, поддерживаемый диспетчером команд.CommandBuildContext registryAccess— Предоставляет абстракцию реестров, которую можно передать некоторым методам аргументов командыCommands.CommandSelection environment— Определяет тип сервера, на котором регистрируются команды.
В инициализаторе мода мы просто регистрируем простую команду:
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("test_command").executes(context -> {
context.getSource().sendSuccess(() -> Component.literal("Called /test_command."), false);
return 1;
}));
});1
2
3
4
5
6
2
3
4
5
6
В методеsendSuccess() первый аргумент — это текст для отправки, который является Supplier<Component> во избежание создания экземпляров Component вне необходимости.
Второй параметр определяет, следует ли транслировать обратную связь другим модераторам. Чаще всего, если команда предназначена для запроса чего-либо без затрагивания мира, например, запросить текущее время или счёт игрока, он должен быть false. Если команда делает что-либо, например, изменение времени или изменение чьего-либо счёта, он должно быть true.
Если команда не выполняется, вместо вызова sendFeedback() вы можете напрямую выбросить любое исключение, и сервер или клиент справится с этим соответствующим образом.
CommandSyntaxException обычно выбрасывается для обозначения синтаксических ошибок в командах или в аргументах. Вы можете также реализовать своё исключение.
Чтобы выполнить эту команду, вы должны ввести /test_command с учётом регистра.
INFO
С этого момента мы будем извлекать логику, написанную в лямбде, передаваемой в сборщики .executes(), в отдельные методы. После этого мы можем передать ссылку на метод в .executes(). Это сделано для ясности.
Среда регистрации
При желании вы также можете сделать так, чтобы команда регистрировалась только при определённых условиях, например, только в среде выделенного сервера:
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
if (environment.includeDedicated) {
dispatcher.register(Commands.literal("dedicated_command")
.executes(ExampleModCommands::executeDedicatedCommand));
}
});1
2
3
4
5
6
2
3
4
5
6
java
private static int executeDedicatedCommand(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /dedicated_command."), false);
return 1;
}1
2
3
4
2
3
4
Требования к команде
Допустим, что у вас есть команда и вы хотите, чтобы её могли выполнять только модераторы. В этом случае можно использовать метод requires(). Метод requires() имеет один аргумент Predicate<S>, который будет предоставлять CommandSourceStack для проверки возможности CommandSource выполнить команду.
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("required_command")
.requires(source -> source.permissions().hasPermission(Permissions.COMMANDS_MODERATOR))
.executes(ExampleModCommands::executeRequiredCommand));
});1
2
3
4
5
2
3
4
5
java
private static int executeRequiredCommand(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /required_command."), false);
return 1;
}1
2
3
4
2
3
4
Эта команда выполнится только в том случае, если источник команды является как минимум модератором, что также включает командные блоки. В противном случае команда не зарегистрируется.
Побочным эффектом этого является то, что эта команда не показывается в Tab-автозаполнении для всех игроков, не являющихся модераторами. Это также объясняет, почему вы не можете Tab-дополнить большинство команд, когда не включены читы.
Подкоманды
Чтобы добавить подкоманду, первый литеральный узел команды регистрируется как обычно. Чтобы получить подкоманду, вы должны добавить следующий литеральный узел к уже существующему узлу.
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("command_one")
.then(Commands.literal("sub_command_one").executes(ExampleModCommands::executeSubCommandOne)));
});1
2
3
4
2
3
4
java
private static int executeSubCommandOne(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /command sub_command_one."), false);
return 1;
}1
2
3
4
2
3
4
Подобно аргументам, узлы подкоманд могут также быть необязательными. В следующем случае будут допустимы как /command_two, так и /command_two sub_command_two.
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("command_two")
.executes(ExampleModCommands::executeCommandTwo)
.then(Commands.literal("sub_command_two").executes(ExampleModCommands::executeSubCommandTwo)));
});1
2
3
4
5
2
3
4
5
java
private static int executeCommandTwo(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /command_two."), false);
return 1;
}
private static int executeSubCommandTwo(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /sub_command_two."), false);
return 1;
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Клиентские команды
Аналогично, Fabric API предоставляет событие ClientCommandRegistrationCallback в пакете net.fabricmc.fabric.api.cient.command.v2, которое может быть использовано для регистрации клиентских команд, заменяя ванильный класс Commands на эквивалентный ему ClientCommands. Этот код должен существовать только на стороне клиента.
java
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(ClientCommands.literal("clienttater").executes(context -> {
context.getSource().sendFeedback(Component.literal("Called /clienttater with no arguments."));
return 1;
}));
});1
2
3
4
5
6
2
3
4
5
6
Перенаправления команд
Перенаправления команд, также известное как псевдонимы — это способ перенаправить функционал одной команды к другой. Это полезно, если вы хотите изменить название команды, но всё равно хотите поддерживать старое название.
WARNING
Brigadier будет перенаправлять только командные узлы с аргументами. Если вы хотите перенаправить командный узел без аргументов, предоставьте конструктор .executes() со ссылкой на ту же логику, что описана в примере.
java
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
var redirectedBy = dispatcher.register(Commands.literal("redirected_by").executes(ExampleModCommands::executeRedirectedBy));
dispatcher.register(Commands.literal("to_redirect").executes(ExampleModCommands::executeRedirectedBy).redirect(redirectedBy));
});1
2
3
4
2
3
4
java
private static int executeRedirectedBy(CommandContext<CommandSourceStack> context) {
context.getSource().sendSuccess(() -> Component.literal("Called /redirected_by."), false);
return 1;
}1
2
3
4
2
3
4
ЧАВО
Почему мой код не компилируется?
Словите или выбросите
CommandSyntaxException—CommandSyntaxExceptionне являетсяRuntimeException. Если вы его выбросите, то это должно быть сделано в методах, в сигнатурах которых указано выбрасываниеCommandSyntaxException, или его нужно будет поймать. Brigadier обработает проверяемые исключения и отправит вам сообщение об ошибке в игре.Проблемы с параметрами типов — иногда у вас могут возникать проблемы с параметрами типов. Если вы регистрируете серверные команды (в большинстве случаев), убедитесь, что вы используете
Commands.literalилиCommands.argumentвместоLiteralArgumentBuilder.literalилиRequiredArgumentBuilder.argument.Проверьте метод
sendFeedback()— возможно, вы забыли указать логическое значение как второй аргумент. Также помните, что, начиная с версии Minecraft 1.20, первым аргументом должен бытьSupplier<Component>вместоComponent.Команда должна возвращать целое число — при регистрации команд метод
executes()принимает объектCommand, который обычно является лямбдой. Лямбда должна возвращать целое число, а не какой-то другой тип.
Могу ли я зарегистрировать команды во время игры?
WARNING
Это сделать возможно, но не рекомендуется. Вам бы потребовалось получить Commands с сервера и добавить что угодно, связанное с командами, в его CommandDispatcher.
После этого вам нужно снова отправить дерево команд всем игрокам с помощью Commands.sendCommands(ServerPlayer).
Это необходимо, поскольку клиент локально кэширует дерево команд, получаемую во время входа (или когда отправляются пакеты модераторов) для локальных сообщений об ошибках с автозаполнением.
Могу я отменить регистрацию команд во время игры?
WARNING
Это тоже сделать возможно, но это намного нестабильнее, чем регистрация команд во время игры, и может вызвать нежелательные побочные эффекты.
Проще говоря, вам нужно использовать рефлексию на Brigadier и удалить узлы дерева. После этого вам необходимо снова отправить дерево команд каждому игроку с помощью sendCommands(ServerPlayer).
Если вы не отправите новое дерево команд, клиент может всё ещё думать, что команда существует, хотя сервер и провалит её выполнение.













