User Tools

Site Tools


tutorial:commands

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Next revisionBoth sides next revision
tutorial:commands [2019/11/16 02:14] i509vcbtutorial:commands [2020/06/14 05:17] – Move old categories below FAQ to be migrated i509vcb
Line 1: Line 1:
 ====== Creating Commands ====== ====== Creating Commands ======
  
-Creating commands can allow a mod developer to add functionality that a player can use through a command.  +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 command structure of Brigadier along with some more advanced commands structures.+This tutorial will teach you how to register commands, and the general command structure of Brigadier.
  
-===== Registering Commands =====+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.
  
-If you just want to see how to register commands you've come to the right place here.+===== What is Brigadier? =====
  
-Registering commands is done through ''CommandRegistry'' with the ''register'' method.+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.
  
-The ''register'' method specifies two arguments, the dedicated flag and a consumer representing the ''CommandDispatcher''. These methods should be placed in your mod's initializer.+The source code for brigadier can be found here: https://github.com/Mojang/brigadier
  
-The dedicated flag if set to true will tell Fabric to only register the command on ''DedicatedServer'' (if false than the command will register on both the ''InternalServer'' and ''DedicatedServer'').+===== What is a command? =====
  
-Below are few examples of how the commands can be registered.+Brigadier requires you specify the ''Command'' to be run. A "command" is fairly loose term within brigadier, but typically it means an exit point of the command tree. This is where the code is executed for your command. 
 +A ''Command'' is a functional interface. The command has a generic type of ''S'' which defines the type of the command source. The command source provides some context in which a command was ran. In Minecraft, this is typically a ''ServerCommandSource'' which can represent a server, a command block, rcon connection, a player or an entity.
  
 +The single method in ''Command'', ''run(CommandContext<S>)'' takes a ''CommandContext<S>'' as the sole parameter and returns an integer. The command context holds your command source of ''S'' and allows you to obtain arguments, look at the parsed command nodes and see the input used in this command.
 +
 +A command can be implemented in several ways as shown below:
 +
 +**__As a lambda__**
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-CommandRegistry.INSTANCE.register(false, dispatcher -TutorialCommands.register(dispatcher)); // All commands are registered in a single class that references every command+Command<Object> command = context -> { 
-  +    return 0
-CommandRegistry.INSTANCE.register(false, dispatcher -> { // You can also just reference every single class also. There is also the alternative of just using CommandRegistry +};
-    TutorialCommand.register(dispatcher); +
-    TutorialHelpCommand.register(dispatcher); +
-}); +
-  +
-CommandRegistry.INSTANCE.register(true, dispatcher -> { // Or directly registering the command to the dispatcher. +
- dispatcher.register(LiteralArgumentBuilder.literal("tutorial").executes(ctx -> execute(ctx)))+
-});+
 </code> </code>
  
-==== A very basic command ====+**__As an anonymous class__** 
 +<code java [enable_line_numbers="true"]> 
 +Command<Object> command = new Command<Object>() { 
 +    @Override 
 +    public int run(CommandContext<Object> context) { 
 +        return 0; 
 +    } 
 +
 +</code>
  
-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 command.+**__Implemented as class__** 
 +<code java [enable_line_numbers="true"]> 
 +final class XYZCommand implements Command<Object>
 +    @Override 
 +    public int run(CommandContext<Object> context) { 
 +        return 0; 
 +    } 
 +
 +</code>
  
 +**__As a method reference__**
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-// The root of the command. This must be a literal argument+void registerCommand() { 
-dispatcher.register(CommandManager.literal("foo")  +    // Ignore this for now, we will explain it next
-// Then add an argument named bar that is an integer +    dispatcher.register(CommandManager.literal("foo")
-    .then(CommandManager.argument("bar", integer()+        .executes(this::execute)// This refers to the "executemethod below
- // The command to be executed if the command "foois entered with the argument "bar" +} 
-        .executes(ctx -> {  + 
-            System.out.println("Bar is " + IntArgumentType.getInteger(ctx, "bar")); +private int execute(CommandContext<Objectcontext) 
-     // Return a result. -1 is failure, 0 is a pass and 1 is success. +    return 0
-            return 1; +}
-            })) +
-    // The command "foo" to execute if there are no arguments. +
-    .executes(ctx -> {  +
-        System.out.println("Called foo with no arguments"); +
-        return 1+
-    }+
-);+
 </code> </code>
  
-The main process registers the command "foo" (Root Node) with an optional argument of "bar" (Child node).  
-Since the root node must be literal, The sender must enter the exact same sequence of letters to execute the command, so "Foo", "fOo" or "fooo" will not execute the command. 
  
-===== Brigadier Explained =====+The ''run(CommandContext)'' method can throw a ''CommandSyntaxException'', but this is covered later in the tutorial. 
  
-Brigadier starts with the ''CommandDispatcher'' which should be thought more as a tree rather than a list of methods.  +The integer can be considered the result of the commandIn Minecraft, the result can correspond to the power of a redstone comparator feeding from a command block or the value that will be passed the chain command block the command block is facing. Typically negative values mean a command has failed and will do nothing. A result of ''0'' means the command has passed. Positive values mean the command was successful and did something.
-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 can be seen at the leaves of the tree where it ends and also supplies the outcome of the system.+
  
-The execute blocks specify the command to be ran. As Brigadier's Command is a FunctionalInterface you can use lambdas to specify commands.+===== A basic command =====
  
-==== CommandContexts ====+Below is a command that contains no arguments:
  
-When a command is ran, Brigadier provides a CommandContext to the command that is ran.  +<code java [enable_line_numbers="true"]> 
-The CommandContext contains all arguments and other objects such as the inputted String and the ''Command Source'' (ServerCommandSource in Minecraft's implementation).+dispatcher.register(CommandManager.literal("foo").executes(context -> {  
 +    System.out.println("Called foo with no arguments");
  
-==== Arguments ====+    return 1; 
 +})); 
 +</code>
  
