tutorial:commands
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
tutorial:commands [2019/11/16 02:14] – i509vcb | tutorial:commands [2023/11/18 11:21] – solidblock | ||
---|---|---|---|
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 | + | |
- | ===== Registering Commands | + | ===== What is Brigadier? |
- | If you just want to see how to register | + | 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 through '' | + | The source code for brigadier can be found here: https:// |
- | The '' | + | ===== The '' |
- | The dedicated flag if set to true will tell Fabric to only register the command on a '' | + | In Minecraft, '' |
- | Below are a few examples | + | The single method in '' |
- | <code java [enable_line_numbers=" | + | Like other functional interfaces, it is usually used as a lambda or a method |
- | CommandRegistry.INSTANCE.register(false, dispatcher -> TutorialCommands.register(dispatcher)); | + | |
- | + | ||
- | CommandRegistry.INSTANCE.register(false, | + | |
- | TutorialCommand.register(dispatcher); | + | |
- | TutorialHelpCommand.register(dispatcher); | + | |
- | }); | + | |
- | + | ||
- | CommandRegistry.INSTANCE.register(true, | + | |
- | 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. -1 is failure, 0 is a pass and 1 is success. | + | |
- | return 1; | + | |
- | })) | + | |
- | | + | |
- | .executes(ctx -> { | + | |
- | System.out.println(" | + | |
- | | + | |
- | }) | + | |
- | ); | + | |
</ | </ | ||
- | The main process registers the command " | + | The integer can be considered |
- | Since the root node must be literal, The sender must enter the exact same sequence | + | |
- | ===== Brigadier Explained ===== | ||
- | Brigadier starts with the '' | + | ==== What can the ServerCommandSource do? ==== |
- | The trunk of the tree is the CommandDispatcher. | + | |
- | The register(LiteralArgumentBuilder) methods specify the beginning of branches with the following then methods specifying the shape of length of the branches. | + | |
- | The executes blocks | + | |
- | The execute blocks specify the command to be ran. As Brigadier's Command is a FunctionalInterface you can use lambdas | + | A '' |
- | ==== CommandContexts ==== | ||
- | |||
- | When a command is ran, Brigadier provides a CommandContext to the command that is ran. | ||
- | The CommandContext contains all arguments and other objects such as the inputted String and the '' | ||
- | |||
- | ==== Arguments ==== | ||
- | |||
- | The arguments in Brigadier both parse and error check any inputted arguments. | ||
- | Minecraft creates some special arguments for it's own use such as the '' | ||
- | |||
- | 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.*; | + | |
- | </ | + | |
- | Brigadier' | + | // Unchecked, may be null if the sender was the console or the command block. |
+ | final @Nullable Entity sender = source.getEntity(); | ||
- | Minecraft' | + | // 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 | ||
+ | // 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(); | ||
- | ==== Suggestions ==== | + | // null if the executor of the command is not a player. |
+ | final @Nullable ServerPlayerEntity player | ||
- | Suggestions can be provided to the client to recommend what to input into the command. | + | // Will throw an exception if the executor of the command |
- | < | + | // Also will send feedback telling |
- | SUMMONABLE_ENTITIES | + | // This method will require your methods to throw a CommandSyntaxException. |
- | AVAILIBLE_SOUNDS | + | final @NotNull ServerPlayerEntity player = source.getPlayerOrThrow(); |
- | ALL_RECIPES | + | |
- | ASK_SERVER | + | |
- | </code> | + | |
- | Loot tables specify their own SuggestionProvider inside LootCommand for example. | + | // Gets the sender' |
+ | // This could be the location of the entity/ | ||
+ | final Vec3d position = source.getPosition(); | ||
- | The example below is a dynamically changing SuggestionProvider that lists several words for a StringArgumentType to demonstrate how it works: | + | // Gets the world the sender |
- | <code java [enable_line_numbers=" | + | final ServerWorld world = source.getWorld(); |
- | 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 | + | |
- | if (str.toLowerCase(Locale.ROOT).startsWith(remaining)) { | + | |
- | | + | |
- | } | + | |
- | } | + | |
- | return builder.buildFuture(); | + | |
- | } | + | |
- | </ | + | |
- | The SuggestionProvider is a FunctionalInterface that returns a CompletableFuture containing a list of suggestions. These suggestions are given to client | + | // Gets the sender' |
+ | final Vec2f rotation = source.getRotation(); | ||
- | Though remember these are suggestions. The inputted command may not contain an argument you suggested so you still have to parse check inside | + | // Access |
+ | final MinecraftServer server = source.getServer(); | ||
- | To use the suggestion you would append it right after the argument you want to recommend arguments for. This can be any argument and the normal client side exception popups will still work. Note this cannot be applied to literals. | + | // 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, " | ||
+ | final String name = source.getName(); | ||
- | <code java [enable_line_numbers=" | + | // Returns |
- | argument(argumentName, word()) | + | // This is based on the operator status of the sender. |
- | .suggests(CompletionProviders.suggestedStrings()) | + | // (On an integrated server, the player must have cheats enabled to execute these commands.) |
- | .then(/*Rest of the command*/)); | + | final boolean b = source.hasPermissionLevel(int level); |
</ | </ | ||
- | ==== Requires | + | ===== 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 | + | In the mod initializer, we just register |
- | + | ||
- | ==== 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. | + | |
- | + | ||
- | 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 to show an example of exceptions in use. | + | |
<code java [enable_line_numbers=" | <code java [enable_line_numbers=" | ||
- | dispatcher.register(CommandManager.literal(" | + | public class ExampleMod implements ModInitializer { |
- | .executes(ctx -> { | + | @Override |
- | Random random = new Random(); | + | 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; | + | |
- | } | + | |
- | throw | + | |
- | })); | + | |
- | </ | + | |
- | + | ||
- | Though you are not just limited to a single type of exception as Brigadier also supplies Dynamic exceptions. | + | |
- | + | ||
- | <code java [enable_line_numbers="true" | + | |
- | DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> { | + | |
- | return new LiteralText("The name: " + (String) name + " has been used" | + | |
- | }); | + | |
- | </ | + | |
- | + | ||
- | There are more Dynamic exception types which each take a different amount of arguments into account ('' | + | |
- | 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 | + | |
- | + | ||
- | Redirects are Brigadier' | + | |
- | + | ||
- | <code java [enable_line_numbers=" | + | |
- | public static void register(CommandDispatcher< | + | |
- | LiteralCommandNode node = registerMain(dispatcher); // Registers main command | + | |
- | dispatcher.register(literal(" | + | |
- | .redirect(node)); | + | |
- | dispatcher.register(literal(" | + | |
- | .redirect(node)); // Alias 2, redirect to main command | + | |
- | } | + | |
- | public static LiteralCommandNode registerMain(CommandDispatcher< | + | return 1; |
- | | + | |
- | .then(argument(" | + | } |
- | .then(argument(" | + | |
- | .executes(ctx -> { | + | |
- | return execute(ctx.getSource(), | + | |
- | })))); | + | |
} | } | ||
</ | </ | ||
- | The redirect registers a branch into the command tree, where the dispatcher is told when executing a redirected command to instead look on a different branch for more arguments and executes blocks. The literal | + | **Please ensure you import |
- | Redirects do not work in shortened aliases such as '' | + | In the '' |
- | ==== Redirects (Chainable Commands) ==== | + | If the command fails, instead of calling |
- | Commands such as '' | + | |
- | <code java [enable_line_numbers=" | + | To execute this command, you must type ''/ |
- | LiteralCommandNode< | + | |
- | LiteralCommandNode< | + | |
- | // 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. | + | |
- | .then(literal(" | + | |
- | .then(literal(" | + | |
- | .redirect(root)) | + | |
- | .then(literal(" | + | |
- | .redirect(root))) | + | |
- | .then(literal(" | + | |
- | .executes(ctx -> { | + | |
- | ctx.getSource().sendFeedback(new LiteralText(" | + | |
- | return Command.SINGLE_SUCCESS; | + | |
- | }))); | + | |
- | </ | + | |
- | The redirect can also modify | + | |
- | <code java [enable_line_numbers=" | + | ===== Registration environment ===== |
- | .redirect(rootNode, (commandContext_1x) -> { | + | If desired, you can also make sure a command is only registered under some specific circumstances, for example, only in the dedicated environment: |
- | return ((ServerCommandSource) commandContext_1x.getSource()).withLookingAt(Vec3ArgumentType.getVec3(commandContext_1x, " | + | |
- | }) | + | |
- | </ | + | |
- | ===== ServerCommandSource ===== | + | <yarncode |
- | + | public class ExampleCommandMod implements ModInitializer { | |
- | What if you wanted a command that the CommandSource must be an entity to execute? The ServerCommandSource provides this option with a couple of methods | + | |
- | + | public void onInitialize() { | |
- | <code java [enable_line_numbers=" | + | |
- | ServerCommandSource source = ctx.getSource(); | + | |
- | // Get the source. This will always work. | + | ...; |
- | + | } | |
- | Entity sender = source.getEntity(); | + | }); |
- | // Unchecked, may be null if the sender was the console. | + | } |
- | + | ||
- | Entity sender2 = source.getEntityOrThrow(); | + | |
- | // 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. | + | |
- | + | ||
- | ServerPlayerEntity player = source.getPlayer(); | + | |
- | // 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. | + | |
- | </ | + | |
- | + | ||
- | The ServerCommandSource also provides other information about the sender of the command. | + | |
- | + | ||
- | <code java [enable_line_numbers="true" | + | |
- | source.getPosition(); | + | |
- | // Get's the sender' | + | |
- | + | ||
- | source.getWorld(); | + | |
- | // Get's the world the sender is within. The console' | + | |
- | + | ||
- | source.getRotation(); | + | |
- | // Get's the sender' | + | |
- | + | ||
- | source.getMinecraftServer(); | + | |
- | // Access to the instance of the MinecraftServer this command was ran on. | + | |
- | + | ||
- | source.getName(); | + | |
- | // 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" | + | |
- | + | ||
- | source.hasPermissionLevel(int level); | + | |
- | // 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) | + | |
- | </code> | + | |
- | + | ||
- | ===== Some actual examples ===== | + | |
- | + | ||
- | Just a few to show: | + | |
- | + | ||
- | === Broadcast a message === | + | |
- | + | ||
- | <code java [enable_line_numbers=" | + | |
- | public | + | |
- | | + | |
- | .requires(source | + | |
- | .then(argument(" | + | |
- | .then(argument(" | + | |
- | | + | |
} | } | ||
+ | </ | ||
- | public static int broadcast(ServerCommandSource source, Formatting formatting, String message) { | + | ===== Static Imports ===== |
- | Text text = new LiteralText(message).formatting(formatting); | + | In the example above, the use of static imports is used for code simplifying. For a literal |
- | + | ||
- | source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, | + | |
- | return Command.SINGLE_SUCCESS; | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | === / | + | |
- | + | ||
- | First the basic code where we register " | + | |
+ | Below is an example of some static imports: | ||
<code java [enable_line_numbers=" | <code java [enable_line_numbers=" | ||
- | public static LiteralCommandNode register(CommandDispatcher< | + | // getString(ctx, " |
- | | + | import static com.mojang.brigadier.arguments.StringArgumentType.getString; |
- | .executes(ctx -> giveDiamond(ctx))); | + | // word() |
- | } | + | import static com.mojang.brigadier.arguments.StringArgumentType.word; |
+ | // | ||
+ | import static net.minecraft.server.command.CommandManager.literal; | ||
+ | // argument(" | ||
+ | import static net.minecraft.server.command.CommandManager.argument; | ||
+ | // Import everything in the CommandManager | ||
+ | import static net.minecraft.server.command.CommandManager.*; | ||
</ | </ | ||
- | Then since we only want to give to players, we check if the CommandSource is a player. But we can use '' | + | Note: Please be sure you use the '' |
- | <code java [enable_line_numbers=" | + | Brigadier' |
- | public static int giveDiamond(CommandContext< | + | |
- | ServerCommandSource source = ctx.getSource(); | + | |
- | + | ||
- | PlayerEntity self = source.getPlayer(); | + | |
- | </ | + | |
- | Then we add to the player' | + | Minecraft' |
- | <code java [enable_line_numbers=" | + | ===== Add Requirements ===== |
- | if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){ | + | |
- | throw new SimpleCommandExceptionType(new TranslateableText(" | + | |
- | } | + | |
- | return 1; | + | |
- | } | + | |
- | </ | + | |
- | === Antioch === | + | Let's say you have a command that you only want operators to be able to execute. This is where the '' |
- | ...lobbest thou thy Holy Hand Grenade | + | |
- | 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' | + | For example |
- | + | ||
- | First create an entry into the CommandDispatcher that takes a literal of antioch with an optional argument of the location to summon | + | |
<code java [enable_line_numbers=" | <code java [enable_line_numbers=" | ||
- | public static void register(CommandDispatcher< | + | dispatcher.register(literal(" |
- | | + | .requires(source |
- | .then(required(" | + | .executes(ctx -> { |
- | .executes(ctx | + | |
- | .executes(ctx -> antioch(ctx.getSource(), | + | |
- | } | + | |
- | </ | + | |
- | + | ||
- | Then the creation and messages behind the joke. | + | |
- | + | ||
- | <code java [enable_line_numbers=" | + | |
- | public static int antioch(ServerCommandSource source, BlockPos blockPos) throws CommandSyntaxException | + | |
- | + | ||
- | | + | |
- | blockPos = LocationUtil.calculateCursorOrThrow(source, | + | |
- | } | + | |
- | + | ||
- | TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), | + | |
- | + | ||
- | source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe").formatting(Formatting.RED), false); | + | |
- | source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText(" | + | |
- | source.getWorld().spawnEntity(tnt); | + | |
return 1; | return 1; | ||
- | } | + | |
</ | </ | ||
- | === Finding Biomes via Command === | + | This command will only execute if the source of the command is a level 2 operator at minimum, // |
- | This example shows examples of redirects, exceptions, suggestions and a tiny bit of text. Note this command when used works but can take a bit of time to work similarly to ''/ | + | To create commands that only level 4 operators |
- | <code java [enable_line_numbers=" | + | |
- | public class CommandLocateBiome { | + | |
- | // First make method to register | + | |
- | public static void register(CommandDispatcher< | + | |
- | LiteralCommandNode< | + | |
- | .then(argument(" | + | |
- | .then(argument(" | + | |
- | .executes(ctx -> execute(ctx.getSource(), | + | |
- | .executes(ctx -> execute(ctx.getSource(), | + | |
- | // Register redirect | + | |
- | dispatcher.register(literal(" | + | |
- | .redirect(basenode)); | + | |
- | } | + | |
- | // Beginning of the method | + | |
- | private static int execute(ServerCommandSource source, Identifier biomeId, int range) throws CommandSyntaxException { | + | |
- | Biome biome = Registry.BIOME.get(biomeId); | + | |
- | + | ||
- | 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()); | + | |
- | | + | |
- | BlockPos pos = bsource.locateBiome(loc.getX(), loc.getZ(), range, bio, new Random(world.getSeed())); | + | |
- | + | ||
- | // Since this method | + | |
- | 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_1x.setColor(Formatting.GREEN).setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, | + | |
- | }); | + | |
- | + | ||
- | | + | |
- | + | ||
- | return 1; | + | |
- | } | + | |
- | // Just a normal old 2d distance method. | + | |
- | private static float getDistance(int int_1, int int_2, int int_3, int int_4) { | + | |
- | int int_5 = int_3 - int_1; | + | |
- | int int_6 = int_4 - int_2; | + | |
- | + | ||
- | return MathHelper.sqrt((float) (int_5 * int_5 + int_6 * int_6)); | + | |
- | } | + | |
- | + | ||
- | + | ||
- | + | ||
- | public static class BiomeCompletionProvider { | + | |
- | // This provides suggestions of what biomes can be selected. Since this uses the registry, mods that add new biomes will work without modification. | + | |
- | public static final SuggestionProvider< | + | |
- | Registry.BIOME.getIds().stream().forEach(identifier -> builder.suggest(identifier.toString(), | + | |
- | return builder.buildFuture(); | + | |
- | }); | + | |
- | + | ||
- | } | + | |
- | </ | + | |
- | ===== Custom | + | ===== Arguments ===== |
- | Brigadier has support for custom | + | Arguments are used in most of commands. Sometimes they can be optional, which means if you do not provide that argument, the command will also run. One node may have multiple |
- | First create a class which extends '' | + | In this case, we add one integer argument, and calculate |
<code java> | <code java> | ||
- | public class UUIDArgumentType implements ArgumentType< | + | 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; | ||
+ | }))); | ||
</ | </ | ||
- | ArgumentType requires you to implement | + | In this case, after the word '' |
- | <code java> | + | |
- | @Override | + | |
- | public UUID parse(StringReader reader) throws CommandSyntaxException { | + | |
- | </code> | + | |
- | 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 | + | Note: for simplicity, '' |
- | + | ||
- | Next you will store the current position of the cursor, this is so you can substring out only the specific argument. This will always | + | |
+ | Then we add an optional second argument: | ||
<code java> | <code java> | ||
- | int argBeginning | + | dispatcher.register(literal(" |
- | if (!reader.canRead()) { | + | .then(argument(" |
- | | + | .executes(context -> { |
- | } | + | final int value = IntegerArgumentType.getInteger(context, " |
+ | final int result = value * value; | ||
+ | context.getSource().sendFeedback(() -> Text.literal(" | ||
+ | | ||
+ | }) | ||
+ | .then(argument(" | ||
+ | .executes(context -> { | ||
+ | final int value = IntegerArgumentType.getInteger(context, " | ||
+ | final int value2 = IntegerArgumentType.getInteger(context, | ||
+ | final int result = value * value2; | ||
+ | context.getSource().sendFeedback(() -> Text.literal(" | ||
+ | return result; | ||
+ | | ||
</ | </ | ||
- | Now we grab the entire argument. Depending on your argument type, you may have a different criteria | + | 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 |
<code java> | <code java> | ||
- | while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position. | + | public class ExampleMod implements ModInitializer { |
- | | + | @Override |
- | } | + | public void onInitialize() { |
- | </code> | + | CommandRegistrationCallback.EVENT.register((dispatcher, |
+ | | ||
+ | .executes(context -> executeMultiply(IntegerArgumentType.getInteger(context, | ||
+ | | ||
+ | | ||
+ | } | ||
- | Then we will ask the StringReader what the current position of the cursor is an substring our argument out of the command line. | + | private static int executeMultiply(int value, int value2, CommandContext<ServerCommandSource> context) |
- | + | | |
- | <code java> | + | |
- | + | return result; | |
- | 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. | + | } |
- | + | ||
- | <code java> | + | |
- | try { | + | |
- | | + | |
- | | + | |
- | } 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 failed at. | + | |
- | // Though normal create method could be used. | + | |
- | throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader); | + | |
} | } | ||
</ | </ | ||
+ | ===== A sub command ===== | ||
- | The ArgumentType is done, however your client will refuse the parse the argument and throw an error. To fix this we need to register | + | To add a sub command, you register the first literal node of the command |
- | Within your ModInitializer (Not only client or server) you will add this so the client can recognize the argument when the command | + | |
- | < | + | < |
- | ArgumentTypes.register(" | + | dispatcher.register(literal("foo")) |
- | // The argument should be what will create the ArgumentType. | + | |
</ | </ | ||
- | And here is the whole ArgumentType: | + | In order to have a sub command, one needs to append |
- | <file java UUIDArgumentType.java> | + | This creates the command '' |
- | import com.mojang.brigadier.StringReader; | + | <code java [enable_line_numbers=" |
- | import com.mojang.brigadier.arguments.ArgumentType; | + | dispatcher.register(literal(" |
- | import com.mojang.brigadier.context.CommandContext; | + | .then(literal(" |
- | import com.mojang.brigadier.exceptions.CommandSyntaxException; | + | .executes(context |
- | import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; | + | // For versions below 1.19, use '' |
- | import net.minecraft.text.LiteralText; | + | // For versions below 1.20, use directly the '' |
- | import net.minecraft.util.SystemUtil; | + | |
- | import java.util.ArrayList; | + | return 1; |
- | import java.util.Collection; | + | }) |
- | import java.util.UUID; | + | ) |
+ | ); | ||
+ | </ | ||
- | /** | + | Similar to arguments, sub command nodes can also be set optional. In the following case, both '' |
- | * Represents an ArgumentType that will return a UUID. | + | <code java [enable_line_numbers=" |
- | */ | + | dispatcher.register(literal(" |
- | public class UUIDArgumentType implements ArgumentType<UUID> { // ArgumentType has a generic, which is the return | + | .executes(context -> { |
- | | + | context.getSource().sendFeedback(() -> Text.literal(" |
- | | + | |
- | | + | |
- | } | + | |
+ | | ||
+ | context.getSource().sendFeedback(() -> Text.literal(" | ||
+ | | ||
+ | | ||
+ | ) | ||
+ | ); | ||
+ | </ | ||
+ | ====== Advanced concepts ====== | ||
- | // This is also for convince and you could always just grab the argument from the CommandContext. | + | Below are links to the articles about more complex concepts used in brigadier. |
- | 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 access the ServerCommandSource make sure you verify the source is an instanceof ServerCommandSource before dangerous casting. | + | |
- | return context.getArgument(name, | + | |
- | } | + | |
- | private static final Collection< | + | ^ Page ^ Description |
- | list.add(" | + | | [[command_exceptions |
- | list.add(" | + | | [[command_suggestions|Suggestions]] |
- | list.add(" | + | | [[command_redirects|Redirects]] |
- | | + | | [[command_argument_types|Custom Argument Types]] |
+ | | [[command_examples|Examples]] | Some example commands | | ||
- | @Override | + | ====== FAQ ====== |
- | public UUID parse(StringReader reader) throws CommandSyntaxException { | + | |
- | int argBeginning | + | |
- | if (!reader.canRead()) { | + | |
- | reader.skip(); | + | |
- | } | + | |
- | // Now we check the contents of the argument till either we hit the end of the command line (When canRead becomes false) | + | ===== Why does my code not compile ===== |
- | // 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(); | + | |
- | } | + | |
- | // Now we substring the specific part we want to see using the starting cursor position and the ends where the next argument starts. | + | There are several immediate possibilities for why this could occur. |
- | String uuidString = reader.getString().substring(argBeginning, | + | |
- | try { | + | |
- | UUID uuid = UUID.fromString(uuidString); | + | |
- | 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 failed at. | + | |
- | // Though normal create method | + | |
- | throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader); | + | |
- | } | + | |
- | } | + | |
- | @Override | + | * **Catch or throw a CommandSyntaxException: |
- | public Collection< | + | * **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 '' |
- | } | + | * **'' |
- | } | + | |
- | </file> | + | |
- | ===== FAQ ===== | + | |
- | === What else can I send feedback to the CommandSource? === | + | ===== Can I register client side commands? ===== |
- | You can choose between Brigadier' | + | Fabric has a '' |
- | + | ||
- | === 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 in run time? === | + | |
- | + | ||
- | You can do this but it is not reccomended. You would get the instance of the CommandManager and add anything you wish to the CommandDispatcher within it. | + | |
- | + | ||
- | After that you will need to send the command tree to every player again using '' | + | |
- | + | ||
- | === Can I unregister commands in run time? === | + | |
- | + | ||
- | 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' | + | |
- | https:// | + | |
- | + | ||
- | If you only want the command to only be visible on the integrated server like ''/ | + | |
<code java> | <code java> | ||
- | dispatcher.register(literal(" | + | ClientCommandRegistrationCallback.EVENT.register((dispatcher, |
- | // The permission level 4 on integrated server is the equivalent of having cheats enabled. | + | .executes(context |
- | .requires(source | + | context.getSource().sendFeedback(Text.literal("The command is executed in the client!" |
+ | return 1; | ||
+ | } | ||
+ | | ||
</ | </ | ||
- | === I want to access X from my mod when a command | + | If you need to open a screen in the client |
- | This is going to require a way to statically access your mod with a '' | + | ===== Can I register commands |
- | <code java> | + | You can do this but it is not recommended. You would get the '' |
- | private static Type instance; | + | |
- | static { // Static option on class initalize for seperate API class for example | + | After that you need to send the command tree to every player again using '' |
- | | + | |
- | } | + | |
- | public void onInitalize() { // If within your mod initalizer | + | ===== Can I unregister commands in runtime? ===== |
- | | + | |
- | } | + | 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 the nodes. After this, you need to send the command tree to every player again using '' |
- | public static Type getInstance() { | ||
- | return instance; | ||
- | } | ||
- | </ |
tutorial/commands.txt · Last modified: 2024/02/23 14:22 by allen1210