tutorial:commands
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
tutorial:commands [2020/05/16 01:57] – Reword some parts of the tutorial, includes a few gramatical corrections i509vcb | tutorial:commands [2024/02/23 14:22] (current) – Documenting and first draft allen1210 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | Licensing: The code in this article is licensed under the " | ||
+ | |||
====== Creating Commands ====== | ====== Creating Commands ====== | ||
- | Creating commands can allow a mod developer to add functionality that a player | + | Creating commands can allow a mod developer to add functionality that can used through a command. This tutorial will teach you how to register commands, and the general |
- | This tutorial will teach you how to register commands, and the command structure of Brigadier. | + | |
- | Note: All code written here was written for 1.14.4. Some mappings may have changed in yarn, but all code should still be applicable. | + | ===== What is Brigadier? ===== |
- | ===== Registering Commands ===== | + | Brigadier is a command parser & dispatcher written by Mojang for use in Minecraft. Brigadier is a tree based command library where you build a tree of arguments and commands. |
- | Registering commands is done by registering a new listener in the '' | + | The source code for brigadier can be found here: https:// |
- | The event should | + | |
- | The dedicated parameter if true will tell event listeners that the server commands are being registered on is a '' | + | ===== The '' |
- | Below are a few examples of how the commands can be registered. | + | |
- | <code java [enable_line_numbers=" | + | In Minecraft, '' |
- | // Method reference | + | |
- | CommandRegistrationCallback.EVENT.register(TutorialCommands:: | + | |
- | // Using a lambda | + | The single method in '' |
- | CommandRegistrationCallback.EVENT.register((dispatcher, | + | |
- | // This command will be registered regardless of the server being dedicated or integrated | + | |
- | TutorialCommand.register(dispatcher); | + | |
- | if (dedicated) { | + | |
- | // This command | + | |
- | TutorialHelpCommand.register(dispatcher); | + | |
- | } else { | + | |
- | // This command | + | |
- | // Commands which call client only classes | + | |
- | IntegratedTutorialHelpCommand.register(dispatcher); | + | |
- | } | + | |
- | }); | + | |
- | // Or register directly | + | Like other functional interfaces, it is usually used as a lambda or a method reference: |
- | CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { | + | |
- | dispatcher.register(LiteralArgumentBuilder.literal(" | + | |
- | }); | + | |
- | </ | + | |
- | ==== A very basic command ==== | + | <code java> |
- | + | Command< | |
- | Wait isn't this the exact same command from the Brigadier tutorial? Well yes it is but it is here to help explain the structure of a command. | + | return |
- | + | }; | |
- | <code java [enable_line_numbers=" | + | |
- | // The root of the command. This must be a literal argument. | + | |
- | dispatcher.register(CommandManager.literal(" | + | |
- | // Then add an argument named bar that is an integer | + | |
- | .then(CommandManager.argument(" | + | |
- | // The command to be executed if the command " | + | |
- | .executes(ctx | + | |
- | System.out.println(" | + | |
- | // Return a result. Typically -1 is failure, 0 is pass and 1 is success. | + | |
- | return 1; | + | |
- | })) | + | |
- | | + | |
- | .executes(ctx -> { | + | |
- | System.out.println(" | + | |
- | | + | |
- | }) | + | |
- | ); | + | |
</ | </ | ||
- | The main process registers | + | The integer can be considered the result of the command. Typically negative values mean a command has failed and will do nothing. A result |
- | Since the root node must be literal, The sender must enter the exact same sequence of letters to execute | + | |
- | ===== Brigadier Explained ===== | ||
- | Brigadier starts with the '' | + | ==== What can the ServerCommandSource do? ==== |
- | Command nodes are similar | + | A '' |
- | The execute blocks specify the command to be ran. As Brigadier' | ||
- | |||
- | ==== CommandContexts ==== | ||
- | |||
- | When a command is ran, Brigadier provides a '' | ||
- | The CommandContext contains all arguments and other objects such as the inputted String and the '' | ||
- | |||
- | ==== Arguments ==== | ||
- | |||
- | Arguments in Brigadier both parse and error check any inputted arguments. | ||
- | Minecraft creates some special argument types for it's own use such as the '' | ||
- | |||
- | ==== Static Imports ==== | ||
- | You could do the long method of typing '' | ||
- | This also works for getting arguments, which shortens the already long '' | ||
- | This also works for Minecraft' | ||
- | |||
- | And your imports would look something like this: | ||
<code java [enable_line_numbers=" | <code java [enable_line_numbers=" | ||
- | import static com.mojang.brigadier.arguments.StringArgumentType.getString; | + | // Get the source. This will always work. |
- | import static com.mojang.brigadier.arguments.StringArgumentType.word; | + | final ServerCommandSource source = ctx.getSource(); |
- | import static net.minecraft.server.command.CommandManager.literal; | + | |
- | import static net.minecraft.server.command.CommandManager.argument; // argument(" | + | |
- | import static net.minecraft.server.command.CommandManager.*; | + | |
- | </ | + | |
- | Note: Please | + | // Unchecked, may be null if the sender was the console |
+ | final @Nullable Entity sender = source.getEntity(); | ||
- | Brigadier' | + | // Will throw an exception if the executor of the command was not an Entity. |
+ | // The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity. | ||
+ | // This method will require your methods to throw a CommandSyntaxException. | ||
+ | // The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player. | ||
+ | final @NotNull Entity sender2 = source.getEntityOrThrow(); | ||
- | Minecraft' | + | // null if the executor of the command is not a player. |
+ | final @Nullable ServerPlayerEntity player = source.getPlayer(): | ||
- | ==== Suggestions ==== | + | // Will throw an exception if the executor 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. | ||
+ | final @NotNull ServerPlayerEntity player | ||
- | Suggestions can be provided to the client to recommend what to input into the command. | + | // Gets the sender' |
- | < | + | // This could be the location of the entity/ |
- | SUMMONABLE_ENTITIES | + | final Vec3d position = source.getPosition(); |
- | AVAILIBLE_SOUNDS | + | |
- | ALL_RECIPES | + | |
- | ASK_SERVER | + | |
- | </ | + | |
- | Loot tables specify their own SuggestionProvider inside | + | // Gets the world the sender is within. The console's world is the same as the default spawn world. |
+ | final ServerWorld world = source.getWorld(); | ||
- | The example below is a dynamically changing SuggestionProvider that lists several words for a StringArgumentType to demonstrate how it works: | + | // Gets the sender' |
- | <code java [enable_line_numbers=" | + | final Vec2f rotation = source.getRotation(); |
- | public static SuggestionProvider< | + | |
- | return (ctx, builder) -> getSuggestionsBuilder(builder, | + | |
- | } | + | |
- | + | ||
- | private static CompletableFuture< | + | |
- | String remaining = builder.getRemaining().toLowerCase(Locale.ROOT); | + | |
- | + | ||
- | if(list.isEmpty()) { // If the list is empty then return no suggestions | + | |
- | return Suggestions.empty(); // No suggestions | + | |
- | } | + | |
- | + | ||
- | for (String str : list) { // Iterate through the supplied list | + | |
- | if (str.toLowerCase(Locale.ROOT).startsWith(remaining)) { | + | |
- | builder.suggest(str); // Add every single entry to suggestions list. | + | |
- | } | + | |
- | } | + | |
- | return builder.buildFuture(); | + | |
- | } | + | |
- | </ | + | |
- | The SuggestionProvider is a functional interface that returns a CompletableFuture containing a list of suggestions. These suggestions are sent to client as a command is typed and can be changed while server is running. The SuggestionProvider provides a CommandContext and a SuggestionBuilder to allow determination | + | // Access |
+ | final MinecraftServer server = source.getServer(); | ||
- | Though remember these are suggestions. | + | // The name of the command |
+ | // the name of a CommandBlock that has been renamed before being placed down, or in the case of the Console, " | ||
+ | final String name = source.getName(); | ||
- | To use the suggestion you would append it right after the argument you want to suggest possible arguments for. This can be any argument and the normal client side exception popups will still work. Note this cannot be applied to literals. | + | // Returns true if the source of the command has a certain permission level. |
- | + | // This is based on the operator status of the sender. | |
- | <code java [enable_line_numbers=" | + | // (On an integrated server, the player must have cheats enabled to execute these commands.) |
- | argument(argumentName, word()) | + | final boolean b = source.hasPermissionLevel(int level); |
- | .suggests(CompletionProviders.suggestedStrings()) | + | |
- | .then(/*Rest of the command*/)); | + | |
</ | </ | ||
- | ==== Requiring Permissions | + | ===== Register a basic command ===== |
- | Lets say you have a command you only want operators to be able to execute. This is where the '' | + | Commands are registered by registering in '' |
- | For example this may look like the following: | + | The event should be registered in your mod's initializer. The callback has three parameters. The '' |
- | <code java [enable_line_numbers=" | + | To simplify the code, it is highly recommended to '' |
- | dispatcher.register(literal(" | + | <code java> |
- | .requires(source -> source.hasPermissionLevel(4)) | + | import static net.minecraft.server.command.CommandManager.*; |
- | .executes(ctx -> { | + | |
- | ctx.getSource().sendFeedback(new LiteralText(" | + | |
- | return 1; | + | |
- | }); | + | |
</ | </ | ||
- | 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. | + | In the mod initializer, we just register |
- | + | ||
- | Nothing prevents someone from specifying calls to permissions implementations within the '' | + | |
- | + | ||
- | ==== 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 '' | + | |
- | Below is a coin flip command | + | |
<code java [enable_line_numbers=" | <code java [enable_line_numbers=" | ||
- | dispatcher.register(CommandManager.literal(" | + | public class ExampleMod implements ModInitializer { |
- | .executes(ctx -> { | + | @Override |
- | | + | public void onInitialize() { |
- | + | CommandRegistrationCallback.EVENT.register((dispatcher, | |
- | if(random.nextBoolean()) { // If heads succeed. | + | .executes(context |
- | ctx.getSource().sendMessage(new TranslateableText("coin.flip.heads")) | + | // For versions below 1.19, replace " |
- | return Command.SINGLE_SUCCESS; | + | // For versions below 1.20, remode "() ->" directly. |
- | } | + | |
- | throw new SimpleCommandExceptionType(new TranslateableText(" | + | return 1; |
- | })); | + | }))); |
+ | } | ||
+ | } | ||
</ | </ | ||
- | Though | + | **Please ensure |
- | <code java [enable_line_numbers=" | + | In the '' |
- | DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> { | + | |
- | return new LiteralText(" | + | |
- | }); | + | |
- | </code> | + | |
- | There are more Dynamic exception types which each take a different amount | + | If the command fails, instead |
- | You should remember that the Dynamic exceptions takes an object as an argument so you may have to cast the argument | + | |
- | ==== Redirects (Aliases) ==== | + | To execute this command, you must type ''/ |
- | Redirects are Brigadier' | + | ===== Registration environment ===== |
+ | If desired, you can also make sure a command | ||
- | <code java [enable_line_numbers=" | + | <yarncode |
- | public | + | public |
- | | + | |
- | | + | public void onInitialize() { |
- | | + | |
- | | + | |
- | | + | ...; |
+ | } | ||
+ | | ||
+ | } | ||
} | } | ||
+ | </ | ||
- | public | + | ===== Static Imports ===== |
- | return dispatcher.register(literal(" | + | In the example above, the use of static |
- | | + | |
- | | + | |
- | .executes(ctx -> { | + | |
- | return execute(ctx.getSource(), | + | |
- | })))); | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | The redirect tells brigadier to continue parsing the command at another command node. | + | |
- | + | ||
- | ==== Redirects (Chainable Commands) ==== | + | |
- | Commands such as '' | + | |
+ | Below is an example of some static imports: | ||
<code java [enable_line_numbers=" | <code java [enable_line_numbers=" | ||
- | LiteralCommandNode< | + | // getString(ctx, "string") |
- | LiteralCommandNode< | + | import static com.mojang.brigadier.arguments.StringArgumentType.getString; |
- | // 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. | + | // word() |
- | .then(literal(" | + | import static com.mojang.brigadier.arguments.StringArgumentType.word; |
- | .then(literal(" | + | // literal(" |
- | | + | import static net.minecraft.server.command.CommandManager.literal; |
- | .then(literal("short") | + | // |
- | .redirect(root, this:: | + | import static net.minecraft.server.command.CommandManager.argument; |
- | .then(literal(" | + | // Import everything in the CommandManager |
- | | + | import static net.minecraft.server.command.CommandManager.*; |
- | ctx.getSource().sendFeedback(new LiteralText(" | + | |
- | | + | |
- | }))); | + | |
</ | </ | ||
- | The redirect can also modify the CommandSource by use of a '' | ||
- | <code java [enable_line_numbers=" | + | Note: Please be sure you use the '' |
- | .redirect(rootNode, context -> { | + | |
- | return ((ServerCommandSource) context.getSource()).withLookingAt(Vec3ArgumentType.getVec3(context, | + | |
- | }) | + | |
- | </ | + | |
- | ===== ServerCommandSource ===== | + | Brigadier' |
- | 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 | + | Minecraft' |
- | <code java [enable_line_numbers=" | + | ===== Add Requirements ===== |
- | final ServerCommandSource source | + | |
- | // Get the source. This will always work. | + | |
- | final Entity sender = source.getEntity(); | + | Let's say you have a command that you only want operators to be able to execute. This is where the '' |
- | // Unchecked, may be null if the sender was the console. | + | |
- | final Entity sender2 = source.getEntityOrThrow(); | + | For example |
- | // Will end the command if the source of the command was not an Entity. | + | |
- | // The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity. | + | |
- | // This method will require your methods to throw a CommandSyntaxException. | + | |
- | // The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player. | + | |
- | final ServerPlayerEntity player | + | <code java [enable_line_numbers=" |
- | // Will end the command if the source | + | dispatcher.register(literal(" |
+ | | ||
+ | | ||
+ | ctx.getSource().sendFeedback(() -> Text.literal(" | ||
+ | return 1; | ||
+ | }); | ||
+ | </ | ||
- | source.getPosition(); | + | This command will only execute if the source |
- | // Get's the sender' | + | |
- | source.getWorld(); | + | To create commands that only level 4 operators |
- | // Get's the world the sender is within. The console's world is the same as the default spawn world. | + | |
- | source.getRotation(); | + | ===== Arguments ===== |
- | // Get's the sender' | + | |
- | source.getMinecraftServer(); | + | Arguments are used in most of commands. Sometimes they can be optional, which means if you do not provide that argument, |
- | // Access to the instance of the MinecraftServer this command | + | |
- | source.getName(); | + | In this case, we add one integer argument, and calculate |
- | // 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, " | + | |
- | source.hasPermissionLevel(int level); | + | <code java> |
- | // 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) | + | dispatcher.register(literal(" |
+ | .then(argument(" | ||
+ | .executes(context -> { | ||
+ | final int value = IntegerArgumentType.getInteger(context, | ||
+ | final int result = value * value; | ||
+ | context.getSource().sendFeedback(() -> Text.literal(" | ||
+ | return result; | ||
+ | }))); | ||
</ | </ | ||
- | ===== A few examples ===== | + | In this case, after the word ''/ |
- | === Broadcast a message === | + | Note: for simplicity, '' |
- | <code java [enable_line_numbers=" | + | Then we add an optional second argument: |
- | public static void register(CommandDispatcher< | + | <code java> |
- | dispatcher.register(literal(" | + | dispatcher.register(literal(" |
- | .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. | + | .then(argument(" |
- | .then(argument("color", ColorArgumentType.color()) | + | |
- | .then(argument(" | + | final int value = IntegerArgumentType.getInteger(context, |
- | .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. | + | final int result = value * value; |
- | } | + | context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(value, value, result)), false); |
- | + | | |
- | public static int broadcast(ServerCommandSource source, Formatting formatting, String message) { | + | }) |
- | | + | |
- | + | .executes(context | |
- | source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, false); | + | final int value = IntegerArgumentType.getInteger(context, "value"); |
- | return | + | final int value2 = IntegerArgumentType.getInteger(context, "value2"); |
- | } | + | final int result |
+ | | ||
+ | return | ||
+ | })))); | ||
</ | </ | ||
- | === / | + | Now you can type one or two integers. If you give one integer, that square of integer will be calculated. If you provide two integers, their product will be calculated. You may find it unnecessary to specify similar executions twice. Therefore, we can create a method that will be used in both executions. |
- | First the basic code where we register "giveMeDiamond" | + | <code java> |
+ | public class ExampleMod implements ModInitializer { | ||
+ | @Override | ||
+ | public void onInitialize() { | ||
+ | CommandRegistrationCallback.EVENT.register((dispatcher, | ||
+ | .then(argument(" | ||
+ | .executes(context -> executeMultiply(IntegerArgumentType.getInteger(context, | ||
+ | .then(argument(" | ||
+ | .executes(context -> executeMultiply(IntegerArgumentType.getInteger(context, | ||
+ | } | ||
- | <code java [enable_line_numbers=" | + | private |
- | public | + | |
- | | + | context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s" |
- | | + | return result; |
+ | } | ||
} | } | ||
</ | </ | ||
+ | ===== A sub command ===== | ||
- | Then since we only want to give to players, we check if the CommandSource is a player. But we can use '' | + | To add a sub command, you register |
- | < | + | < |
- | public static int giveDiamond(CommandContext< | + | dispatcher.register(literal(" |
- | final ServerCommandSource source = ctx.getSource(); | + | |
- | + | ||
- | final PlayerEntity self = source.getPlayer(); // If not a player than the command ends | + | |
</ | </ | ||
- | Then we add to the player' | + | In order to have a sub command, one needs to append the next node to the existing node. |
- | <code java [enable_line_numbers=" | + | This creates |
- | if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){ | + | |
- | throw new SimpleCommandExceptionType(new TranslatableText(" | + | |
- | } | + | |
- | + | ||
- | return 1; | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | === 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 | + | |
- | + | ||
- | 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. | + | |
<code java [enable_line_numbers=" | <code java [enable_line_numbers=" | ||
- | public static void register(CommandDispatcher< | + | dispatcher.register(literal(" |
- | | + | .then(literal("bar") |
- | .then(required("location", BlockPosArgumentType.blockPos() | + | .executes(context |
- | .executes(ctx -> antioch(ctx.getSource(), BlockPosArgument.getBlockPos(ctx, " | + | // For versions below 1.19, use '' |
- | .executes(ctx | + | // For versions below 1.20, use directly the '' |
- | } | + | context.getSource().sendFeedback(() -> Text.literal(" |
- | </ | + | |
- | Then the creation and messages behind the joke. | + | return 1; |
- | + | }) | |
- | <code java [enable_line_numbers=" | + | ) |
- | public static int antioch(ServerCommandSource source, BlockPos blockPos) throws CommandSyntaxException { | + | ); |
- | if(blockPos == null) { | + | |
- | | + | |
- | // This class is used as an example and actually doesn' | + | |
- | blockPos = LocationUtil.calculateCursorOrThrow(source, | + | |
- | | + | |
- | + | ||
- | final TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), | + | |
- | | + | |
- | + | ||
- | source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText(" | + | |
- | source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText(" | + | |
- | source.getWorld().spawnEntity(tnt); | + | |
- | return 1; | + | |
- | } | + | |
</ | </ | ||
- | === Finding Biomes via Command === | + | Similar to arguments, sub command |
- | + | ||
- | This example shows examples of redirects, exceptions, suggestions and a tiny bit of text. Note this command | + | |
<code java [enable_line_numbers=" | <code java [enable_line_numbers=" | ||
- | public class CommandLocateBiome { | + | dispatcher.register(literal(" |
- | // First make method to register | + | .executes(context |
- | public static void register(CommandDispatcher< | + | |
- | LiteralCommandNode< | + | |
- | | + | |
- | .then(argument(" | + | |
- | | + | |
- | .executes(ctx -> execute(ctx.getSource(), | + | |
- | // Register a redirect for /biome alias | + | |
- | dispatcher.register(literal(" | + | |
- | .redirect(basenode)); | + | |
- | } | + | |
- | + | ||
- | private static int execute(ServerCommandSource source, Identifier biomeId, int range) throws CommandSyntaxException | + | |
- | | + | |
- | + | ||
- | if(biome == null) { | + | |
- | // Since the argument is an Identifier we need to check if the identifier actually exists in the registry | + | |
- | throw new SimpleCommandExceptionType(new TranslatableText(" | + | |
- | } | + | |
- | + | ||
- | List< | + | |
- | bio.add(biome); | + | |
- | + | ||
- | ServerWorld world = source.getWorld(); | + | |
- | BiomeSource bsource = world.getChunkManager().getChunkGenerator().getBiomeSource(); | + | |
- | BlockPos loc = new BlockPos(source.getPosition()); | + | |
- | + | ||
- | // This call will likely block the main thread | + | |
- | BlockPos pos = bsource.locateBiome(loc.getX(), | + | |
- | + | ||
- | // If null, a biome was not found | + | |
- | if(pos == null) { | + | |
- | throw new SimpleCommandExceptionType(new TranslatableText(" | + | |
- | } | + | |
- | + | ||
- | int distance = MathHelper.floor(getDistance(loc.getX(), | + | |
- | // Popup text that can suggest commands. This is the exact same system that /locate uses. | + | |
- | Text teleportButtonPopup = Texts.bracketed(new TranslatableText(" | + | |
- | style.setColor(Formatting.GREEN).setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, | + | |
- | }); | + | |
- | + | ||
- | source.sendFeedback(new TranslatableText(" | + | |
return 1; | return 1; | ||
- | } | + | }) |
- | | + | .then(literal(" |
- | private static float getDistance(int int_1, int int_2, int int_3, int int_4) { | + | |
- | | + | context.getSource().sendFeedback(() -> Text.literal(" |
- | | + | return 1; |
+ | | ||
+ | ) | ||
+ | ); | ||
+ | </ | ||
+ | ====== Advanced concepts ====== | ||
- | return MathHelper.sqrt((float) (int_5 * int_5 + int_6 * int_6)); | + | Below are links to the articles about more complex concepts used in brigadier. |
- | } | + | |
- | + | ||
- | | + | |
- | public static class BiomeCompletionProvider { | + | ^ Page ^ Description |
- | // This provides suggestions | + | | [[command_exceptions |
- | | + | | [[command_suggestions|Suggestions]] |
- | | + | | [[command_redirects|Redirects]] |
- | return builder.buildFuture(); | + | | [[command_argument_types|Custom Argument Types]] |
- | }); | + | | [[command_examples|Examples]] | Some example commands | |
- | + | ||
- | } | + | |
- | </ | + | |
- | ===== Custom Argument Types ===== | + | ====== FAQ ====== |
- | Brigadier has support for custom argument types and this section goes into showing how to create a simple argument type. | + | ===== Why does my code not compile ===== |
- | 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. | + | There are several immediate possibilities for why this could occur. |
- | For this example we will create | + | * **Catch or throw a CommandSyntaxException: |
+ | * **Issues with generics:** You may have an issue with generics once in a while. If you are registering server command (which is most of the case), make sure you are using '' | ||
+ | * **Check '' | ||
+ | * **'' | ||
- | First create a class which extends '' | + | ===== Can I register client side commands? ===== |
- | <code java [enable_line_numbers=" | + | Fabric has a '' |
- | public class UuidArgumentType implements ArgumentType< | + | |
- | </ | + | |
- | ArgumentType requires you to implement the '' | ||
<code java> | <code java> | ||
- | @Override | + | ClientCommandRegistrationCallback.EVENT.register((dispatcher, |
- | public UUID parse(StringReader reader) throws CommandSyntaxException | + | .executes(context -> { |
+ | context.getSource().sendFeedback(Text.literal(" | ||
+ | return 1; | ||
+ | } | ||
+ | ))); | ||
</ | </ | ||
- | This method is where all of your parsing will occur. Either this method will return the object based on the arguments provided | + | If you need to open a screen |
- | 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. | + | ===== Can I register commands in runtime? ===== |
- | <code java [enable_line_numbers=" | + | You can do this but it is not recommended. You would get the '' |
- | int argBeginning = reader.getCursor(); | + | |
- | if (!reader.canRead()) { | + | |
- | reader.skip(); | + | |
- | } | + | |
- | </ | + | |
- | Now we grab the entire argument. Depending on your argument type, you may have a different criteria or be similar | + | After that you need to send the command tree to every player again using '' |
- | <code java [enable_line_numbers=" | + | ===== Can I unregister commands in runtime? ===== |
- | while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position. | + | |
- | reader.skip(); | + | |
- | } | + | |
- | </ | + | |
- | Then we will ask the StringReader what the current position of the cursor is an substring our argument out of the command | + | You can also do this, however it is much less stable than registering commands and could cause unwanted side effects. To keep things simple, you need to use reflection on brigadier and remove |
- | <code java [enable_line_numbers=" | + | ===== Can I execute command without typing in game? ===== |
+ | Yes! You can. Before trying the next code, take note it works on Fabric 0.91.6+1.20.2 and it was not tested in other versions. | ||
- | Now finally we check if our argument | + | Here is the code example |
- | <code java [enable_line_numbers=" | + | <code java> |
- | try { | + | |
- | | + | |
- | return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on. | + | if (player |
- | } catch (Exception ex) { | + | |
- | // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type. | + | |
- | // Create with context tells Brigadier to supply some context to tell the user where the command | + | |
- | // Though normal create method could be used. | + | |
- | throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader); | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | 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. | + | |
- | + | ||
- | <code java [enable_line_numbers=" | + | |
- | ArgumentTypes.register(" | + | |
- | // The argument should be what will create the ArgumentType. | + | |
- | </ | + | |
- | + | ||
- | And here is the whole ArgumentType: | + | |
- | + | ||
- | <file java UuidArgumentType.java [enable_line_numbers=" | + | |
- | + | ||
- | import com.mojang.brigadier.StringReader; | + | |
- | import com.mojang.brigadier.arguments.ArgumentType; | + | |
- | import com.mojang.brigadier.context.CommandContext; | + | |
- | import com.mojang.brigadier.exceptions.CommandSyntaxException; | + | |
- | import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; | + | |
- | import net.minecraft.text.LiteralText; | + | |
- | import net.minecraft.util.SystemUtil; | + | |
- | + | ||
- | import java.util.ArrayList; | + | |
- | import java.util.Collection; | + | |
- | import java.util.UUID; | + | |
- | + | ||
- | /** | + | |
- | * Represents an ArgumentType that will return a UUID. | + | |
- | */ | + | |
- | public class UuidArgumentType implements ArgumentType< | + | |
- | public static UuidArgumentType uuid() { | + | |
- | return new UuidArgumentType(); | + | |
- | } | + | |
- | + | ||
- | public static <S> UUID getUuid(String name, CommandContext< | + | |
- | // Note that you should assume the CommandSource wrapped inside of the CommandContext will always be a generic type. | + | |
- | // If you need to access the ServerCommandSource make sure you verify the source is a server command source before casting. | + | |
- | return context.getArgument(name, | + | |
- | } | + | |
- | + | ||
- | private static final Collection< | + | |
- | list.add(" | + | |
- | list.add(" | + | |
- | list.add(" | + | |
- | }); | + | |
- | + | ||
- | @Override | + | |
- | public UUID parse(StringReader reader) throws CommandSyntaxException { | + | |
- | int argBeginning = reader.getCursor(); | + | |
- | if (!reader.canRead()) { | + | |
- | | + | |
- | } | + | |
- | + | ||
- | // Now we check the contents of the argument till either we hit the end of the command line (When canRead becomes false) | + | |
- | // Otherwise we go till reach reach a space, which signifies the next argument | + | |
- | while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position. | + | |
- | reader.skip(); // Tells the StringReader to move it's cursor to the next position. | + | |
- | } | + | |
- | + | ||
- | // Now we substring the specific part we want to see using the starting cursor position and the ends where the next argument starts. | + | |
- | String uuidString | + | |
- | try { | + | |
- | | + | |
- | return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on. | + | |
- | } catch (Exception ex) { | + | |
- | // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type. | + | |
- | // Create with context tells Brigadier to supply some context to tell the user where the command | + | |
- | // Though normal create method could be used. | + | |
- | throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader); | + | |
} | } | ||
} | } | ||
+ | </ | ||
- | @Override | + | First, you need a CommandManager<ServerCommandSource>. |
- | public Collection< | + | Second, you need the ServerCommandSource. |
- | return EXAMPLES; | + | Then, u can call some CommandManager |
- | } | + | But commandeManader.executeWithPrefix allow you to use String. You can also put the slash (/). |
- | } | + | |
- | </file> | + | |
- | ===== FAQ ===== | + | |
- | + | ||
- | === What else can I send feedback to the CommandSource? | + | |
- | + | ||
- | You use the Text classes (LiteralText, TranslatableText, | + | |
- | + | ||
- | === Why does my IDE complain saying that a method executed by my command needs to catch or throw a CommandSyntaxException === | + | |
- | + | ||
- | The solution to this is just to make the methods throw a CommandSyntaxException down the whole chain as the executes block handles the exceptions. | + | |
- | + | ||
- | === Can I register commands runtime? === | + | |
- | + | ||
- | You can do this but it is not reccomended. You would get the instance of the CommandManager | + | |
- | + | ||
- | After that you will need to send the command tree to every player again using '' | + | |
- | + | ||
- | === Can I unregister commands runtime? === | + | |
- | + | ||
- | You can also do this but it is very unstable and could cause unwanted side effects. Lets just say it involves a bunch of Reflection. | + | |
- | + | ||
- | Once again you will need to send the command tree to every player again using '' | + | |
- | + | ||
- | === Can I register client side commands? === | + | |
- | Well Fabric currently doesn' | + | So... have fun! |
- | https:// | + |
tutorial/commands.1589594237.txt.gz · Last modified: 2020/05/16 01:57 by i509vcb