User Tools

Site Tools


ru:tutorial:commands

This is an old revision of the document!


Создание команды

Создание команд позволяет разработчику наращивать функционал и способ воздействия с пользователем через, собственно, команды. Это руководство расскажет как регистрировать команды и общую информацию о структуре Brigadier.

Примечание: Весь код, описанный здесь, написан для 1.14.4. Что-то может быть изменено в yarn, но большая часть кода всё ещё применима и там.

Что такое Brigadier?

Brigadier - исполнитель и обработчик команд, написаный Mojang для Minecraft. Brigadier это древоподобная библиотека, с помощью которой вы можете создавать дерево из команд и аргументов.

С исходным кодом Brigadier вы можете ознакомиться тут: https://github.com/Mojang/brigadier

Что такое "команда"?

Brigadier требует описание команды чтобы её исполнять. На самом деле, “команда” это довольно неточный термин, если мы говорим о Brigadier, но обычно он означает конечную точку для команды, т.е. то место, где исполняется ваш код. Command - это функциональный интерфейс. Команда имеет подтип S, где S - это источник команды. Источник команды может предоставить некий “контекст” для неё. В Minecraft обычно используется ServerCommandSource, что представляет сервер, командный блок, соединение RCON, игрока или сущность.

Единственный метод в Command - run(CommandContext<S>) принимает один параметр CommandContext<S> и возвращает целое число. Контекст команды (CommandContext) типа S содержит в себе S, позволяет увидеть используемые аргументы, работать с обработанными аргументами и посмотреть на непосредственный ввод пользователя.

Вы можете создать команду несколькими образами, которые написаны ниже:

Как лямбда-выражение

  1. Command<Object> command = context -> {
  2. return 0;
  3. };

Как анонимный класс

  1. Command<Object> command = new Command<Object>() {
  2. @Override
  3. public int run(CommandContext<Object> context) {
  4. return 0;
  5. }
  6. }

Как часть класса

  1. final class XYZCommand implements Command<Object> {
  2. @Override
  3. public int run(CommandContext<Object> context) {
  4. return 0;
  5. }
  6. }

Как обращение к методу

  1. void registerCommand() {
  2. // Не обращайте внимание, мы объясним про это далее.
  3. dispatcher.register(CommandManager.literal("foo"))
  4. .executes(this::execute); // Это относится к приведённому ниже методу "выполнить".
  5. }
  6.  
  7. private int execute(CommandContext<Object> context) {
  8. return 0;
  9. }

Метод run(CommandContext) может произвести исключение CommandSyntaxException, это будет объяснено далее в руководстве.

Возвращаемое число можно понимать как результат команды. В Minecraft результатом может являться силой сигнала красного камня, который выдаёт командный блок или число, которое будет передано в следующий цепной командный блок. Обычно отрицательное число значит что команда не выполнена и что-то пошло не по плану, 0 что команда прошла, положительные числа - что выполнена и что-то произошло.

Простейшая команда

Ниже вы можете увидеть команду, без аргументов:

  1. dispatcher.register(CommandManager.literal("foo").executes(context -> {
  2. System.out.println("Called foo with no arguments");
  3.  
  4. return 1;
  5. }));

CommandManager.literal(“foo”) даёт brigadier'у понять, что у команды только одна ветвь, это буквальный ввод foo. Чтобы команда была выполнена, нужно ввести /foo. Если введено /Foo, /FoO, /FOO, /fOO или /fooo, команда не будет считаться верной.

Статические импорты

Вы можете вводить CommandManager.literal(“foo”) каждый раз, когда хотите создать литерал. Это работает, но вы можете статически импортировать аргументы и сократить оператор до literal(“foo”). Это также работает для получения значения аргумента. Это сокращает StringArgumentType.getString(ctx, “string”) до getString(ctx, “string”). Это также работает для собственных типов аргументов Minecraft.

И ваш импорт будет выглядеть примерно так:

  1. // getString(ctx, "string")
  2. import static com.mojang.brigadier.arguments.StringArgumentType.getString;
  3. // word()
  4. import static com.mojang.brigadier.arguments.StringArgumentType.word;
  5. // literal("foo")
  6. import static net.minecraft.server.command.CommandManager.literal;
  7. // argument("bar", word())
  8. import static net.minecraft.server.command.CommandManager.argument;
  9. // Импортировать всё
  10. import static net.minecraft.server.command.CommandManager.*;

Примечание: Пожалуйста, убедитесь, что вы используете literal и argument из CommandManager, иначе у вас могут возникнуть проблемы с дженериками при попытке компиляции.

