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 [2019/08/22 03:55] – [A very basic command] Change ArgumentBuilders to CommandManager i509vcbtutorial:commands [2024/02/23 14:22] (current) – Documenting and first draft allen1210
Line 1: Line 1:
 +Licensing: The code in this article is licensed under the "Creative Commons Zero v1.0 Universal" license. The license grants you the rights to use the code examples shown in this article in your own mods.
 +
 ====== 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 general command structure of Brigadier.
-This tutorial will teach you how to register commands, and the command structure of Brigadier along with some more advanced commands structures.+
  
-===== Registering Commands =====+===== What is Brigadier? =====
  
-If you just want to see how to register commands you've come to the right place here.+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 ''CommandRegistry'' with the ''register'' method.+The source code for brigadier can be found here: https://github.com/Mojang/brigadier
  
-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 ''Command'' interface =====
  
-The dedicated flag if set to true will tell Fabric to only register the command on a ''DedicatedServer'' (if false than the command will register on both the ''InternalServer'' and ''DedicatedServer'').+In Minecraft, ''com.mojang.brigadier.Command'' is a functional interface, which runs some specific things, and throw a ''CommandSyntaxException'' in some cases. It has a generic type ''S'', which defines the type of the //command source//. The command source provides some context in which a command was ran. In Minecraft, the command source is typically a ''<yarn class_2168>'' which can represent a server, a command block, rcon connection, a player or an entity. In some cases, it can also be a ''<yarn class_637>''.
  
-Below are few examples of how the commands can be registered.+The single method in ''Command'', ''run(CommandContext<S>)'' takes ''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.
  
