tutorial:commands
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
tutorial:commands [2019/07/29 15:18] – Grammar correction i509vcb | tutorial:commands [2019/11/16 02:14] – i509vcb | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Creating Commands ====== | ====== Creating Commands ====== | ||
- | Creating commands can allow a mod developer to add functionality a player can run a command | + | Creating commands can allow a mod developer to add functionality |
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 along with some more advanced commands structures. | ||
Line 8: | Line 8: | ||
If you just want to see how to register commands you've come to the right place here. | If you just want to see how to register commands you've come to the right place here. | ||
- | Registering commands is done through | + | Registering commands is done through '' |
- | The register method specifies two arguments, the dedicated flag and a consumer representing the CommandDispatcher. These methods should be placed in your mod' | + | The '' |
- | 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). | + | The dedicated flag if set to true will tell Fabric to only register the command on a '' |
Below are a few examples of how the commands can be registered. | Below are a few examples of how the commands can be registered. | ||
- | <code java> | + | <code java [enable_line_numbers=" |
- | CommandRegistryImpl.INSTANCE.register(false, | + | CommandRegistry.INSTANCE.register(false, |
CommandRegistry.INSTANCE.register(false, | CommandRegistry.INSTANCE.register(false, | ||
Line 33: | Line 33: | ||
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. | 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. | ||
- | <code java> | + | <code java [enable_line_numbers=" |
// The root of the command. This must be a literal argument. | // The root of the command. This must be a literal argument. | ||
- | dispatcher.register(LiteralArgumentBuilder.literal(" | + | dispatcher.register(CommandManager.literal(" |
// Then add an argument named bar that is an integer | // Then add an argument named bar that is an integer | ||
- | .then(RequiredArgumentBuilder.argument(" | + | .then(CommandManager.argument(" |
// The command to be executed if the command " | // The command to be executed if the command " | ||
.executes(ctx -> { | .executes(ctx -> { | ||
Line 79: | Line 79: | ||
And your imports would look something like this: | And your imports would look something like this: | ||
- | <code java> | + | <code java [enable_line_numbers=" |
import static com.mojang.brigadier.arguments.StringArgumentType.getString; | import static com.mojang.brigadier.arguments.StringArgumentType.getString; | ||
import static com.mojang.brigadier.arguments.StringArgumentType.word; | import static com.mojang.brigadier.arguments.StringArgumentType.word; | ||
Line 104: | Line 104: | ||
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: | ||
- | <code java> | + | <code java [enable_line_numbers=" |
public static SuggestionProvider< | public static SuggestionProvider< | ||
return (ctx, builder) -> getSuggestionsBuilder(builder, | return (ctx, builder) -> getSuggestionsBuilder(builder, | ||
Line 131: | Line 131: | ||
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 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. | ||
- | <code java> | + | <code java [enable_line_numbers=" |
argument(argumentName, | argument(argumentName, | ||
.suggests(CompletionProviders.suggestedStrings()) | .suggests(CompletionProviders.suggestedStrings()) | ||
Line 143: | Line 143: | ||
For example this may look like the following: | For example this may look like the following: | ||
- | <code java> | + | <code java [enable_line_numbers=" |
dispatcher.register(literal(" | dispatcher.register(literal(" | ||
.requires(source -> source.hasPermissionLevel(4)) | .requires(source -> source.hasPermissionLevel(4)) | ||
Line 161: | Line 161: | ||
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> | + | <code java [enable_line_numbers=" |
dispatcher.register(CommandManager.literal(" | dispatcher.register(CommandManager.literal(" | ||
.executes(ctx -> { | .executes(ctx -> { | ||
Line 176: | Line 176: | ||
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. | ||
- | <code java> | + | <code java [enable_line_numbers=" |
DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> { | DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> { | ||
return new LiteralText(" | return new LiteralText(" | ||
Line 189: | Line 189: | ||
Redirects are Brigadier' | Redirects are Brigadier' | ||
- | <code java> | + | <code java [enable_line_numbers=" |
public static void register(CommandDispatcher< | public static void register(CommandDispatcher< | ||
LiteralCommandNode node = registerMain(dispatcher); | LiteralCommandNode node = registerMain(dispatcher); | ||
Line 210: | Line 210: | ||
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 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. | ||
- | Redirects | + | Redirects |
- | <code java> | + | ==== Redirects |
- | public static void register(CommandDispatcher< | + | Commands such as ''/ |
- | LiteralCommandNode node = registerShortened(dispatcher); | + | |
- | dispatcher.register(literal(" | + | |
- | .then(literal(" | + | |
- | .redirect(node))); | + | |
- | } | + | |
- | public static | + | <code java [enable_line_numbers=" |
- | | + | LiteralCommandNode< |
- | .then(argument("argument", word()) | + | LiteralCommandNode< |
- | | + | // You can register under the same literal more than once, it will just register new parts of the branch as shown below if you register a duplicate branch an error will popup in console warning of conflicting commands but one will still work. |
- | } | + | .then(literal(" |
+ | .then(literal("long") | ||
+ | .redirect(root)) // Return to root for chaining | ||
+ | .then(literal(" | ||
+ | .redirect(root))) // Return to root for chaining | ||
+ | .then(literal(" | ||
+ | .executes(ctx -> { | ||
+ | | ||
+ | return Command.SINGLE_SUCCESS; | ||
+ | }))); | ||
+ | </ | ||
+ | The redirect can also modify the CommandSource. | ||
+ | |||
+ | <code java [enable_line_numbers=" | ||
+ | .redirect(rootNode, | ||
+ | return ((ServerCommandSource) commandContext_1x.getSource()).withLookingAt(Vec3ArgumentType.getVec3(commandContext_1x, | ||
+ | }) | ||
</ | </ | ||
Line 231: | Line 242: | ||
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 | 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 | ||
- | <code java> | + | <code java [enable_line_numbers=" |
ServerCommandSource source = ctx.getSource(); | ServerCommandSource source = ctx.getSource(); | ||
// Get the source. This will always work. | // Get the source. This will always work. | ||
Line 239: | Line 250: | ||
Entity sender2 = source.getEntityOrThrow(); | Entity sender2 = source.getEntityOrThrow(); | ||
- | // Will end the command if the source of the command was not an Entity. The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity. This method will require your methods to throw a CommandSyntaxException. The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player. | + | // Will end the command if the source of the command was not an Entity. |
+ | // The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity. | ||
+ | // This method will require your methods to throw a CommandSyntaxException. | ||
+ | // The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player. | ||
ServerPlayerEntity player = source.getPlayer(); | ServerPlayerEntity player = source.getPlayer(); | ||
Line 247: | Line 261: | ||
The ServerCommandSource also provides other information about the sender of the command. | The ServerCommandSource also provides other information about the sender of the command. | ||
- | <code java> | + | <code java [enable_line_numbers=" |
source.getPosition(); | source.getPosition(); | ||
// Get's the sender' | // Get's the sender' | ||
Line 273: | Line 287: | ||
=== Broadcast a message === | === Broadcast a message === | ||
- | <code java> | + | <code java [enable_line_numbers=" |
public static void register(CommandDispatcher< | public static void register(CommandDispatcher< | ||
dispatcher.register(literal(" | dispatcher.register(literal(" | ||
Line 282: | Line 296: | ||
} | } | ||
- | public int broadcast(ServerCommandSource source, Formatting formatting, String message) { | + | public |
Text text = new LiteralText(message).formatting(formatting); | Text text = new LiteralText(message).formatting(formatting); | ||
Line 294: | Line 308: | ||
First the basic code where we register " | First the basic code where we register " | ||
- | <code java> | + | <code java [enable_line_numbers=" |
public static LiteralCommandNode register(CommandDispatcher< | public static LiteralCommandNode register(CommandDispatcher< | ||
return dispatcher.register(literal(" | return dispatcher.register(literal(" | ||
Line 303: | Line 317: | ||
Then since we only want to give to players, we check if the CommandSource is a player. But we can use '' | Then since we only want to give to players, we check if the CommandSource is a player. But we can use '' | ||
- | <code java> | + | <code java [enable_line_numbers=" |
- | public static | + | public static |
ServerCommandSource source = ctx.getSource(); | ServerCommandSource source = ctx.getSource(); | ||
Line 312: | Line 326: | ||
Then we add to the player' | Then we add to the player' | ||
- | <code java> | + | <code java [enable_line_numbers=" |
if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){ | if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){ | ||
throw new SimpleCommandExceptionType(new TranslateableText(" | throw new SimpleCommandExceptionType(new TranslateableText(" | ||
Line 328: | Line 342: | ||
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. | 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. | ||
- | <code java> | + | <code java [enable_line_numbers=" |
public static void register(CommandDispatcher< | public static void register(CommandDispatcher< | ||
dispatcher.register(literal(" | dispatcher.register(literal(" | ||
Line 339: | Line 353: | ||
Then the creation and messages behind the joke. | Then the creation and messages behind the joke. | ||
- | <code java> | + | <code java [enable_line_numbers=" |
- | public static | + | public static |
if(blockPos==null) { | if(blockPos==null) { | ||
Line 351: | Line 365: | ||
source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText(" | source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText(" | ||
source.getWorld().spawnEntity(tnt); | source.getWorld().spawnEntity(tnt); | ||
+ | return 1; | ||
} | } | ||
</ | </ | ||
+ | === Finding Biomes via Command === | ||
+ | |||
+ | This example shows examples of redirects, exceptions, suggestions and a tiny bit of text. Note this command when used works but can take a bit of time to work similarly to ''/ | ||
+ | <code java [enable_line_numbers=" | ||
+ | public class CommandLocateBiome { | ||
+ | // First make method to register | ||
+ | public static void register(CommandDispatcher< | ||
+ | LiteralCommandNode< | ||
+ | .then(argument(" | ||
+ | .then(argument(" | ||
+ | .executes(ctx -> execute(ctx.getSource(), | ||
+ | .executes(ctx -> execute(ctx.getSource(), | ||
+ | // Register redirect | ||
+ | dispatcher.register(literal(" | ||
+ | .redirect(basenode)); | ||
+ | } | ||
+ | // Beginning of the method | ||
+ | private static int execute(ServerCommandSource source, Identifier biomeId, int range) throws CommandSyntaxException { | ||
+ | Biome biome = Registry.BIOME.get(biomeId); | ||
+ | | ||
+ | if(biome == null) { // Since the argument is an Identifier we need to check if the identifier actually exists in the registry | ||
+ | throw new SimpleCommandExceptionType(new TranslatableText(" | ||
+ | } | ||
+ | | ||
+ | List< | ||
+ | bio.add(biome); | ||
+ | | ||
+ | ServerWorld world = source.getWorld(); | ||
+ | | ||
+ | BiomeSource bsource = world.getChunkManager().getChunkGenerator().getBiomeSource(); | ||
+ | | ||
+ | BlockPos loc = new BlockPos(source.getPosition()); | ||
+ | // Now here is the heaviest part of the method. | ||
+ | BlockPos pos = bsource.locateBiome(loc.getX(), | ||
+ | | ||
+ | // Since this method can return null if it failed to find a biome | ||
+ | if(pos == null) { | ||
+ | throw new SimpleCommandExceptionType(new TranslatableText(" | ||
+ | } | ||
+ | | ||
+ | int distance = MathHelper.floor(getDistance(loc.getX(), | ||
+ | // Popup text that can suggest commands. This is the exact same system that /locate uses. | ||
+ | Text teleportButtonPopup = Texts.bracketed(new TranslatableText(" | ||
+ | style_1x.setColor(Formatting.GREEN).setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, | ||
+ | }); | ||
+ | | ||
+ | source.sendFeedback(new TranslatableText(" | ||
+ | |||
+ | return 1; | ||
+ | } | ||
+ | // Just a normal old 2d distance method. | ||
+ | private static float getDistance(int int_1, int int_2, int int_3, int int_4) { | ||
+ | int int_5 = int_3 - int_1; | ||
+ | int int_6 = int_4 - int_2; | ||
+ | |||
+ | return MathHelper.sqrt((float) (int_5 * int_5 + int_6 * int_6)); | ||
+ | } | ||
+ | | ||
+ | | ||
+ | |||
+ | public static class BiomeCompletionProvider { | ||
+ | // This provides suggestions of what biomes can be selected. Since this uses the registry, mods that add new biomes will work without modification. | ||
+ | public static final SuggestionProvider< | ||
+ | Registry.BIOME.getIds().stream().forEach(identifier -> builder.suggest(identifier.toString(), | ||
+ | return builder.buildFuture(); | ||
+ | }); | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Custom Arguments ===== | ||
+ | |||
+ | Brigadier has support for custom argument types and this section goes into showing how to create a simple argument type. For this example we will create a UUIDArgumentType | ||
+ | |||
+ | First create a class which extends '' | ||
+ | |||
+ | <code java> | ||
+ | public class UUIDArgumentType implements ArgumentType< | ||
+ | </ | ||
+ | |||
+ | ArgumentType requires you to implement the '' | ||
+ | <code java> | ||
+ | @Override | ||
+ | public UUID parse(StringReader reader) throws CommandSyntaxException { | ||
+ | </ | ||
+ | |||
+ | This method is where all of your parsing will occur. Either this method will return the object based on the arguments provided in the command line or throw a CommandSyntaxException and parsing will fail. | ||
+ | |||
+ | Next you will store the current position of the cursor, this is so you can substring out only the specific argument. This will always be at the beginning of where your argument appears on the command line. | ||
+ | |||
+ | <code java> | ||
+ | int argBeginning = reader.getCursor(); | ||
+ | if (!reader.canRead()) { | ||
+ | reader.skip(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Now we grab the entire argument. Depending on your argument type, you may have a different criteria or be similar to some arguments where detecting a '' | ||
+ | |||
+ | <code java> | ||
+ | while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position. | ||
+ | reader.skip(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Then we will ask the StringReader what the current position of the cursor is an substring our argument out of the command line. | ||
+ | |||
+ | <code java> | ||
+ | |||
+ | Now finally we check if our argument is correct and parse the specific argument to our liking, and throwing an exception if the parsing fails. | ||
+ | |||
+ | <code java> | ||
+ | try { | ||
+ | UUID uuid = UUID.fromString(uuidString); | ||
+ | return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on. | ||
+ | } catch (Exception ex) { | ||
+ | // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type. | ||
+ | // Create with context tells Brigadier to supply some context to tell the user where the command failed at. | ||
+ | // Though normal create method could be used. | ||
+ | throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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. | ||
+ | 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. | ||
+ | |||
+ | <code java> | ||
+ | ArgumentTypes.register(" | ||
+ | // The argument should be what will create the ArgumentType. | ||
+ | </ | ||
+ | |||
+ | And here is the whole ArgumentType: | ||
+ | |||
+ | <file java UUIDArgumentType.java> | ||
+ | |||
+ | import com.mojang.brigadier.StringReader; | ||
+ | import com.mojang.brigadier.arguments.ArgumentType; | ||
+ | import com.mojang.brigadier.context.CommandContext; | ||
+ | import com.mojang.brigadier.exceptions.CommandSyntaxException; | ||
+ | import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; | ||
+ | import net.minecraft.text.LiteralText; | ||
+ | import net.minecraft.util.SystemUtil; | ||
+ | |||
+ | import java.util.ArrayList; | ||
+ | import java.util.Collection; | ||
+ | import java.util.UUID; | ||
+ | |||
+ | /** | ||
+ | * Represents an ArgumentType that will return a UUID. | ||
+ | */ | ||
+ | public class UUIDArgumentType implements ArgumentType< | ||
+ | // This method exists for convince and you could just initialize the class. | ||
+ | public static UUIDArgumentType uuid() { | ||
+ | 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< | ||
+ | // Note that you should assume the CommandSource wrapped inside of the CommandContext will always be a generic type. | ||
+ | // If you access the ServerCommandSource make sure you verify the source is an instanceof ServerCommandSource before dangerous casting. | ||
+ | return context.getArgument(name, | ||
+ | } | ||
+ | |||
+ | private static final Collection< | ||
+ | list.add(" | ||
+ | list.add(" | ||
+ | list.add(" | ||
+ | }); | ||
+ | |||
+ | @Override | ||
+ | public UUID parse(StringReader reader) throws CommandSyntaxException { | ||
+ | int argBeginning = reader.getCursor(); | ||
+ | if (!reader.canRead()) { | ||
+ | reader.skip(); | ||
+ | } | ||
+ | |||
+ | // Now we check the contents of the argument till either we hit the end of the command line (When canRead becomes false) | ||
+ | // Otherwise we go till reach reach a space, which signifies the next argument | ||
+ | while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position. | ||
+ | reader.skip(); | ||
+ | } | ||
+ | |||
+ | // Now we substring the specific part we want to see using the starting cursor position and the ends where the next argument starts. | ||
+ | String uuidString = reader.getString().substring(argBeginning, | ||
+ | try { | ||
+ | UUID uuid = UUID.fromString(uuidString); | ||
+ | return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on. | ||
+ | } catch (Exception ex) { | ||
+ | // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type. | ||
+ | // Create with context tells Brigadier to supply some context to tell the user where the command failed at. | ||
+ | // Though normal create method could be used. | ||
+ | throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Collection< | ||
+ | return EXAMPLES; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
===== FAQ ===== | ===== FAQ ===== | ||
tutorial/commands.txt · Last modified: 2024/02/23 14:22 by allen1210