Аргументы Brigadier по умолчанию находятся в com.mojang.brigadier.arguments

Аргументы Minecraft находятся в разделе net.minecraft.command.arguments. CommandManager находится в net.minecraft.server.command

Подкоманда

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

  1. dispatcher.register(CommandManager.literal("foo")

В целом, чтобы получить подкоманду, нужно расширить текущую ветвь следующей, уже существующей ветвью. Это можно сделать с помощью метода then(ArgumentBuilder) который принимает в себя ArgumentBuilder.

То, что показано ниже, создаёт команду foo <bar>:

  1. dispatcher.register(CommandManager.literal("foo")
  2. .then(CommandManager.literal("bar"))
  3. );

Рекомендуется делать отступы в вашем коде при добавлении узлов в команду. Обычно отступ соответствует количеству глубоких узлов в дереве команд. Новая строка также делает видимым, что добавляется еще один узел. Существуют альтернативные стили для форматирования команды дерева, которые будут показаны позже в этом руководстве.

Итак, давайте попробуем выполнить команду

Скорее всего, если вы наберете /foo bar в игре, команда не будет выполнена. Это происходит потому, что нет кода для игры, который выполнялся бы после выполнения всех требуемых аргументов. Чтобы исправить это, вам нужно указать игре, что запускать, когда выполняется команда, используя метод executes(Command). Ниже в качестве примера показано, как должна выглядеть команда:

  1. dispatcher.register(CommandManager.literal("foo")
  2. .then(CommandManager.literal("bar")
  3. .executes(context -> {
  4. System.out.println("Called foo with bar");
  5.  
  6. return 1;
  7. })
  8. )
  9. );

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

Регистрация команд выполняется путем регистрации обратного вызова с помощью CommandRegistrationCallback. Для получения информации о регистрации обратных вызовов, ознакомьтесь с статьёй об обратных вызовах.

Событие должно быть зарегистрировано в инициализаторе вашего мода. Обратный вызов имеет два параметра. CommmandDispatcher<S> используется для регистрации, анализа и выполнения команд. S i- это тип источника команд, который поддерживает диспетчер команд. Второй параметр - это логическое значение, которое определяет тип сервера, на котором регистрируются команды. Это может быть dedicated (выделенный) или integrated(интегрированный) (false) сервер:

  1. public class ExampleCommandMod implements ModInitializer {
  2. @Override
  3. public void onInitialize() {
  4. CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
  5. ...
  6. });
  7. }
  8. }

Внутри вашего лямбда-выражения, ссылки на метод или чего бы вы ни выбрали, зарегистрируете свои команды:

  1. public class ExampleCommandMod implements ModInitializer {
  2. @Override
  3. public void onInitialize() {
  4. CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
  5. dispatcher.register(CommandManager.literal("foo").executes(context -> {
  6. System.out.println("foo");
  7. return 1;
  8. }));
  9. });
  10. }
  11. }

При желании вы можете убедиться, что команда зарегистрирована только на выделенном сервере, установив флажок dedicated(выделенный):

  1. public class ExampleCommandMod implements ModInitializer {
  2. @Override
  3. public void onInitialize() {
  4. CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
  5. if (dedicated) {
  6. TestDedicatedCommand.register(dispatcher);
  7. }
  8. });
  9. }
  10. }

И наоборот:

  1. public class ExampleCommandMod implements ModInitializer {
  2. @Override
  3. public void onInitialize() {
  4. CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
  5. if (!dedicated) {
  6. TestIntegratedCommand.register(dispatcher);
  7. }
  8. });
  9. }
  10. }

Аргументы

Аргументы в Brigadier как анализируют, так и проверяют на ошибки любые введенные аргументы. Minecraft создает некоторые специальные типы аргументов для собственного использования, такие как EntityArgumentType, который представляет внутриигровые селекторы сущностей @a, @r, @p, @e[type=!player, limit=1, distance=..2], или NbtTagArgumentType, который анализирует строковый nbt (snbt) и проверяет правильность ввода синтаксиса.

Будет сделано позже: Подробнее о том, как использовать аргументы.

Дополнительные концепции

Ниже приведены ссылки на статьи о более сложных концепциях, используемых в Brigadier.

