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 revision Previous revision
tutorial:commands [2020/05/07 20:12]
i509vcb Amend to use new API
tutorial:commands [2020/05/16 01:57] (current)
i509vcb Reword some parts of the tutorial, includes a few gramatical corrections
Line 2: Line 2:
  
 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 a player can use 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 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.+===== Registering Commands =====
  
 Registering commands is done by registering a new listener in the ''​CommandRegistrationCallback''​. Registering commands is done by registering a new listener in the ''​CommandRegistrationCallback''​.
Line 15: Line 15:
  
 <code java [enable_line_numbers="​true"​]>​ <code java [enable_line_numbers="​true"​]>​
-// The actual registration of commands can be delegated to another class via a method ​reference+// Method ​reference
 CommandRegistrationCallback.EVENT.register(TutorialCommands::​register);​ CommandRegistrationCallback.EVENT.register(TutorialCommands::​register);​
  
-// Or you can define every command within the event listener+// Using a lambda
 CommandRegistrationCallback.EVENT.register((dispatcher,​ dedicated) -> { CommandRegistrationCallback.EVENT.register((dispatcher,​ dedicated) -> {
-    ​TutorialCommand.register(dispatcher); ​// This command will be registered regardless of the server being dedicated or integrated+    // This command will be registered regardless of the server being dedicated or integrated 
 +    TutorialCommand.register(dispatcher);​
     if (dedicated) {     if (dedicated) {
-        ​TutorialHelpCommand.register(dispatcher); ​// This command will only be registered on a dedicated server+        // This command will only be registered on a dedicated server 
 +        TutorialHelpCommand.register(dispatcher);​
     } else {     } else {
-        ​IntegratedTutorialHelpCommand.register(dispatcher); ​// This command will only be registered on an integrated server.+        // This command will only be registered on an integrated server. 
 +        // Commands which call client only classes and methods should be registered in your ClientModInitializer 
 +        IntegratedTutorialHelpCommand.register(dispatcher); ​
     }     }
 }); });
-  + 
-CommandRegistrationCallback.EVENT.register((dispatcher,​ dedicated) -> { // Or directly registering the command to the dispatcher. +// Or register directly 
- dispatcher.register(LiteralArgumentBuilder.literal("​tutorial"​).executes(ctx -> execute(ctx)));​+CommandRegistrationCallback.EVENT.register((dispatcher,​ dedicated) -> { 
 +    dispatcher.register(LiteralArgumentBuilder.literal("​tutorial"​).executes(ctx -> execute(ctx)));​
 }); });
 </​code>​ </​code>​