-The arguments in Brigadier both parse and error check any inputted arguments.  +''CommandManager.literal("foo")'' tells brigadier this command has one node, a **literal** called ''foo''
-Minecraft creates some special arguments for it's own use such as the ''EntityArgumentType'' which represents the in-game entity selectors ''@a@r, @p@e[type=!player, limit=1, distance=..2]'', or an ''NBTTagArgumentType'' that parses NBT and verifies that the input is the correct syntax.+To execute this command, one must type ''/foo''. If ''/Foo'', ''/FoO''''/FOO'', ''/fOO'' or ''/fooo'' is typed instead, the command will not run.
  
-You could do the long method of typing ''CommandManager.literal("foo")'' and it would workbut you can statically import the arguments and shorten that to ''literal("foo")''.  +==== A sub command ==== 
-This also works for getting arguments, which shortens the already long ''StringArgumentType.getString(ctx, "string")'' to ''getString(ctx, "string")''+ 
-This also works for Minecraft's arguments.+To add a sub command, you register the first literal node of the command normally.
  
-And your imports would look something like this: 
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-import static com.mojang.brigadier.arguments.StringArgumentType.getString; // getString(ctx, "string"+dispatcher.register(CommandManager.literal("foo")
-import static com.mojang.brigadier.arguments.StringArgumentType.word; // word(), string(), greedyString() +
-import static net.minecraft.server.command.CommandManager.literal; // literal("foo") +
-import static net.minecraft.server.command.CommandManager.argument; // argument("bar", word()) +
-import static net.minecraft.server.command.CommandManager.*; // Import everything+
 </code> </code>
  
-Brigadier's default arguments are located in ''com.mojang.brigadier.arguments''+In order to have a sub command, one needs to append the next node to the existing node. This is done use the ''then(ArgumentBuilder)'method which takes in an ''ArgumentBuilder''.
  
-Minecraft's arguments hide in ''net.minecraft.command.arguments'' and the CommandManager is at ''net.minecraft.server.command''+This creates the command ''foo <bar>'' as shown below.
  
-==== Suggestions ====+<code java [enable_line_numbers="true", highlight_lines_extra="2"]> 
 +dispatcher.register(CommandManager.literal("foo"
 +    .then(CommandManager.literal("bar")) 
 +); 
 +</code>
  
-Suggestions can be provided to the client to recommend what to input into the command.  This is used for Scoreboards and Loot Tables ingame. The game stores these in the SuggestionProvidersA few examples of Minecraft's built in suggestions providers are below +It is advised to indent your code as you add nodes to the command. Usually the indentation corresponds to how many nodes deep one is on the command tree. The new line also makes it visible that another node is being added. There are alternative styles to formatting the tree command that are shown later on in this tutorial. 
-<code> + 
-SUMMONABLE_ENTITIES +**So let's try running the command** 
-AVAILIBLE_SOUNDS + 
-ALL_RECIPES +Most likely if you typed ''/foo bar'' in game, the command will fail to run. This is because there is no code for the game to execute when all the required arguments have been metTo fix this, you need to tell the game what to run when the command is being executed using the ''executes(Command)'' method. Below is how the command should look as an example. 
-ASK_SERVER+ 
 +<code java [enable_line_numbers="true", highlight_lines_extra="3,4,5,6,7"]
 +dispatcher.register(CommandManager.literal("foo"
 +    .then(CommandManager.literal("bar"
 +        .executes(context -> { 
 +            System.out.println("Called foo with bar"); 
 + 
 +            return 1; 
 +        }) 
 +    ) 
 +);
 </code> </code>
  
-Loot tables specify their own SuggestionProvider inside LootCommand for example.+===== Registering the commands ===== 
 + 
 +Registering commands is done by registering a callback using the ''CommandRegistrationCallback''For information on registering callbacks, please see the [[tutorial:callbacks|callbacks article]]. 
 + 
 +The event should be registered in your mod's initializer. The callback has two parameters. The ''CommmandDispatcher<S>'' is used to register, parse and execute commands. ''S'' is the type of command source the command dispatcher supports. The second parameter is a boolean which identifies the type of server the commands are being registered. on is an ''dedicated'' or ''integrated'' (false) server. 
  
-The example below is a dynamically changing SuggestionProvider that lists several words for a StringArgumentType to demonstrate how it works: 
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-public static SuggestionProvider<ServerCommandSource> suggestedStrings() { +public class ExampleCommandMod implements ModInitializer { 
-    return (ctxbuilder) -> getSuggestionsBuilder(builder, /*Access to a list here*/);+    @Override 
 +    public void onInitialize() { 
 +        CommandRegistrationCallback.EVENT.register((dispatcherdedicated) -> 
 +            ... 
 +        }); 
 +    }
 } }
-     +</code> 
-private static CompletableFuture<SuggestionsgetSuggestionsBuilder(SuggestionsBuilder builderList<Stringlist) + 
-    String remaining = builder.getRemaining().toLowerCase(Locale.ROOT); +Inside your lambdamethod reference or whatever you have chosen, you will register your commands. 
-         + 
-    if(list.isEmpty()// If the list is empty then return no suggestions +<code java [enable_line_numbers="true", highlight_lines_extra="5,6,7,8"]> 
-        return Suggestions.empty(); // No suggestions+public class ExampleCommandMod implements ModInitializer 
 +    @Override 
 +    public void onInitialize() 
 +        CommandRegistrationCallback.EVENT.register((dispatcher, dedicated-> { 
 +            dispatcher.register(CommandManager.literal("foo").executes(context -> 
 +                System.out.println("foo"); 
 +                return 1; 
 +            })); 
 +        });
     }     }
-         +
-    for (String str : list) { // Iterate through the supplied list +</code> 
-        if (str.toLowerCase(Locale.ROOT).startsWith(remaining)) { + 
-            builder.suggest(str); // Add every single entry to suggestions list. +If desired, you can make sure a command is only registered on a dedicated server by checking the ''dedicated'' flag 
-        }+ 
 + 
 +<code java [enable_line_numbers="true", highlight_lines_extra="5,6,7"]> 
 +public class ExampleCommandMod implements ModInitializer { 
 +    @Override 
 +    public void onInitialize() { 
 +        CommandRegistrationCallback.EVENT.register((dispatcher, dedicated-> 
 +            if (dispatcher) { 
 +                TestDedicatedCommand.register(dispatcher); 
 +            } 
 +        });
     }     }
-    return builder.buildFuture(); // Create the CompletableFuture containing all the suggestions 
 } }
 </code> </code>
  
-The SuggestionProvider is a FunctionalInterface that returns a CompletableFuture containing a list of suggestions. These suggestions are given to client as a command is typed and can be changed while server is running. The SuggestionProvider provides a CommandContext and a SuggestionBuilder to determine all the suggestions. The CommandSource can also be taken into account during the suggestion creation process as it is available through the CommandContext.+And vice versa
  
-Though remember these are suggestionsThe inputted command may not contain an argument you suggested so you still have to parse check inside the command to see if the argument is what you want if it's a String for example and parsers may still throw exceptions if an invalid syntax is inputted.+<code java [enable_line_numbers="true", highlight_lines_extra="5,6,7"]> 
 +public class ExampleCommandMod implements ModInitializer { 
 +    @Override 
 +    public void onInitialize() { 
 +        CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { 
 +            if (!dispatcher) { 
 +                TestIntegratedCommand.register(dispatcher); 
 +            } 
 +        }); 
 +    } 
 +
 +</code>
  
-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.+===== 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 ''EntityArgumentType'' which represents the in-game entity selectors ''@a, @r, @p, @e[type=!player, limit=1, distance=..2]'', or an ''NbtTagArgumentType'' that parses stringified nbt (snbt) and verifies that the input is the correct syntax.
 +
 +**TODO:** Go into more detail on how to use arguments
 +
 +===== Static Imports =====
 +You could type out ''CommandManager.literal("foo")'' every time you want to create a literal. This works, but you can statically import the arguments and shorten the statement to ''literal("foo")''. This also works for getting the value of an argument. This shortens ''StringArgumentType.getString(ctx, "string")'' to ''getString(ctx, "string")''.
 +This also works for Minecraft's own argument types.
 +
 +And your imports would look something like this:
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-argument(argumentName, word()+// getString(ctx"string"
-.suggests(CompletionProviders.suggestedStrings()) +import static com.mojang.brigadier.arguments.StringArgumentType.getString; 
-    .then(/*Rest of the command*/));+// word() 
 +import static com.mojang.brigadier.arguments.StringArgumentType.word; 
 + // literal("foo"
 +import static net.minecraft.server.command.CommandManager.literal; 
 + // argument("bar", word()) 
 +import static net.minecraft.server.command.CommandManager.argument; 
 +// Import everything 
 +import static net.minecraft.server.command.CommandManager.*;
 </code> </code>
  
-==== Requires ====+Note: Please be sure you use the ''literal'' and ''argument'' from CommandManager or you may have issues with generics when trying to compile. 
 + 
 +Brigadier's default arguments are at ''com.mojang.brigadier.arguments'' 
 + 
 +Minecraft's arguments are in ''net.minecraft.command.arguments''
 +CommandManager is in ''net.minecraft.server.command'' 
 + 
 +====== Advanced concepts ====== 
 + 
 +Below are links to the articles about more complex concepts used in brigadier. 
 + 
 +^ Page                                                           ^ Description                                                                     ^ 
 +| [[tutorials:commands:requirements|Requirements]]               | Only allow users to execute commands in certain scenarios.                      | 
 +| [[tutorials:commands:exceptions  |Exceptions]]                 | Fail execution of a command with a descriptive message and in certain contexts. |                                                                   
 +| [[tutorials:commands:suggestions |Suggestions]]                | Suggesting input to be sent to the client.                                      | 
 +| [[tutorials:commands:redirects_aliases|Redirects (Aliases)]]   | Allow use of aliases to execute commands.                                       | 
 +| [[tutorials:commands:redirects_chaining|Redirects (Chaining)]] | Allow commands to have repeating elements and flags.                            | 
 +| [[tutorials:commands:argument_types|Custom Argument Types]]    | Parse your own arguments and return your own types.                             | 
 + 
 +**TODO:** Sections are being moved to sub categories and will be added to their respective articles as they are migrated. 
 + 
 +====== FAQ ====== 
 + 
 +===== Why does my command not compile ===== 
 + 
 +There are two immediate possibilities for why this could occur. 
 + 
 +==== Catch or throw a CommandSyntaxException ==== 
 + 
 +The solution to this issue is to make the run or suggest methods throw a CommandSyntaxException. Don't worry, brigadier will handle the exceptions and output the proper error message. 
 + 
 +==== Issues with generics ==== 
 + 
 +You may have an issue with generic types once in a while. Verify you are using ''CommandManager.literal(...)'' or ''CommandManager.argument(...)'' instead ''LiteralArgumentBuilder'' or ''RequiredArgumentBuilder''
 + 
 +===== Can I register client side commands? ===== 
 + 
 +Fabric doesn't currently support client side commands. There is a [[https://github.com/CottonMC/ClientCommands|third-party mod]] by the Cotton team that adds this functionality. 
 + 
 +===== Dark Arts ===== 
 + 
 +A few things we don't recommend, but are possible. 
 + 
 +==== Can I register commands in runtime? ==== 
 + 
 +You can do this but it is not recommended. You would get the ''CommandManager'' from the server and add anything commands you wish to it's ''CommandDispatcher''
 + 
 +After that you need to send the command tree to every player again using ''sendCommandTree(ServerPlayerEntity)''. This is required because the client locally caches the command tree it receives during login (or when operator packets are sent) for local completions rich error messages. 
 + 
 +==== 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 ''sendCommandTree(ServerPlayerEntity)''. If you don't send the updated command tree, the client may think a command still exists, even though the server will fail execution. 
 + 
 +---- 
 + 
 +====== Sorry for the mess ====== 
 + 
 +**__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. 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.
Line 154: Line 283:
 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. 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.
  
-==== Exceptions ====+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.
  
-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. +===== Exceptions =====
  
-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)''. Though this can only be used with a custom parser. These can be defined and thrown under certain scenarios during the command. +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. Below is a coin flip command to show an example of exceptions in use.
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
 dispatcher.register(CommandManager.literal("coinflip") dispatcher.register(CommandManager.literal("coinflip")
- .executes(ctx -> { +    .executes(ctx -> { 
- Random random = new Random();+        Random random = new Random();
   
- if(random.nextBoolean()) { // If heads succeed. +        if(random.nextBoolean()) { // If heads succeed. 
- ctx.getSource().sendMessage(new TranslateableText("coin.flip.heads")) +            ctx.getSource().sendMessage(new TranslateableText("coin.flip.heads")) 
- return Command.SINGLE_SUCCESS; +            return Command.SINGLE_SUCCESS; 
- +        
- throw new SimpleCommandExceptionType(new TranslateableText("coin.flip.tails")).create(); // Oh no tails, you lose. + 
- }));+        throw new SimpleCommandExceptionType(new TranslateableText("coin.flip.tails")).create(); // Oh no tails, you lose. 
 +    }));
 </code> </code>
  
-Though you are not just limited to a single type of exception as Brigadier also supplies Dynamic exceptions.+Though you are not just limited to a single type of exception as Brigadier also supplies Dynamic exceptions which take additional parameters for context.
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
 DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> { DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> {
- return new LiteralText("The name: " + (String) name + " has been used");+    return new LiteralText("The name: " + (String) name + " has been used");
 }); });
 </code> </code>
Line 185: Line 317:
 You should remember that the Dynamic exceptions takes an object as an argument so you may have to cast the argument for your use. 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 (Aliases) =====
  
 Redirects are Brigadier's form of aliases. Below is how Minecraft handles /msg have an alias of /tell and /w.  Redirects are Brigadier's form of aliases. Below is how Minecraft handles /msg have an alias of /tell and /w. 
Line 193: Line 325:
     LiteralCommandNode node = registerMain(dispatcher); // Registers main command     LiteralCommandNode node = registerMain(dispatcher); // Registers main command
     dispatcher.register(literal("tell")     dispatcher.register(literal("tell")
- .redirect(node)); // Alias 1, redirect to main command+        .redirect(node)); // Alias 1, redirect to main command
     dispatcher.register(literal("w")     dispatcher.register(literal("w")
- .redirect(node)); // Alias 2, redirect to main command+        .redirect(node)); // Alias 2, redirect to main command
 } }
  
 public static LiteralCommandNode registerMain(CommandDispatcher<ServerCommandSource> dispatcher) { public static LiteralCommandNode registerMain(CommandDispatcher<ServerCommandSource> dispatcher) {
     return dispatcher.register(literal("msg")     return dispatcher.register(literal("msg")
- .then(argument("targets", EntityArgumentType.players()) +    .then(argument("targets", EntityArgumentType.players()) 
-     .then(argument("message", MessageArgumentType.message()) +        .then(argument("message", MessageArgumentType.message()) 
- .executes(ctx -> { +            .executes(ctx -> { 
-     return execute(ctx.getSource(), getPlayers(ctx, "targets"), getMessage(ctx, "message")); +                return execute(ctx.getSource(), getPlayers(ctx, "targets"), getMessage(ctx, "message")); 
- }))));+            }))));
 } }
 </code> </code>
  
-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 argument that the redirect is placed on will also rename first literal on the targeted branch in the new command.+The redirect tells brigadier to continue parsing the command at another command node.
  
-Redirects do not work in shortened aliases such as ''/mod thing <argument>'' having an alias of ''/thing <argument>'' as Brigadier does not allow forwarding nodes with children. Though you could use alternative methods to reduce the amount of duplicate code for this case. +===== Redirects (Chainable Commands) =====
- +
-==== 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: 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:
  
Line 221: Line 351:
     .then(literal("extra")     .then(literal("extra")
         .then(literal("long")         .then(literal("long")
-            .redirect(root)) // Return to root for chaining+            .redirect(root, this::lengthen)) // Return to root for chaining
         .then(literal("short")         .then(literal("short")
-            .redirect(root))) // Return to root for chaining+            .redirect(root, this::shorten))) // Return to root for chaining
         .then(literal("command")         .then(literal("command")
             .executes(ctx -> {             .executes(ctx -> {
Line 230: Line 360:
 }))); })));
 </code> </code>
-The redirect can also modify the CommandSource.+The redirect can also modify the CommandSource by use of a ''redirect modifier'' which can be used for builder commands.
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-.redirect(rootNode, (commandContext_1x) -> { +.redirect(rootNode, context -> { 
-    return ((ServerCommandSource) commandContext_1x.getSource()).withLookingAt(Vec3ArgumentType.getVec3(commandContext_1x, "pos"));+    return ((ServerCommandSource) context.getSource()).withLookingAt(Vec3ArgumentType.getVec3(context, "pos"));
 }) })
 </code> </code>
  
-===== ServerCommandSource =====+===== What can the ServerCommandSource do? =====
  
-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+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.
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-ServerCommandSource source = ctx.getSource(); +final ServerCommandSource source = ctx.getSource(); 
 // Get the source. This will always work. // Get the source. This will always work.
  
-Entity sender = source.getEntity(); +final Entity sender = source.getEntity(); 
 // Unchecked, may be null if the sender was the console. // Unchecked, may be null if the sender was the console.
  
-Entity sender2 = source.getEntityOrThrow(); +final Entity sender2 = source.getEntityOrThrow(); 
 // Will end the command if the source of the command was not an Entity.  // 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.  // The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity. 
Line 255: Line 385:
 // The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player. // The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player.
  
-ServerPlayerEntity player = source.getPlayer(); +final 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.  This method will require your methods to throw a CommandSyntaxException // 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
-</code> 
  
-The ServerCommandSource also provides other information about the sender of the command. 
- 
-<code java [enable_line_numbers="true"]> 
 source.getPosition();  source.getPosition(); 
 // 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. // 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.
Line 281: Line 407:
 </code> </code>
  
-===== Some actual examples ===== +===== Some example commands examples =====
- +
-Just a few to show:+
  
 === Broadcast a message === === Broadcast a message ===
Line 290: Line 414:
 public static void register(CommandDispatcher<ServerCommandSource> dispatcher){ public static void register(CommandDispatcher<ServerCommandSource> dispatcher){
     dispatcher.register(literal("broadcast")     dispatcher.register(literal("broadcast")
- .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 op'or any op that is permission level 1. +        .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("color", ColorArgumentType.color()) +            .then(argument("color", ColorArgumentType.color()) 
- .then(argument("message", greedyString()) +                .then(argument("message", greedyString()) 
-     .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.+                    .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.
 } }
  
 public static int broadcast(ServerCommandSource source, Formatting formatting, String message) { public static int broadcast(ServerCommandSource source, Formatting formatting, String message) {
-    Text text = new LiteralText(message).formatting(formatting);+    final Text text = new LiteralText(message).formatting(formatting);
  
     source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, false);     source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, false);
Line 304: Line 428:
 </code> </code>
  
-=== /giveMeDiamond ===+==== /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. First the basic code where we register "giveMeDiamond" as a literal and then an executes block to tell the dispatcher which method to run.
Line 319: Line 443:
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
 public static int giveDiamond(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException { public static int giveDiamond(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException {
-    ServerCommandSource source = ctx.getSource();+    final ServerCommandSource source = ctx.getSource();
   
-    PlayerEntity self = source.getPlayer(); // If not a player than the command ends+    final PlayerEntity self = source.getPlayer(); // If not a player than the command ends
 </code> </code>
  
Line 328: Line 452:
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
     if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){     if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){
-        throw new SimpleCommandExceptionType(new TranslateableText("inventory.isfull")).create(); +        throw new SimpleCommandExceptionType(new TranslatableText("inventory.isfull")).create(); 
-    } +    } 
     return 1;     return 1;
 } }
 </code> </code>
  
-=== Antioch ===+==== Antioch ====
 ...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe. ...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe.
 who being naughty in My sight, shall snuff it. who being naughty in My sight, shall snuff it.
Line 346: Line 471:
     dispatcher.register(literal("antioch")     dispatcher.register(literal("antioch")
         .then(required("location", BlockPosArgumentType.blockPos()         .then(required("location", BlockPosArgumentType.blockPos()
-     .executes(ctx -> antioch(ctx.getSource(), BlockPosArgument.getBlockPos(ctx, "location"))))) +            .executes(ctx -> antioch(ctx.getSource(), BlockPosArgument.getBlockPos(ctx, "location"))))) 
- .executes(ctx -> antioch(ctx.getSource(), null)));+        .executes(ctx -> antioch(ctx.getSource(), null)));
 } }
 </code> </code>
Line 354: Line 479:
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-public static int antioch(ServerCommandSource source, BlockPos blockPos) throws CommandSyntaxException {  +public static int antioch(ServerCommandSource source, BlockPos blockPos) throws CommandSyntaxException { 
-  +    if(blockPos == null) { 
-    if(blockPos==null) { +        // 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. 
-        blockPos = LocationUtil.calculateCursorOrThrow(source, source.getRotation()); // 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. This class is used as an example and actually doesn't exist yet.+        // This class is used as an example and actually doesn't exist yet. 
 +        blockPos = LocationUtil.calculateCursorOrThrow(source, source.getRotation());
     }     }
-  + 
-    TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ(), null);+    final TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ(), null); 
 +    tnt.setFuse(3);
                  
     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("...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe").formatting(Formatting.RED), false);
Line 369: Line 496:
 </code> </code>
  
-=== Finding Biomes via Command ===+==== More examples coming soon ====
  
-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 ''/locate'' +===== Custom Argument Types =====
-<code java [enable_line_numbers="true"]> +
-public class CommandLocateBiome { +
-    // First make method to register  +
-    public static void register(CommandDispatcher<ServerCommandSource> dispatcher) { +
-        LiteralCommandNode<ServerCommandSource> basenode dispatcher.register(literal("findBiome"+
-                .then(argument("biome_identifier", identifier()).suggests(BiomeCompletionProvider.BIOMES) // We use Biome suggestions for identifier argument +
-                        .then(argument("distance", integer(0, 20000)) +
-                                .executes(ctx -> execute(ctx.getSource(), getIdentifier(ctx, "biome_identifier"), getInteger(ctx, "distance")))) +
-                        .executes(ctx -> execute(ctx.getSource(), getIdentifier(ctx, "biome_identifier"), 1000)))); +
-        // Register redirect +
-        dispatcher.register(literal("biome"+
-                .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("biome.not.exist", biomeId)).create(); +
-        } +
-         +
-        List<Biome> bio new ArrayList<Biome>(); +
-        bio.add(biome); +
-         +
-        ServerWorld world source.getWorld(); +
-         +
-        BiomeSource bsource world.getChunkManager().getChunkGenerator().getBiomeSource(); +
-         +
-        BlockPos loc new BlockPos(source.getPosition()); +
-        // Now here is the heaviest part of the method. +
-        BlockPos pos bsource.locateBiome(loc.getX(), loc.getZ(), range, bio, new Random(world.getSeed())); +
-         +
-        // Since this method can return null if it failed to find a biome +
-        if(pos == null) { +
-            throw new SimpleCommandExceptionType(new TranslatableText("biome.notfound", biome.getTranslationKey())).create(); +
-        } +
-         +
-        int distance = MathHelper.floor(getDistance(loc.getX(), loc.getZ(), pos.getX(), pos.getZ())); +
-        // Popup text that can suggest commands. This is the exact same system that /locate uses. +
-        Text teleportButtonPopup = Texts.bracketed(new TranslatableText("chat.coordinates", new Object[] { pos.getX(), "~", pos.getZ()})).styled((style_1x) -> { +
-            style_1x.setColor(Formatting.GREEN).setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/tp @s " + pos.getX() + " ~ " + pos.getZ())).setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableText("chat.coordinates.tooltip", new Object[0]))); +
-        }); +
-         +
-        source.sendFeedback(new TranslatableText("commands.locate.success", new Object[] { new TranslatableText(Registry.BIOME.get(biomeId).getTranslationKey()), teleportButtonPopup, distance}), false);+
  
-        return 1; +Brigadier has support for custom argument types and this section goes into showing how to create simple argument type
-    } +
-    // Just 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)); +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.
-    } +
-     +
-    +
  
-    public static class BiomeCompletionProvider { +For this example we will create a UuidArgumentType.
-        // 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<ServerCommandSource> BIOMES = SuggestionProviders.register(new Identifier("biomes"), (ctx, builder) -> { +
-            Registry.BIOME.getIds().stream().forEach(identifier -> builder.suggest(identifier.toString(), new TranslatableText(Registry.BIOME.get(identifier).getTranslationKey()))); +
-            return builder.buildFuture(); +
-        }); +
-         +
-    } +
-</code> +
- +
-===== Custom Arguments ===== +
- +
-Brigadier has support for custom argument types and this section goes into showing how to create a simple argument type. 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 First create a class which extends ''ArgumentType''. Note that ArgumentType is a generic, so the generic will define what type the ArgumentType will return
  
-<code java> +<code java [enable_line_numbers="true"]
-public class UUIDArgumentType implements ArgumentType<UUID> {+public class UuidArgumentType implements ArgumentType<UUID> {
 </code> </code>
  
Line 459: Line 522:
 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. 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.
  
-<code java>+<code java [enable_line_numbers="true"]>
 int argBeginning = reader.getCursor(); // The starting position of the cursor is at the beginning of the argument. int argBeginning = reader.getCursor(); // The starting position of the cursor is at the beginning of the argument.
 if (!reader.canRead()) { if (!reader.canRead()) {
Line 468: Line 531:
 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. 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.
  
-<code java>+<code java [enable_line_numbers="true"]>
 while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position. 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.     reader.skip(); // Tells the StringReader to move it's cursor to the next position.
Line 476: Line 539:
 Then we will ask the StringReader what the current position of the cursor is an substring our argument out of the command line. Then we will ask the StringReader what the current position of the cursor is an substring our argument out of the command line.
  
-<code java>String uuidString = reader.getString().substring(argBeginning, reader.getCursor());</code>+<code java [enable_line_numbers="true"]>String uuidString = reader.getString().substring(argBeginning, reader.getCursor());</code>
  
 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. 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>+<code java [enable_line_numbers="true"]>
 try { try {
     UUID uuid = UUID.fromString(uuidString); // Now our actual logic.     UUID uuid = UUID.fromString(uuidString); // Now our actual logic.
Line 492: Line 555:
 </code> </code>
  
-The ArgumentType is done, however your client will refuse the parse the argument and throw an error. To fix this we need to register an ArgumentSerializer.  +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 (Not only client or server) you will add this so the client can recognize the argument when the command tree is sent. For more complex argument types, you may need to create your own ArgumentSerializer.+Within your ModInitializer. For more complex argument types, you may need to create your own ArgumentSerializer.
  
-<code java> +<code java [enable_line_numbers="true"]
-ArgumentTypes.register("mymod:uuid", UUIDArgumentType.class, new ConstantArgumentSerializer(UUIDArgumentType::uuid)); +ArgumentTypes.register("mymod:uuid", UuidArgumentType.class, new ConstantArgumentSerializer(UuidArgumentType::uuid)); 
 // The argument should be what will create the ArgumentType. // The argument should be what will create the ArgumentType.
 </code> </code>
Line 502: Line 565:
 And here is the whole ArgumentType: And here is the whole ArgumentType:
  
-<file java UUIDArgumentType.java>+<file java UuidArgumentType.java [enable_line_numbers="true"]>
  
 import com.mojang.brigadier.StringReader; import com.mojang.brigadier.StringReader;
Line 519: Line 582:
  * Represents an ArgumentType that will return a UUID.  * Represents an ArgumentType that will return a UUID.
  */  */
-public class UUIDArgumentType implements ArgumentType<UUID>// ArgumentType has a generic, which is the return type of the +public class UuidArgumentType implements ArgumentType<UUID>
-    // This method exists for convince and you could just initialize the class. +    public static UuidArgumentType uuid() { 
-    public static UUIDArgumentType uuid() { +        return new UuidArgumentType();
-        return new UUIDArgumentType();+
     }     }
  
-    // This is also for convince and you could always just grab the argument from the CommandContext. 
     public static <S> UUID getUuid(String name, CommandContext<S> context) {     public static <S> UUID getUuid(String name, CommandContext<S> context) {
         // Note that you should assume the CommandSource wrapped inside of the CommandContext will always be a generic type.         // 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.+        // If you need to access the ServerCommandSource make sure you verify the source is a server command source before casting.
         return context.getArgument(name, UUID.class);         return context.getArgument(name, UUID.class);
     }     }
Line 565: Line 626:
  
     @Override     @Override
-    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.+    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 
         return EXAMPLES;         return EXAMPLES;
     }     }
 } }
 </file> </file>
-===== FAQ ===== 
- 
-=== What else can I send feedback to the CommandSource? === 
- 
-You can choose between Brigadier's default LiteralMessage or use any one of Minecraft's 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 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 ''CommandManager.sendCommandTree(PlayerEntity)'' 
- 
-=== 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 ''CommandManager.sendCommandTree(PlayerEntity)'' afterwards. 
- 
-=== Can I register client side commands? === 
- 
-Well Fabric currently doesn't support this natively but there is a mod by the Cotton team that adds this functionality where the commands do not run on the server and only on the client: 
-https://github.com/CottonMC/ClientCommands 
- 
-If you only want the command to only be visible on the integrated server like ''/publish'' then you would modify your requires block: 
- 
-<code java> 
-dispatcher.register(literal("publish") 
-    // The permission level 4 on integrated server is the equivalent of having cheats enabled. 
-    .requires(source -> source.getMinecraftServer().isSinglePlayer() && source.hasPermissionLevel(4))); 
-</code> 
- 
-=== I want to access X from my mod when a command is ran === 
- 
-This is going to require a way to statically access your mod with a ''getInstance'' call. Below is a very simple instance system you can place in your mod 
- 
-<code java> 
-private static Type instance; 
- 
-static { // Static option on class initalize for seperate API class for example 
-   instance = new Type(); 
-} 
- 
-public void onInitalize() { // If within your mod initalizer 
-   instance = this; 
-} 
- 
-public static Type getInstance() { 
-    return instance; 
-} 
-</code> 
tutorial/commands.txt · Last modified: 2024/02/23 14:22 by allen1210