Страница Описание
Условия Разрешать пользователям выполнять команды только в определенных сценариях.
Исключения Сбой выполнения команды с описательным сообщением и в определенных контекстах.
Предложения Предложение входных данных для отправки клиенту.
Перенаправления (Разные вариации) Разрешить использование разновидностей команд.
Перенаправления (Цепочки) Разрешить командам иметь повторяющиеся элементы и флаги.
Свои типы аргументов Анализируйте свои аргументы и возвращайте свои типы.

Будет сделано позже: Разделы перемещаются в подкатегории и будут добавлены в соответствующие статьи по мере их переноса.

Часто задаваемые вопросы

Почему моя команда не компилируется?

Есть две непосредственные возможности того, почему это могло произойти.

Поймать или вызвать исключение CommandSyntaxException

Решение этой проблемы состоит в том, чтобы заставить методы run или suggest вызывать исключение CommandSyntaxException. Не волнуйтесь, Brigadier обработает исключения и выдаст соответствующее сообщение об ошибке.

Проблемы с дженериками

Время от времени у вас могут возникать проблемы с типами дженерика. Убедитесь, что вы используете CommandManager.literal(…) или CommandManager.argument(…) вместо LiteralArgumentBuilder или RequiredArgumentBuilder.

Могу ли я зарегистрировать команды на стороне клиента?

Fabric в настоящее время не поддерживает команды на стороне клиента. Существует сторонний мод от команды Cotton, которая добавляет эту функциональность.

"Темные искусства"

Несколько вещей, которые мы не рекомендуем, но которые возможны.

Могу ли я регистрировать команды во время выполнения?

Вы можете это сделать, но это не рекомендуется. Вы получите CommandManager с сервера и добавите любые команды, которые пожелаете, в его CommandDispatcher.

После этого вам нужно снова отправить дерево команд каждому игроку, используя sendCommandTree(ServerPlayerEntity). Это необходимо, поскольку клиент локально кэширует дерево команд, которое он получает во время входа в систему (или при отправке пакетов оператора) для локальных завершений и сообщений об ошибках.

Могу ли я отменить регистрацию команд во время выполнения?

Вы также можете сделать это, однако это гораздо менее стабильно, чем регистрация команд, и может вызвать нежелательные побочные эффекты. Чтобы все было просто, вам нужно использовать отражение для бригадира и удалить узлы. После этого вам нужно снова отправить дерево команд каждому игроку, используя sendCommandTree(ServerPlayerEntity). Если вы не отправите обновленное дерево команд, клиент может подумать, что команда все еще существует, даже если сервер завершит выполнение с ошибкой.


Со временем уберётся

Currently this article is being migrated, so things may be a mess. Below is are the parts of the article that are yet to be migrated to the new format.

Requirements

Lets say you have a command you only want operators to be able to execute. This is where the requires method comes into play. The requires method has one argument of a Predicate<ServerCommandSource> which will supply a ServerCommandSource to test with and determine if the CommandSource can execute the command.

For example this may look like the following:

  1. dispatcher.register(literal("foo")
  2. .requires(source -> source.hasPermissionLevel(4))
  3. .executes(ctx -> {
  4. ctx.getSource().sendFeedback(new LiteralText("You are an operator", false));
  5. return 1;
  6. });

This command will only execute if the Source of the command is a level 4 operator at minimum. If the predicate returns false, then the command will not execute. Also this has the side effect of not showing this command in tab completion to anyone who is not a level 4 operator.

Nothing prevents someone from specifying calls to permissions implementations within the requires block. Just note that if permissions change, you need to re send the command tree.

Exceptions

Brigadier supports command exceptions which can be used to end a command such as if an argument didn't parse properly or the command failed to execute, as well as including richer details of the failure.

All the exceptions from Brigadier are based on the CommandSyntaxException. The two main types of exceptions Brigadier provides are Dynamic and Simple exception types, of which you must create() the exception to throw it. These exceptions also allow you to specify the context in which the exception was thrown using createWithContext(ImmutableStringReader), which builds the error message to point to where on the inputted command line the error occured. Below is a coin flip command to show an example of exceptions in use.

  1. dispatcher.register(CommandManager.literal("coinflip")
  2. .executes(ctx -> {
  3. Random random = new Random();
  4.  
  5. if(random.nextBoolean()) { // If heads succeed.
  6. ctx.getSource().sendMessage(new TranslateableText("coin.flip.heads"))
  7. return Command.SINGLE_SUCCESS;
  8. }
  9.  
  10. throw new SimpleCommandExceptionType(new TranslateableText("coin.flip.tails")).create(); // Oh no tails, you lose.
  11. }));

Though you are not just limited to a single type of exception as Brigadier also supplies Dynamic exceptions which take additional parameters for context.

  1. DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> {
  2. return new LiteralText("The name: " + (String) name + " has been used");
  3. });