Line 42: Line 47:
 // Then add an argument named bar that is an integer // Then add an argument named bar that is an integer
     .then(CommandManager.argument("​bar",​ integer())     .then(CommandManager.argument("​bar",​ integer())
- // The command to be executed if the command "​foo"​ is entered with the argument "​bar"​+        ​// The command to be executed if the command "​foo"​ is entered with the argument "​bar"​
         .executes(ctx -> {          .executes(ctx -> { 
             System.out.println("​Bar is " + IntArgumentType.getInteger(ctx,​ "​bar"​));​             System.out.println("​Bar is " + IntArgumentType.getInteger(ctx,​ "​bar"​));​
-     ​// Return a result. -1 is failure, 0 is pass and 1 is success.+            ​// Return a result. ​Typically ​-1 is failure, 0 is pass and 1 is success.
             return 1;             return 1;
-            ​}))+        ​}))
     // The command "​foo"​ to execute if there are no arguments.     // The command "​foo"​ to execute if there are no arguments.
     .executes(ctx -> {      .executes(ctx -> { 
Line 56: Line 61:
 </​code>​ </​code>​
  
-The main process registers the command ​"foo" ​(Root Node) with an optional argument of "bar" ​(Child node).  +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.+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 ===== ===== Brigadier Explained =====
  
-Brigadier starts with the ''​CommandDispatcher''​ which should be thought more as a tree rather than a list of methods.  +Brigadier starts with the ''​CommandDispatcher''​ which should be thought more as a tree of command nodes
-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 +Command nodes are similar to the branches ​that define ​the command tree. The executes blocks can be seen at the leaves of the tree where a branch ​ends and also supplies the command to be executed.
-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.+The execute blocks specify the command to be ran. As Brigadier'​s Command is a functional interface ​you can use lambdas to specify commands.
  
 ==== CommandContexts ==== ==== CommandContexts ====
  
-When a command is ran, Brigadier provides a CommandContext to the command that is ran. +When a command is ran, Brigadier provides a ''​CommandContext'' ​to the command that is ran. 
 The CommandContext contains all arguments and other objects such as the inputted String and the ''​Command Source''​ (ServerCommandSource in Minecraft'​s implementation). The CommandContext contains all arguments and other objects such as the inputted String and the ''​Command Source''​ (ServerCommandSource in Minecraft'​s implementation).
  
 ==== Arguments ==== ==== Arguments ====
  
-The arguments ​in Brigadier both parse and error check any inputted arguments.  +Arguments ​in Brigadier both parse and error check any inputted arguments. 
-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.+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.
  
 +==== Static Imports ====
 You could do the long method of typing ''​CommandManager.literal("​foo"​)''​ and it would work, but you can statically import the arguments and shorten that to ''​literal("​foo"​)''​. ​ You could do the long method of typing ''​CommandManager.literal("​foo"​)''​ and it would work, but you can statically import the arguments and shorten that to ''​literal("​foo"​)''​. ​
 This also works for getting arguments, which shortens the already long ''​StringArgumentType.getString(ctx,​ "​string"​)''​ to ''​getString(ctx,​ "​string"​)''​. This also works for getting arguments, which shortens the already long ''​StringArgumentType.getString(ctx,​ "​string"​)''​ to ''​getString(ctx,​ "​string"​)''​.
Line 90: Line 95:
 import static net.minecraft.server.command.CommandManager.*;​ // Import everything import static net.minecraft.server.command.CommandManager.*;​ // Import everything
 </​code>​ </​code>​
 +
 +Note: Please be sure you use the ''​literal''​ and ''​argument''​ from CommandManager or you may have issues with generics.
  
 Brigadier'​s default arguments are located in ''​com.mojang.brigadier.arguments''​ Brigadier'​s default arguments are located in ''​com.mojang.brigadier.arguments''​
Line 105: Line 112:
 </​code>​ </​code>​
  
-Loot tables specify their own SuggestionProvider inside LootCommand for example.+Loot tables specify their own SuggestionProvider inside ​''​LootCommand'' ​for example.
  
 The example below is a dynamically changing SuggestionProvider that lists several words for a StringArgumentType to demonstrate how it works: The example below is a dynamically changing SuggestionProvider that lists several words for a StringArgumentType to demonstrate how it works:
Line 129: Line 136:
 </​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.+The SuggestionProvider is a functional interface ​that returns a CompletableFuture containing a list of suggestions. These suggestions are sent to client as a command is typed and can be changed while server is running. The SuggestionProvider provides a CommandContext and a SuggestionBuilder to allow determination of all the suggestions. The CommandSource can also be taken into account during the suggestion creation process as it is available through the CommandContext.
  
-Though remember these are suggestions. The 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.+Though remember these are suggestions. The inputted command may not contain an argument you suggested so arguments are parsed without consideration ​for suggestions.
  
-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.+To use the suggestion you would append it right after the argument you want to suggest possible ​arguments for. This can be any argument and the normal client side exception popups will still work. Note this cannot be applied to literals.
  
 <code java [enable_line_numbers="​true"​]>​ <code java [enable_line_numbers="​true"​]>​
Line 141: Line 148:
 </​code>​ </​code>​
  
-==== Requires ​====+==== Requiring Permissions ​====
  
 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 157: Line 164:
  
 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.
 +
 +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.
  
 ==== Exceptions ==== ==== Exceptions ====
  
-Brigadier supports command exceptions which can be used to end a command such as if an argument didn't parse properly or the command failed to execute. ​+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)''​. Though this can only be used with a custom parser. These can be defined and thrown under certain scenarios during ​the 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)''​, 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 197: Line 207:
     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) ====
Line 225: Line 233:
     .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 234: Line 242:
 }))); })));
 </​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>​
Line 244: Line 252:
 ===== ServerCommandSource ===== ===== ServerCommandSource =====
  
-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 259: Line 267:
 // 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 285: Line 289:
 </​code>​ </​code>​
  
-===== Some actual ​examples ===== +===== A few examples =====
- +
-Just a few to show:+
  
 === Broadcast a message === === Broadcast a message ===
Line 294: Line 296:
 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'​s ​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 323: Line 325:
 <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 332: Line 334:
 <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;
 } }
Line 350: Line 353:
     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 358: Line 361:
  
 <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 385: Line 390:
                                 .executes(ctx -> execute(ctx.getSource(),​ getIdentifier(ctx,​ "​biome_identifier"​),​ getInteger(ctx,​ "​distance"​))))                                 .executes(ctx -> execute(ctx.getSource(),​ getIdentifier(ctx,​ "​biome_identifier"​),​ getInteger(ctx,​ "​distance"​))))
                         .executes(ctx -> execute(ctx.getSource(),​ getIdentifier(ctx,​ "​biome_identifier"​),​ 1000))));                         .executes(ctx -> execute(ctx.getSource(),​ getIdentifier(ctx,​ "​biome_identifier"​),​ 1000))));
-        // Register redirect+        // Register ​redirect ​for /biome alias
         dispatcher.register(literal("​biome"​)         dispatcher.register(literal("​biome"​)
                 .redirect(basenode));​                 .redirect(basenode));​
     }     }
-    // Beginning of the method+
     private static int execute(ServerCommandSource source, Identifier biomeId, int range) throws CommandSyntaxException {     private static int execute(ServerCommandSource source, Identifier biomeId, int range) throws CommandSyntaxException {
         Biome biome = Registry.BIOME.get(biomeId);​         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+        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();​             throw new SimpleCommandExceptionType(new TranslatableText("​biome.not.exist",​ biomeId)).create();​
         }         }