-<code java [enable_line_numbers="true"]+Like other functional interfaces, it is usually used as a lambda or a method reference: 
-CommandRegistry.INSTANCE.register(false, dispatcher -TutorialCommands.register(dispatcher)); // All commands are registered in a single class that references every command+ 
-  +<code java> 
-CommandRegistry.INSTANCE.register(false, dispatcher -> { // You can also just reference every single class also. There is also the alternative of just using CommandRegistry +Command<ServerCommandSource> command = context -> { 
-    TutorialCommand.register(dispatcher); +    return 0
-    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 ====+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. 
 + 
 + 
 +==== What can the ServerCommandSource do? ====
  
-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.+''ServerCommandSource'' 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"]>
-// The root of the command. This must be a literal argument+// Get the source. This will always work
-dispatcher.register(CommandManager.literal("foo")  +final ServerCommandSource source = ctx.getSource(); 
-// Then add an argument named bar that is an integer +
-    .then(CommandManager.argument("bar", integer()) +
- // The command to be executed if the command "foo" is entered with the argument "bar" +
-        .executes(ctx -> {  +
-            System.out.println("Bar is " + IntArgumentType.getInteger(ctx, "bar")); +
-     // Return a result. -1 is failure, 0 is a pass and 1 is success. +
-            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>+
  
-The main process registers the command "foo" (Root Node) with an optional argument of "bar" (Child node).  +// Unchecked, may be null if the sender was the console or the command block. 
-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.+final @Nullable Entity sender = source.getEntity(); 
  
-===== Brigadier Explained =====+// Will throw an exception if the executor of the command was not an Entity.  
 +// The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity.  
 +// This method will require your methods to throw a CommandSyntaxException.  
 +// The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player. 
 +final @NotNull Entity sender2 source.getEntityOrThrow(); 
  
-Brigadier starts with the ''CommandDispatcher'' which should be thought more as a tree rather than a list of methods.  +// null if the executor of the command is not a player
-The trunk of the tree is the CommandDispatcher.  +final @Nullable ServerPlayerEntity player = source.getPlayer():
-The register(LiteralArgumentBuildermethods 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 ranAs Brigadier's Command is FunctionalInterface you can use lambdas to specify commands.+// Will throw an exception if the executor of the command was not explicitly a Player. 
 +// Also will send feedback telling the sender of the command that they must be player. 
 +// This method will require your methods to throw a CommandSyntaxException. 
 +final @NotNull ServerPlayerEntity player = source.getPlayerOrThrow(); 
  
-==== CommandContexts ====+// Gets the sender's position as a Vec3d 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. 
 +final Vec3d position source.getPosition(); 
  
-When a command is ran, Brigadier provides a CommandContext to the command that is ran +// Gets the world the sender is within. The console's world is the same as the default spawn world. 
-The CommandContext contains all arguments and other objects such as the inputted String and the ''Command Source'' (ServerCommandSource in Minecraft's implementation).+final ServerWorld world = source.getWorld()
  
-==== Arguments ====+// Gets the sender's rotation as a Vec2f. 
 +final Vec2f rotation source.getRotation(); 
  
-The arguments in Brigadier both parse and error check any inputted arguments.  +// Access to the instance of the MinecraftServer this command was ran on
-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.+final MinecraftServer server source.getServer(); 
  
-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")''.  +// The name of the command source. This could be the name of the entity, player, 
-This also works for getting argumentswhich shortens the already long ''StringArgumentType.getString(ctx, "string")'' to ''getString(ctx, "string")''+// the name of a CommandBlock that has been renamed before being placed downor in the case of the Console, "Console". 
-This also works for Minecraft's arguments.+final String name = source.getName(); 
  
-And your imports would look something like this: +// Returns true if the source of the command has a certain permission level. 
-<code java [enable_line_numbers="true"]> +// This is based on the operator status of the sender. 
-import static com.mojang.brigadier.arguments.StringArgumentType.getString; // getString(ctx, "string") +// (On an integrated serverthe player must have cheats enabled to execute these commands.) 
-import static com.mojang.brigadier.arguments.StringArgumentType.word; // word()string(), greedyString() +final boolean b = source.hasPermissionLevel(int level); 
-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''+===== Register a basic command =====
  
-Minecraft's arguments hide in ''net.minecraft.command.arguments'' and the CommandManager is at ''net.minecraft.server.command''+Commands are registered by registering in ''CommandRegistrationCallback'' in the Fabric APIFor information on registering callbacks, please see the [[callbacks]].
  
-==== Suggestions ====+The event should be registered in your mod's initializer. The callback has three parameters. The ''CommmandDispatcher<S>'' is used to register, parse and execute commands. ''S'' is the type of command source the command dispatcher supports, which is usually ''ServerCommandSource''. The second parameter provides an abstraction to registries which may be passed to certain command argument methods. The third parameter is a ''RegistrationEnvironment'' which identifies the type of server the commands are being registered on.
  
-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 SuggestionProviders. A few examples of Minecraft's built in suggestions providers are below +To simplify the code, it is highly recommended to ''static import'' the methods in ''CommandManager'' (see [[#Static Imports]]): 
-<code> +<code java
-SUMMONABLE_ENTITIES +import static net.minecraft.server.command.CommandManager.*;
-AVAILIBLE_SOUNDS +
-ALL_RECIPES +
-ASK_SERVER+
 </code> </code>
  
-Loot tables specify their own SuggestionProvider inside LootCommand for example.+In the mod initializer, we just register the simplest command:
  
-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 ExampleMod implements ModInitializer { 
-    return (ctxbuilder) -> getSuggestionsBuilder(builder, /*Access to a list here*/); +  @Override 
-+  public void onInitialize() { 
-     +    CommandRegistrationCallback.EVENT.register((dispatcherregistryAccess, environment) -> dispatcher.register(literal("foo"
-private static CompletableFuture<Suggestions> getSuggestionsBuilder(SuggestionsBuilder builder, List<Stringlist) +        .executes(context -> { 
-    String remaining = builder.getRemaining().toLowerCase(Locale.ROOT); +      // For versions below 1.19, replace "Text.literal" with "new LiteralText"
-         +      // For versions below 1.20, remode "() ->" directly. 
-    if(list.isEmpty()) { // If the list is empty then return no suggestions +      context.getSource().sendFeedback(() -> Text.literal("Called /foo with no arguments"), false); 
-        return Suggestions.empty(); // No suggestions + 
-    } +      return 1
-         +    }))); 
-    for (String str : list{ // Iterate through the supplied list +  }
-        if (str.toLowerCase(Locale.ROOT).startsWith(remaining)) { +
-            builder.suggest(str)// Add every single entry to suggestions list. +
-        } +
-    } +
-    return builder.buildFuture(); // Create the CompletableFuture containing all the suggestions+
 } }
 </code> </code>
  
-The SuggestionProvider is a FunctionalInterface that returns a CompletableFuture containing a list of suggestionsThese suggestions are given to client as a command is typed and can be changed while server is running. The SuggestionProvider provides 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.+**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, **literal** called ''foo''.
  
-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'a String for example and parsers may still throw exceptions if an invalid syntax is inputted.+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'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.
  
-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.+If the command fails, instead of calling ''sendFeedback'', you may directly throw a ''CommandSyntaxException'' or ''<yarn class_2164>''. See [[command_exceptions]] for details.
  
-<code java [enable_line_numbers="true"]> +To execute this command, you must type ''/foo''which is case-sensitiveIf ''/Foo'', ''/FoO'', ''/FOO'', ''/fOO'' or ''/fooo'' is typed instead, the command will not run.
-argument(argumentNameword()) +
-.suggests(CompletionProviders.suggestedStrings()) +
-    .then(/*Rest of the command*/)); +
-</code>+
  
-==== Requires ====+===== 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:
  
-Lets say you have a command you only want operators to be able to executeThis is where the ''requires'' method comes into playThe requires method has one argument of a Predicate<ServerCommandSourcewhich will supply a ServerCommandSource to test with and determine if the CommandSource can execute the command.+<yarncode java [enable_line_numbers="true", highlight_lines_extra="5,6,7"]> 
 +public class ExampleCommandMod implements ModInitializer { 
 +    @Override 
 +    public void onInitialize() { 
 +        CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -
 +            if (environment.field_25423) { 
 +                ...; 
 +            } 
 +        }); 
 +    } 
 +
 +</yarncode>
  
-For example this may look like the following:+===== Static Imports ===== 
 +In the example above, the use of static imports is used for code simplifying. For a literal this would 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.
  
 +Below is an example of some static imports:
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-dispatcher.register(literal("foo") +// getString(ctx, "string") 
- .requires(source -> source.hasPermissionLevel(4)+import static com.mojang.brigadier.arguments.StringArgumentType.getString; 
- .executes(ctx -> { +// word() 
- ctx.getSource().sendFeedback(new LiteralText("You are an operator", false)); +import static com.mojang.brigadier.arguments.StringArgumentType.word; 
- return 1+ // literal("foo") 
- });+import static net.minecraft.server.command.CommandManager.literal; 
 + // argument("bar", word()) 
 +import static net.minecraft.server.command.CommandManager.argument
 +// Import everything in the CommandManager 
 +import static net.minecraft.server.command.CommandManager.*;
 </code> </code>
  
-This command will only execute if the Source of the command is a level 4 operator at minimum. If the predicate returns falsethen the command will not executeAlso this has the side effect of not showing this command in tab completion to anyone who is not a level 4 operator.+Note: Please be sure you use the ''literal'' and ''argument'' from ''CommandManager'' instead of other classesor you may have issues with generics when trying to compile, because the type parameter ''S'' should be ''ServerCommandSource''(For client-side commands, use ''ClientCommandManager'' instead.)
  
-==== Exceptions ====+Brigadier's default arguments are at ''com.mojang.brigadier.arguments''
  
-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+Minecraft's arguments are in ''net.minecraft.command.arguments''. CommandManager is in the package ''net.minecraft.server.command''.
  
-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.  +===== Add Requirements =====
-Below is a coin flip command to show an example of exceptions in use.+
  
-<code java [enable_line_numbers="true"]> +Let's say you have a command that you only want operators to be able to executeThis is where the ''requires'' method comes into playThe ''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.
-dispatcher.register(CommandManager.literal("coinflip"+
- .executes(ctx -+
- Random random = new Random(); +
-  +
- if(random.nextBoolean()) { // If heads succeed. +
- ctx.getSource().sendMessage(new TranslateableText("coin.flip.heads")) +
- return Command.SINGLE_SUCCESS; +
-+
- throw new SimpleCommandExceptionType(new TranslateableText("coin.flip.tails")).create(); // Oh no tails, you lose. +
- })); +
-</code>+
  
-Though you are not just limited to a single type of exception as Brigadier also supplies Dynamic exceptions.+For example this may look like the following:
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> { +dispatcher.register(literal("foo"
- return new LiteralText("The name: " + (Stringname + has been used"); +  .requires(source -> source.hasPermissionLevel(2)) 
-});+  .executes(ctx -> { 
 +    ctx.getSource().sendFeedback(() -> Text.literal("You are an operator"), false); 
 +    return 1
 +  });
 </code> </code>
  
-There are more Dynamic exception types which each take a different amount of arguments into account (''Dynamic2CommandExceptionType''''Dynamic3CommandExceptionType''''Dynamic4CommandExceptionType'', ''DynamicNCommandExceptionType''). +This command will only execute if the source of the command is a level 2 operator at minimum//including// command blocks. Otherwisethe command is not registeredAlso this has the side effect of not showing this command in tab completion to anyone who is not a level 2 operator. This is also why you cannot tab-complete most commands when you did not enable cheating.
-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====+To create commands that only level 4 operators (//not including// command blockscan execute, use ''source.hasPermissionLevel(4)''.
  
-Redirects are Brigadier's form of aliases. Below is how Minecraft handles /msg have an alias of /tell and /w. +===== Arguments =====
  
-<code java [enable_line_numbers="true"]> +Arguments are used in most of commandsSometimes they can be optionalwhich means if you do not provide that argument, the command will also runOne node may have multiple argument typesbut be aware that there is possibility of ambiguity, which should be avoided.
-public static void register(CommandDispatcher<ServerCommandSource> dispatcher) { +
-    LiteralCommandNode node = registerMain(dispatcher); // Registers main command +
-    dispatcher.register(literal("tell"+
- .redirect(node)); // Alias 1redirect to main command +
-    dispatcher.register(literal("w"+
- .redirect(node)); // Alias 2redirect to main command +
-}+
  
-public static LiteralCommandNode registerMain(CommandDispatcher<ServerCommandSource> dispatcher) { +In this casewe add one integer argument, and calculate the square of the integer.
-    return dispatcher.register(literal("msg"+
- .then(argument("targets"EntityArgumentType.players()) +
-     .then(argument("message"MessageArgumentType.message()) +
- .executes(ctx -> { +
-     return execute(ctx.getSource(), getPlayers(ctx, "targets"), getMessage(ctx, "message")); +
- })))); +
-+
-</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. +<code java> 
- +    dispatcher.register(literal("mul"
-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. +        .then(argument("value", IntegerArgumentType.integer()) 
- +            .executes(context -> { 
-===== ServerCommandSource ===== +              final int value IntegerArgumentType.getInteger(context, "value"); 
- +              final int result = value * value; 
-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 +              context.getSource().sendFeedback(() -> Text.literal("%s × %s %s".formatted(value, value, result)), false); 
- +              return result; 
-<code java [enable_line_numbers="true"]+            })));
-ServerCommandSource source = ctx.getSource() +
-// Get the sourceThis 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 playerAlso 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.  This method will require your methods to throw a CommandSyntaxException+
 </code> </code>
  
-The ServerCommandSource also provides other information about the sender of the command.+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.
-source.getPosition();  +
-// Get's the sender's position as a Vec3 when the command was sentThis could be the location of the entity/command block or in the case of the console, the world's spawn point.+
  
-source.getWorld() +Then we add an optional second argument: 
-// Get's the world the sender is withinThe console's world is the same as the default spawn world+<code java> 
- +    dispatcher.register(literal("mul"
-source.getRotation();  +        .then(argument("value", IntegerArgumentType.integer()) 
-// Get'the sender'rotation as a Vec2f+            .executes(context -> { 
- +              final int value = IntegerArgumentType.getInteger(context, "value"); 
-source.getMinecraftServer() +              final int result = value * value; 
-// Access to the instance of the MinecraftServer this command was ran on+              context.getSource().sendFeedback(() -> Text.literal("%× %= %s".formatted(value, value, result)), false); 
- +              return result; 
-source.getName();  +            }) 
-// The name of the command sourceThis 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+            .then(argument("value2", IntegerArgumentType.integer()) 
- +                .executes(context -> { 
-source.hasPermissionLevel(int level);  +                  final int value = IntegerArgumentType.getInteger(context, "value"); 
-// Returns true if the source of the command has a certain permission levelThis is based on the operator status of the sender. (On an integrated serverthe player must have cheats enabled to execute these commands)+                  final int value2 = IntegerArgumentType.getInteger(context, "value2"); 
 +                  final int result = value * value2; 
 +                  context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(valuevalue2, result)), false); 
 +                  return result; 
 +                }))));
 </code> </code>
  
-===== Some actual examples =====+Now you can type one or two integers. If you give one integer, that square of integer will be calculated. If you provide two integers, their product will be calculated. You may find it unnecessary to specify similar executions twice. Therefore, we can create a method that will be used in both executions.
  
-Just a few to show: +<code java> 
- +public class ExampleMod implements ModInitializer { 
-=== Broadcast a message ===+  @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"]>  +  private static int executeMultiply(int value, int value2, CommandContext<ServerCommandSource> context) { 
-public static void register(CommandDispatcher<ServerCommandSource> dispatcher){ +    final int result = value * value2; 
-    dispatcher.register(literal("broadcast") +    context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(valuevalue2result)), false); 
- .requires(source -> source.hasPermissionLevel(2)) // Must be a game master to use the commandCommand will not show up in tab completion or execute to non op's or any op that is permission level 1. +    return result; 
-     .then(argument("color", ColorArgumentType.color()+  }
- .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.+
 } }
 +</code>
 +===== A sub command =====
  
-public static int broadcast(ServerCommandSource sourceFormatting formatting, String message) { +To add a sub commandyou register the first literal node of the command normally.
-    Text text = new LiteralText(message).formatting(formatting);+
  
-    source.getMinecraftServer().getPlayerManager().broadcastChatMessage(text, false)+<code> 
-    return Command.SINGLE_SUCCESS; // Success +dispatcher.register(literal("foo"))
-}+
 </code> </code>
  
-=== /giveMeDiamond ===+In order to have a sub command, one needs to append the next node to the existing node. 
  
-First the basic code where we register "giveMeDiamond" as a literal and then an executes block to tell the dispatcher which method to run.+This creates the command ''foo <bar>'' as shown below.
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-public static LiteralCommandNode register(CommandDispatcher<ServerCommandSource> dispatcher{ // You can also return a LiteralCommandNode for use with possible redirects +dispatcher.register(literal("foo"
-    return dispatcher.register(literal("giveMeDiamond") +    .then(literal("bar") 
-        .executes(ctx -> giveDiamond(ctx))); +        .executes(context -> 
-+            // For versions below 1.19, use ''new LiteralText''
-</code>+            // For versions below 1.20, use directly the ''Text'' object instead of supplier. 
 +            context.getSource().sendFeedback(() -> Text.literal("Called foo with bar"), false);
  
-Then since we only want to give to players, we check if the CommandSource is a player. But we can use ''getPlayer'' and do both at the same time and throw an error if the source is not a player. +            return 1; 
- +        }
-<code java [enable_line_numbers="true"]> +    ) 
-public static int giveDiamond(CommandContext<ServerCommandSource> ctxthrows CommandSyntaxException { +);
-    ServerCommandSource source = ctx.getSource(); +
-  +
-    PlayerEntity self = source.getPlayer(); // If not a player than the command ends+
 </code> </code>
  
-Then we add to the player's inventorywith a check to see if the inventory is full: +Similar to argumentssub 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"]> <code java [enable_line_numbers="true"]>
-    if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){ +dispatcher.register(literal("foo"
-        throw new SimpleCommandExceptionType(new TranslateableText("inventory.isfull")).create(); +    .executes(context -> { 
-    }  +        context.getSource().sendFeedback(() -> Text.literal("Called foo without bar"), false); 
-    return 1; +        return 1; 
-}+    }) 
 +    .then(literal("bar") 
 +        .executes(context -> { 
 +            context.getSource().sendFeedback(() -> Text.literal("Called foo with bar"), false); 
 +            return 1; 
 +        }
 +    ) 
 +);
 </code> </code>
 +====== Advanced concepts ======
  
-=== Antioch === +Below are links to the articles about more complex concepts used in brigadier.
-...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe. +
-who being naughty in My sight, shall snuff it.+
  
-Aside from the joke this command summons primed TNT to a specified location or the location of the sender's cursor.+^ Page                                                           ^ Description                                                                     ^ 
 +| [[command_exceptions  |Exceptions]]                 | Fail execution of a command with descriptive message and in certain contexts. |                                                                   
 +| [[command_suggestions|Suggestions]]                | Suggesting command input for the client.                                        | 
 +| [[command_redirects|Redirects]]   | Allow use of aliases or repeating elements to execute commands.                                   | 
 +| [[command_argument_types|Custom Argument Types]]    | Parse your own arguments into your own objects                                | 
 +| [[command_examples|Examples]] | Some example commands |
  
-First create an entry into the CommandDispatcher that takes a literal of antioch with an optional argument of the location to summon the entity at.+====== FAQ ======
  
-<code java [enable_line_numbers="true"]> +===== Why does my code not compile =====
-public static void register(CommandDispatcher<ServerCommandSource> dispatcher) { +
-    dispatcher.register(literal("antioch"+
-        .then(required("location", BlockPosArgumentType.blockPos() +
-     .executes(ctx -> antioch(ctx.getSource(), BlockPosArgument.getBlockPos(ctx, "location"))))) +
- .executes(ctx -> antioch(ctx.getSource(), null))); +
-+
-</code>+
  
-Then the creation and messages behind the joke.+There are several immediate possibilities for why this could occur.
  
-<code java [enable_line_numbers="true"]> +  * **Catch or throw a CommandSyntaxException:** ''CommandSyntaxException'' is not a ''RuntimeException''. If you throw itwhere it is throwed should be in methods that throws ''CommandSyntaxException'' in method signaturesor be caughtBrigadier will handle the checked exceptions and forward the proper error message in game for you. 
-public static int antioch(ServerCommandSource sourceBlockPos blockPos) throws CommandSyntaxException {  +  * **Issues with generics:** You may have an issue with generics once in a whileIf 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. 
-  +  * **Check ''sendFeedback'' method:** You may have forgotten to provide a boolean as the second argumentAlso remember thatsince 1.20the first parameter is ''Supplier<Text>'' instead of ''Text''
-    if(blockPos==null) { +  * **''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.
-        blockPos = LocationUtil.calculateCursorOrThrow(sourcesource.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 worldThis class is used as an example and actually doesn't exist yet. +
-    } +
-  +
-    TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ(), null); +
-         +
-    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("who being naughty in My sightshall snuff it.").formatting(Formatting.RED)false); +
-    source.getWorld().spawnEntity(tnt); +
-    return 1; +
-+
-</code>+
  
-=== Finding Biomes via Command ===+===== Can I register client side commands? =====
  
-This example shows examples of redirects, exceptions, suggestions and tiny bit of text. Note this command when used works but can take a bit of time to work similarly to ''/locate'' +Fabric has a ''ClientCommandManager'' that can be used to register client side commandsThe code should exist only in client-side codesExample:
-<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; +<code java> 
-    +    ClientCommandRegistrationCallback.EVENT.register((dispatcherregistryAccess) -> dispatcher.register(ClientCommandManager.literal("foo_client") 
-    // Just a normal old 2d distance method. +        .executes(context -> { 
-    private static float getDistance(int int_1int int_2, int int_3, int int_4+              context.getSource().sendFeedback(Text.literal("The command is executed in the client!")); 
-        int int_5 = int_3 int_1; +              return 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<ServerCommandSourceBIOMES = 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> </code>
  
-===== FAQ =====+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()''.
  
-=== What else can send feedback to the CommandSource? ===+===== Can register commands in runtime=====
  
-You can choose between Brigadier's default LiteralMessage or use any one of Minecraft'Text classes (LiteralText, TranslatableText)+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'''CommandDispatcher''.
  
-=== Why does my IDE complain saying that a method executed by my command needs to catch or throw a CommandSyntaxException ===+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.
  
-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 unregister commands in runtime? =====
  
-=== Can I register commands in run time? ===+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 do this but it is not reccomendedYou would get the instance of the CommandManager and add anything you wish to the CommandDispatcher within it.+===== 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.
  
-After that you will need to send the command tree to every player again using ''CommandManager.sendCommandTree(PlayerEntity)'' +Here is the code example
- +
-=== 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> <code java>
-dispatcher.register(literal("publish") +    private void vanillaCommandByPlayer(World world, BlockPos pos, String command) { 
-    // The permission level 4 on integrated server is the equivalent of having cheats enabled+        PlayerEntity player = world.getClosestPlayer(pos.getX(), pos.getY(), pos.getZ(), 5, false); 
-    .requires(source -> source.getMinecraftServer().isSinglePlayer() && source.hasPermissionLevel(4)));+        if (player != null) { 
 +            CommandManager commandManager = Objects.requireNonNull(player.getServer()).getCommandManager()
 +            ServerCommandSource commandSource = player.getServer().getCommandSource()
 +            commandManager.executeWithPrefix(commandSource, command); 
 +        } 
 +    }
 </code> </code>
  
-=== I want to access X from my mod when command is ran ===+First, you need 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 (/).
  
-This is going to require a way to statically access your mod with a ''getInstance'' callBelow is a very simple instance system you can place in your mod +So... have fun!
- +
-<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