There are more Dynamic exception types which each take a different amount of arguments into account (Dynamic2CommandExceptionType, Dynamic3CommandExceptionType, Dynamic4CommandExceptionType, DynamicNCommandExceptionType). You should remember that the Dynamic exceptions takes an object as an argument so you may have to cast the argument for your use.

Redirects (Aliases)

Redirects are Brigadier's form of aliases. Below is how Minecraft handles /msg have an alias of /tell and /w.

  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
  2. LiteralCommandNode node = registerMain(dispatcher); // Registers main command
  3. dispatcher.register(literal("tell")
  4. .redirect(node)); // Alias 1, redirect to main command
  5. dispatcher.register(literal("w")
  6. .redirect(node)); // Alias 2, redirect to main command
  7. }
  8.  
  9. public static LiteralCommandNode registerMain(CommandDispatcher<ServerCommandSource> dispatcher) {
  10. return dispatcher.register(literal("msg")
  11. .then(argument("targets", EntityArgumentType.players())
  12. .then(argument("message", MessageArgumentType.message())
  13. .executes(ctx -> {
  14. return execute(ctx.getSource(), getPlayers(ctx, "targets"), getMessage(ctx, "message"));
  15. }))));
  16. }

The redirect tells brigadier to continue parsing the command at another command node.

Redirects (Chainable Commands)

Commands such as /execute as @e[type=player] in the_end run tp ~ ~ ~ are possible because of redirects. Below is an example of a chainable command:

  1. LiteralCommandNode<ServerCommandSource> root = dispatcher.register(literal("fabric_test"));
  2. LiteralCommandNode<ServerCommandSource> root1 = dispatcher.register(literal("fabric_test")
  3. // You can register under the same literal more than once, it will just register new parts of the branch as shown below if you register a duplicate branch an error will popup in console warning of conflicting commands but one will still work.
  4. .then(literal("extra")
  5. .then(literal("long")
  6. .redirect(root, this::lengthen)) // Return to root for chaining
  7. .then(literal("short")
  8. .redirect(root, this::shorten))) // Return to root for chaining
  9. .then(literal("command")
  10. .executes(ctx -> {
  11. ctx.getSource().sendFeedback(new LiteralText("Chainable Command"), false);
  12. return Command.SINGLE_SUCCESS;
  13. })));

The redirect can also modify the CommandSource by use of a redirect modifier which can be used for builder commands.

  1. .redirect(rootNode, context -> {
  2. return ((ServerCommandSource) context.getSource()).withLookingAt(Vec3ArgumentType.getVec3(context, "pos"));
  3. })

What can the ServerCommandSource do?

A server command source provides some additional implementation specific context when a command is run. This includes the ability to get the entity that executed the command, the world the command was ran in or the server the command was run on.

  1. final ServerCommandSource source = ctx.getSource();
  2. // Get the source. This will always work.
  3.  
  4. final Entity sender = source.getEntity();
  5. // Unchecked, may be null if the sender was the console.
  6.  
  7. final Entity sender2 = source.getEntityOrThrow();
  8. // Will end the command if the source of the command was not an Entity.
  9. // The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity.
  10. // This method will require your methods to throw a CommandSyntaxException.
  11. // The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player.
  12.  
  13. final ServerPlayerEntity player = source.getPlayer();
  14. // Will end the command if the source of the command was not explicitly a Player. Also will send feedback telling the sender of the command that they must be a player. This method will require your methods to throw a CommandSyntaxException
  15.  
  16. source.getPosition();
  17. // Get's the sender's position as a Vec3 when the command was sent. This could be the location of the entity/command block or in the case of the console, the world's spawn point.
  18.  
  19. source.getWorld();
  20. // Get's the world the sender is within. The console's world is the same as the default spawn world.
  21.  
  22. source.getRotation();
  23. // Get's the sender's rotation as a Vec2f.
  24.  
  25. source.getMinecraftServer();
  26. // Access to the instance of the MinecraftServer this command was ran on.
  27.  
  28. source.getName();
  29. // The name of the command source. This could be the name of the entity, player, the name of a CommandBlock that has been renamed before being placed down or in the case of the Console, "Console"
  30.  
  31. source.hasPermissionLevel(int level);
  32. // Returns true if the source of the command has a certain permission level. This is based on the operator status of the sender. (On an integrated server, the player must have cheats enabled to execute these commands)