Line 399: Line 405:
         List<​Biome>​ bio = new ArrayList<​Biome>​();​         List<​Biome>​ bio = new ArrayList<​Biome>​();​
         bio.add(biome);​         bio.add(biome);​
-        ​+
         ServerWorld world = source.getWorld();​         ServerWorld world = source.getWorld();​
-        ​ 
         BiomeSource bsource = world.getChunkManager().getChunkGenerator().getBiomeSource();​         BiomeSource bsource = world.getChunkManager().getChunkGenerator().getBiomeSource();​
-        ​ 
         BlockPos loc = new BlockPos(source.getPosition());​         BlockPos loc = new BlockPos(source.getPosition());​
-        ​// Now here is the heaviest part of the method.+ 
 +        ​// This call will likely block the main thread
         BlockPos pos = bsource.locateBiome(loc.getX(),​ loc.getZ(), range, bio, new Random(world.getSeed()));​         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 nulla biome was not found
         if(pos == null) {         if(pos == null) {
             throw new SimpleCommandExceptionType(new TranslatableText("​biome.notfound",​ biome.getTranslationKey())).create();​             throw new SimpleCommandExceptionType(new TranslatableText("​biome.notfound",​ biome.getTranslationKey())).create();​
         }         }
-        ​+
         int distance = MathHelper.floor(getDistance(loc.getX(),​ loc.getZ(), pos.getX(), pos.getZ()));​         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.         // 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) -> { +        Text teleportButtonPopup = Texts.bracketed(new TranslatableText("​chat.coordinates",​ new Object[] { pos.getX(), "​~",​ pos.getZ()})).styled((style) -> { 
-            ​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])));+            ​style.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"​)));​
         });         });
         ​         ​
Line 434: Line 439:
  
     public static class BiomeCompletionProvider {     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.+        // This provides suggestions of what biomes can be selected. Since this uses the registry, mods that add new biomes will work without ​any modifications.
         public static final SuggestionProvider<​ServerCommandSource>​ BIOMES = SuggestionProviders.register(new Identifier("​biomes"​),​ (ctx, builder) -> {         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())));​             Registry.BIOME.getIds().stream().forEach(identifier -> builder.suggest(identifier.toString(),​ new TranslatableText(Registry.BIOME.get(identifier).getTranslationKey())));​
Line 443: Line 448:
 </​code>​ </​code>​
  
-===== Custom ​Arguments ​=====+===== Custom ​Argument Types =====
  
 Brigadier has support for custom argument types and this section goes into showing how to create a simple argument type.  Brigadier has support for custom argument types and this section goes into showing how to create a simple argument type. 
Line 449: Line 454:
 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. 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.
  
-For this example we will create a UUIDArgumentType.+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 [enable_line_numbers="​true"​]>​ <code java [enable_line_numbers="​true"​]>​
-public class UUIDArgumentType ​implements ArgumentType<​UUID>​ {+public class UuidArgumentType ​implements ArgumentType<​UUID>​ {
 </​code>​ </​code>​
  
Line 500: Line 505:
 </​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 [enable_line_numbers="​true"​]>​ <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 510: Line 515:
 And here is the whole ArgumentType:​ And here is the whole ArgumentType:​
  
-<file java UUIDArgumentType.java [enable_line_numbers="​true"​]>​+<file java UuidArgumentType.java [enable_line_numbers="​true"​]>​
  
 import com.mojang.brigadier.StringReader;​ import com.mojang.brigadier.StringReader;​
Line 527: Line 532:
  * 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 573: Line 576:
  
     @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;
     }     }
Line 582: Line 585:
 === What else can I send feedback to the CommandSource?​ === === 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)+You use the Text classes (LiteralText,​ TranslatableText, KeybindText,​ etc).
  
 === Why does my IDE complain saying that a method executed by my command needs to catch or throw a CommandSyntaxException === === Why does my IDE complain saying that a method executed by my command needs to catch or throw a CommandSyntaxException ===
Line 588: Line 591:
 The solution to this is just to make the methods throw a CommandSyntaxException down the whole chain as the executes block handles the exceptions. 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? ===+=== Can I register commands ​runtime? ===
  
 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. 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)''​+After that you will need to send the command tree to every player again using ''​CommandManager.sendCommandTree(ServerPlayerEntity)''​
  
-=== Can I unregister commands ​in run time? ===+=== Can I unregister commands ​runtime? ===
  
 You can also do this but it is very unstable and could cause unwanted side effects. Lets just say it involves a bunch of Reflection. 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.+Once again you will need to send the command tree to every player again using ''​CommandManager.sendCommandTree(ServerPlayerEntity)''​ afterwards.
  
 === Can I register client side commands? === === Can I register client side commands? ===
Line 604: Line 607:
 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: 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 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: 2020/05/16 01:57 by i509vcb