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
tutorial:commands [2023/11/18 10:02] – [Requirements] solidblocktutorial:commands [2024/02/23 14:22] (current) – Documenting and first draft allen1210
Line 4: Line 4:
  
 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 command structure of Brigadier. 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 command structure of Brigadier.
- 
-Note: All code written here was written for 1.19.2. For old versions, some versions and mappings may differ. 
  
 ===== What is Brigadier? ===== ===== What is Brigadier? =====
Line 26: Line 24:
 }; };
 </code> </code>
- 
-In vanilla Minecraft, they are usually used as method references, such as static methods named ''register'' under classes named ''XXXCommand''. 
  
 The integer can be considered the result of the command. 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 integer can be considered the result of the command. 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.
Line 100: Line 96:
         .executes(context -> {         .executes(context -> {
       // For versions below 1.19, replace "Text.literal" with "new LiteralText".       // For versions below 1.19, replace "Text.literal" with "new LiteralText".
-      context.getSource().sendMessage(Text.literal("Called /foo with no arguments")); +      // For versions below 1.20, remode "() ->" directly
-      // For versions since 1.20, please use the following, which is intended to avoid creating Text objects if no feedback is needed+      context.getSource().sendFeedback(() -> Text.literal("Called /foo with no arguments"), false);
-      context.getSource().sendMessage(() -> Text.literal("Called /foo with no arguments"));+
  
       return 1;       return 1;
Line 110: Line 105:
 </code> </code>
  
-''CommandManager.literal("foo")'' tells brigadier this command has one node, a **literal** called ''foo''.+**Please ensure you import the correct static method.** The method ''literal'' is ''CommandManager.literal''. You can also alternatively explicitly write ''CommandManager.literal'' instead of using static imports. ''CommandManager.literal("foo")'' tells brigadier this command has one node, a **literal** called ''foo''
 + 
 +In the ''sendFeedback'' method, the first parameter is the text to be sent, which is a ''Text'' in versions below 1.20, or a ''Supplier<Text>'' in 1.20 and above (this is used to avoid instantiating ''Text'' objects when not needed, so please do not use ''Suppliers.ofInstance'' or smiliar methods). The second parameter determines whether to broadcast the feedback to other operators. If the command is to //query// something without actually affecting the world, such as query the current time or some player's score, it should be ''false''. If the command actually //does// something, such as changing the time or modifying someone's score, it should be ''true''. If game rule ''sendCommandFeedback'' is false, you will not accept any feedback. If the sender is modifed through ''/execute as ...'', the feedback is sent to the original sender. 
 + 
 +If the command fails, instead of calling ''sendFeedback'', you may directly throw a ''CommandSyntaxException'' or ''<yarn class_2164>''. See [[command_exceptions]] for details.
  
 To execute this command, you must type ''/foo'', which is case-sensitive. If ''/Foo'', ''/FoO'', ''/FOO'', ''/fOO'' or ''/fooo'' is typed instead, the command will not run. To execute this command, you must type ''/foo'', which is case-sensitive. If ''/Foo'', ''/FoO'', ''/FOO'', ''/fOO'' or ''/fooo'' is typed instead, the command will not run.
  
 +===== Registration environment =====
 If desired, you can also make sure a command is only registered under some specific circumstances, for example, only in the dedicated environment: If desired, you can also make sure a command is only registered under some specific circumstances, for example, only in the dedicated environment:
  
Line 160: Line 160:
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
 dispatcher.register(literal("foo") dispatcher.register(literal("foo")
-  .requires(source -> source.hasPermissionLevel(4))+  .requires(source -> source.hasPermissionLevel(2))
   .executes(ctx -> {   .executes(ctx -> {
     ctx.getSource().sendFeedback(() -> Text.literal("You are an operator"), false);     ctx.getSource().sendFeedback(() -> Text.literal("You are an operator"), false);
Line 167: Line 167:
 </code> </code>
  
-This command will only execute if the source of the command is a level operator at minimum. Otherwise, the command is not registered. Also this has the side effect of not showing this command in tab completion to anyone who is not a level operator. This is also why you cannot tab-complete most commands when you did not enable cheating.+This command will only execute if the source of the command is a level operator at minimum, //including// command blocks. Otherwise, the command is not registered. Also this has the side effect of not showing this command in tab completion to anyone who is not a level operator. This is also why you cannot tab-complete most commands when you did not enable cheating
 + 
 +To create commands that only level 4 operators (//not including// command blocks) can execute, use ''source.hasPermissionLevel(4)''.
  
 ===== Arguments ===== ===== Arguments =====
  
-Arguments in Brigadier both parse and error check any inputted arguments. +Arguments are used in most of commandsSometimes they can be optional, which means if you do not provide that argumentthe command will also runOne node may have multiple argument typesbut be aware that there is possibility of ambiguity, which should be avoided.
-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+In this case, we add one integer argument, and calculate the square of the integer.
  
-===== A sub command =====+<code java> 
 +    dispatcher.register(literal("mul"
 +        .then(argument("value", IntegerArgumentType.integer()) 
 +            .executes(context -> { 
 +              final int value IntegerArgumentType.getInteger(context, "value"); 
 +              final int result value * value; 
 +              context.getSource().sendFeedback(() -> Text.literal("%s × %s %s".formatted(value, value, result)), false); 
 +              return result; 
 +            }))); 
 +</code>
  
-To add a sub command, you register the first literal node of the command normally.+In this case, after the word ''/mul'', you should type an integer. For example, if you run ''/mul 3'', you will get the feedback message "3 × 3 = 9". If you type ''/mul'' without arguments, the command cannot be correctly parsed.
  
-<code java [enable_line_numbers="true"]+Note: for simplicity, ''IntegerArgumentType.integer'' and ''IntegerArgumentType.getInteger'' can be replaced with ''integer'' and ''getInteger'' with static import. This example does not use static imports, in order to be more explicit. 
-dispatcher.register(literal("foo"))+ 
 +Then we add an optional second argument: 
 +<code java
 +    dispatcher.register(literal("mul"
 +        .then(argument("value", IntegerArgumentType.integer()) 
 +            .executes(context -> { 
 +              final int value IntegerArgumentType.getInteger(context, "value"); 
 +              final int result = value * value; 
 +              context.getSource().sendFeedback(() -Text.literal("%s × %s = %s".formatted(value, value, result)), false); 
 +              return result; 
 +            }) 
 +            .then(argument("value2", IntegerArgumentType.integer()) 
 +                .executes(context -> { 
 +                  final int value = IntegerArgumentType.getInteger(context, "value"); 
 +                  final int value2 = IntegerArgumentType.getInteger(context, "value2"); 
 +                  final int result = value * value2; 
 +                  context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(value, value2, result)), false); 
 +                  return result; 
 +                }))));
 </code> </code>
  
-In order to have a sub command, one needs to append the next node to the existing nodeThis is done use the ''then(ArgumentBuilder)'' method which takes in an ''ArgumentBuilder''.+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 twiceTherefore, we can create a method that will be used in both executions.
  
-This creates the command ''foo <bar>'' as shown below.+<code java> 
 +public class ExampleMod implements ModInitializer { 
 +  @Override 
 +  public void onInitialize() { 
 +    CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> dispatcher.register(literal("mul"
 +        .then(argument("value", IntegerArgumentType.integer()) 
 +            .executes(context -> executeMultiply(IntegerArgumentType.getInteger(context, "value"), IntegerArgumentType.getInteger(context, "value"), context)) 
 +            .then(argument("value2", IntegerArgumentType.integer()) 
 +                .executes(context -> executeMultiply(IntegerArgumentType.getInteger(context, "value"), IntegerArgumentType.getInteger(context, "value2"), context)))))); 
 +  }
  
-<code java [enable_line_numbers="true"highlight_lines_extra="2"]+  private static int executeMultiply(int valueint value2, CommandContext<ServerCommandSourcecontext) { 
-dispatcher.register(literal("foo") +    final int result = value * value2; 
-    .then(literal("bar")) +    context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(value, value2, result)), false); 
-);+    return result; 
 +  } 
 +}
 </code> </code>
 +===== A sub command =====
  
-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.+To add a sub command, you register the first literal node of the command normally.
  
-**So let's try running the command**+<code> 
 +dispatcher.register(literal("foo")) 
 +</code>
  
-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 met. To fix thisyou 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.+In order to have a sub commandone needs to append the next node to the existing node
  
-<code java [enable_line_numbers="true", highlight_lines_extra="3,4,5,6,7"]>+This creates the command ''foo <bar>'' as shown below. 
 + 
 +<code java [enable_line_numbers="true"]>
 dispatcher.register(literal("foo") dispatcher.register(literal("foo")
     .then(literal("bar")     .then(literal("bar")
         .executes(context -> {         .executes(context -> {
             // For versions below 1.19, use ''new LiteralText''.             // For versions below 1.19, use ''new LiteralText''.
-            context.getSource().sendMessage(Text.literal("Called foo with bar"));+            // For versions below 1.20, use directly the ''Text'' object instead of supplier. 
 +            context.getSource().sendFeedback(() -> Text.literal("Called foo with bar"), false);
  
             return 1;             return 1;
Line 213: Line 258:
 </code> </code>
  
 +Similar to arguments, sub command nodes can also be set optional. In the following case, both ''/foo'' and ''/foo bar'' will be valid. 
 +<code java [enable_line_numbers="true"]> 
 +dispatcher.register(literal("foo"
 +    .executes(context -> { 
 +        context.getSource().sendFeedback(() -> Text.literal("Called foo without bar"), false); 
 +        return 1; 
 +    }) 
 +    .then(literal("bar"
 +        .executes(context -> { 
 +            context.getSource().sendFeedback(() -> Text.literal("Called foo with bar"), false); 
 +            return 1; 
 +        }) 
 +    ) 
 +); 
 +</code>
 ====== Advanced concepts ====== ====== Advanced concepts ======
  
Line 227: Line 286:
 ====== FAQ ====== ====== FAQ ======
  
-===== Why does my command not compile =====+===== Why does my code not compile =====
  
-There are two immediate possibilities for why this could occur.+There are several immediate possibilities for why this could occur.
  
-==== Catch or throw a CommandSyntaxException ==== +  * **Catch or throw a CommandSyntaxException:** ''CommandSyntaxException'' is not a ''RuntimeException''. If you throw it, where it is throwed should be in methods that throws ''CommandSyntaxException'' in method signatures, or be caught. Brigadier will handle the checked exceptions and forward the proper error message in game for you. 
- +  * **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 ''CommandManager.literal(...)'' or ''CommandManager.argument(...)'' instead of ''LiteralArgumentBuilder.literal'' or ''RequiredArgumentBuilder.argument'' in your static imports
-The solution to this issue is to make the ''run'' or ''suggest'' methods throw ''CommandSyntaxException''. Brigadier will handle the checked exceptions and forward the proper error message in game for you. +  * **Check ''sendFeedback'' method:** You may have forgotten to provide a boolean as the second argument. Also remember that, since 1.20, the first parameter is ''Supplier<Text>'' instead of ''Text''
- +  * **''Command'' should return an integer:** When registering commands, the ''executes'' method accepts a ''Command'' object, which is usually a lambda. The lambda should return an integer, instead of other types.
-==== 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'' in your static imports.+
  
 ===== Can I register client side commands? ===== ===== Can I register client side commands? =====
  
-Fabric has a ClientCommandManager that can be used to register client side commands.+Fabric has a ''ClientCommandManager'' that can be used to register client side commands. The code should exist only in client-side codes. Example:
  
-===== Dark Arts =====+<code java> 
 +    ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("foo_client"
 +        .executes(context -> { 
 +              context.getSource().sendFeedback(Text.literal("The command is executed in the client!")); 
 +              return 1; 
 +            } 
 +        ))); 
 +</code>
  
-A few things we don't recommendbut are possible.+If you need to open a screen in the client command execution, instead of directly calling ''client.setScreen(...)'', you should call ''%%client.execute(() -> client.setScreen(...))%%''. The variable ''client'' can be obtained with ''context.getSource().getClient()''.
  
-==== Can I register commands in runtime? ====+===== 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''. 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''.
Line 253: Line 316:
 After that you need to send the command tree to every player again using ''CommandManager.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. After that you need to send the command tree to every player again using ''CommandManager.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? ====+===== 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. 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.
  
 +===== 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.
 +
 +Here is the code example
 +
 +<code java>
 +    private void vanillaCommandByPlayer(World world, BlockPos pos, String command) {
 +        PlayerEntity player = world.getClosestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5, false);
 +        if (player != null) {
 +            CommandManager commandManager = Objects.requireNonNull(player.getServer()).getCommandManager();
 +            ServerCommandSource commandSource = player.getServer().getCommandSource();
 +            commandManager.executeWithPrefix(commandSource, command);
 +        }
 +    }
 +</code>
 +
 +First, you need a CommandManager<ServerCommandSource>.
 +Second, you need the ServerCommandSource.
 +Then, u can call some CommandManager public methods like commandeManader.execute. (.execute need ParseResults<ServerCommandSource>)
 +But commandeManader.executeWithPrefix allow you to use String. You can also put the slash (/).
 +
 +So... have fun!
tutorial/commands.1700301771.txt.gz · Last modified: 2023/11/18 10:02 by solidblock