Some example commands examples

Broadcast a message

  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher){
  2. dispatcher.register(literal("broadcast")
  3. .requires(source -> source.hasPermissionLevel(2)) // Must be a game master to use the command. Command will not show up in tab completion or execute to non operators or any operator that is permission level 1.
  4. .then(argument("color", ColorArgumentType.color())
  5. .then(argument("message", greedyString())
  6. .executes(ctx -> broadcast(ctx.getSource(), getColor(ctx, "color"), getString(ctx, "message")))))); // You can deal with the arguments out here and pipe them into the command.
  7. }
  8.  
  9. public static int broadcast(ServerCommandSource source, Formatting formatting, String message) {
  10. final Text text = new LiteralText(message).formatting(formatting);
  11.  
  12. source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, false);
  13. return Command.SINGLE_SUCCESS; // Success
  14. }

/giveMeDiamond

First the basic code where we register “giveMeDiamond” as a literal and then an executes block to tell the dispatcher which method to run.

  1. public static LiteralCommandNode register(CommandDispatcher<ServerCommandSource> dispatcher) { // You can also return a LiteralCommandNode for use with possible redirects
  2. return dispatcher.register(literal("giveMeDiamond")
  3. .executes(ctx -> giveDiamond(ctx)));
  4. }

Then since we only want to give to players, we check if the CommandSource is a player. But we can use getPlayer and do both at the same time and throw an error if the source is not a player.

  1. public static int giveDiamond(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
  2. final ServerCommandSource source = ctx.getSource();
  3.  
  4. final PlayerEntity self = source.getPlayer(); // If not a player than the command ends

Then we add to the player's inventory, with a check to see if the inventory is full:

  1. if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){
  2. throw new SimpleCommandExceptionType(new TranslatableText("inventory.isfull")).create();
  3. }
  4.  
  5. return 1;
  6. }

Antioch

…lobbest thou thy Holy Hand Grenade of Antioch towards thy foe. who being naughty in My sight, shall snuff it.

Aside from the joke this command summons a primed TNT to a specified location or the location of the sender's cursor.

First create an entry into the CommandDispatcher that takes a literal of antioch with an optional argument of the location to summon the entity at.

  1. public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
  2. dispatcher.register(literal("antioch")
  3. .then(required("location", BlockPosArgumentType.blockPos()
  4. .executes(ctx -> antioch(ctx.getSource(), BlockPosArgument.getBlockPos(ctx, "location")))))
  5. .executes(ctx -> antioch(ctx.getSource(), null)));
  6. }

Then the creation and messages behind the joke.

  1. public static int antioch(ServerCommandSource source, BlockPos blockPos) throws CommandSyntaxException {
  2. if(blockPos == null) {
  3. // For the case of no inputted argument we calculate the cursor position of the player or throw an error if the nearest position is too far or is outside of the world.
  4. // This class is used as an example and actually doesn't exist yet.
  5. blockPos = LocationUtil.calculateCursorOrThrow(source, source.getRotation());
  6. }
  7.  
  8. final TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ(), null);
  9. tnt.setFuse(3);
  10.  
  11. source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe").formatting(Formatting.RED), false);
  12. source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("who being naughty in My sight, shall snuff it.").formatting(Formatting.RED), false);
  13. source.getWorld().spawnEntity(tnt);
  14. return 1;
  15. }

More examples coming soon

Custom Argument Types

Brigadier has support for custom argument types and this section goes into showing how to create a simple argument type.

Warning: Custom arguments require client mod installation to be registered correctly! If you are making a server plugin, consider using existing argument type and a custom suggestions provider instead.

For this example we will create a UuidArgumentType.

First create a class which extends ArgumentType. Note that ArgumentType is a generic, so the generic will define what type the ArgumentType will return

  1. public class UuidArgumentType implements ArgumentType<UUID> {

ArgumentType requires you to implement the parse method, the type it returns will match with the Generic type.

@Override
public UUID parse(StringReader reader) throws CommandSyntaxException {

This method is where all of your parsing will occur. Either this method will return the object based on the arguments provided in the command line or throw a CommandSyntaxException and parsing will fail.

Next you will store the current position of the cursor, this is so you can substring out only the specific argument. This will always be at the beginning of where your argument appears on the command line.

  1. int argBeginning = reader.getCursor(); // The starting position of the cursor is at the beginning of the argument.
  2. if (!reader.canRead()) {
  3. reader.skip();
  4. }

Now we grab the entire argument. Depending on your argument type, you may have a different criteria or be similar to some arguments where detecting a { on the command line will require it to be closed. For a UUID we will just figure out what cursor position the argument ends at.

  1. while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position.
  2. reader.skip(); // Tells the StringReader to move it's cursor to the next position.
  3. }

Then we will ask the StringReader what the current position of the cursor is an substring our argument out of the command line.

  1. String uuidString = reader.getString().substring(argBeginning, reader.getCursor());

Now finally we check if our argument is correct and parse the specific argument to our liking, and throwing an exception if the parsing fails.

  1. try {
  2. UUID uuid = UUID.fromString(uuidString); // Now our actual logic.
  3. return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on.
  4. } catch (Exception ex) {
  5. // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type.
  6. // Create with context tells Brigadier to supply some context to tell the user where the command failed at.
  7. // Though normal create method could be used.
  8. throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader);
  9. }

The ArgumentType is done, however your client will refuse the parse the argument and throw an error. This is because the server will tell the client what argument type the command node is. And the client will not parse any argument types it does not know how to parse. To fix this we need to register an ArgumentSerializer. Within your ModInitializer. For more complex argument types, you may need to create your own ArgumentSerializer.

  1. ArgumentTypes.register("mymod:uuid", UuidArgumentType.class, new ConstantArgumentSerializer(UuidArgumentType::uuid));
  2. // The argument should be what will create the ArgumentType.

And here is the whole ArgumentType:

UuidArgumentType.java
  1. import com.mojang.brigadier.StringReader;
  2. import com.mojang.brigadier.arguments.ArgumentType;
  3. import com.mojang.brigadier.context.CommandContext;
  4. import com.mojang.brigadier.exceptions.CommandSyntaxException;
  5. import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
  6. import net.minecraft.text.LiteralText;
  7. import net.minecraft.util.SystemUtil;
  8.  
  9. import java.util.ArrayList;
  10. import java.util.Collection;
  11. import java.util.UUID;
  12.  
  13. /**
  14.  * Represents an ArgumentType that will return a UUID.
  15.  */
  16. public class UuidArgumentType implements ArgumentType<UUID> {
  17. public static UuidArgumentType uuid() {
  18. return new UuidArgumentType();
  19. }
  20.  
  21. public static <S> UUID getUuid(String name, CommandContext<S> context) {
  22. // Note that you should assume the CommandSource wrapped inside of the CommandContext will always be a generic type.
  23. // If you need to access the ServerCommandSource make sure you verify the source is a server command source before casting.
  24. return context.getArgument(name, UUID.class);
  25. }
  26.  
  27. private static final Collection<String> EXAMPLES = SystemUtil.consume(new ArrayList<>(), list -> {
  28. list.add("765e5d33-c991-454f-8775-b6a7a394c097"); // i509VCB: Username The_1_gamers
  29. list.add("069a79f4-44e9-4726-a5be-fca90e38aaf5"); // Notch
  30. list.add("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"); // Dinnerbone
  31. });
  32.  
  33. @Override
  34. public UUID parse(StringReader reader) throws CommandSyntaxException {
  35. int argBeginning = reader.getCursor(); // The starting position of the cursor is at the beginning of the argument.
  36. if (!reader.canRead()) {
  37. reader.skip();
  38. }
  39.  
  40. // Now we check the contents of the argument till either we hit the end of the command line (When canRead becomes false)
  41. // Otherwise we go till reach reach a space, which signifies the next argument
  42. while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position.
  43. reader.skip(); // Tells the StringReader to move it's cursor to the next position.
  44. }
  45.  
  46. // Now we substring the specific part we want to see using the starting cursor position and the ends where the next argument starts.
  47. String uuidString = reader.getString().substring(argBeginning, reader.getCursor());
  48. try {
  49. UUID uuid = UUID.fromString(uuidString); // Now our actual logic.
  50. return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on.
  51. } catch (Exception ex) {
  52. // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type.
  53. // Create with context tells Brigadier to supply some context to tell the user where the command failed at.
  54. // Though normal create method could be used.
  55. throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader);
  56. }
  57. }
  58.  
  59. @Override
  60. public Collection<String> getExamples() { // Brigadier has support to show examples for what the argument should look like, this should contain a Collection of only the argument this type will return. This is mainly used to calculate ambiguous commands which share the exact same
  61. return EXAMPLES;
  62. }
  63. }
ru/tutorial/commands.1646308225.txt.gz · Last modified: 2022/03/03 11:50 by furnygo