Documentation for v1.5
An API to use the new command UI introduced in Minecraft 1.13
Contents
- Installation for server owners
- Using the CommandAPI in your projects
- Config.yml
- Basic usage (for developers)
- Command registration
- Arguments
- Permissions
- CommandExecutor
- Examples
- FAQ
Installation (For server owners)
- Download the v1.5 CommandAPI.jar from the download page here
- Place the CommandAPI.jar file in your server's
/plugins/
folder - That's it!
Config.yml
Default config.yml:
verbose-outputs: true
create-dispatcher-json: true
test-code: false
verbose-outputs
- Outputs command registration and unregistration in the consolecreate-dispatcher-json
- Creates acommand_registration.json
file showing the mapping of registered commandstest-code
- Runs test code. Not recommended.
Using the CommandAPI in your projects (For developers)
Manual Installation with .jar
- Download the v1.5 CommandAPI.jar from the download page here
- Add the CommandAPI.jar file to your project/environment's build path
- Add the plugin as a dependent in the plugin.yml (
depend: [CommandAPI]
)
Maven
-
Add the maven repository:
<repository> <id>mccommandapi</id> <url>https://raw.githubusercontent.com/JorelAli/1.13-Command-API/mvn-repo/1.13CommandAPI/</url> </repository>
-
Add the dependency:
<dependency> <groupId>io.github.jorelali</groupId> <artifactId>commandapi</artifactId> <version>1.5</version> </dependency>
Basic usage (For developers)
-
Generate a
LinkedHashMap<String, Argument>
to store your arguments for your command. The insertion order IS IMPORTANT.LinkedHashMap<String, Argument> args = new LinkedHashMap<>(); args.put("time", new IntegerArgument());
-
Register your command using the CommandAPI instance
CommandAPI.getInstance().register("mycommand", arguments, (sender, args) -> { if(sender instanceof Player) { Player player = (Player) sender; player.getWorld().setTime((int) args[0]); } });
Command registration
CommandRegistration method | Outcome |
---|---|
CommandAPI.getInstance().register(String, LinkedHashMap, CommandExecutor) | Basic command registration |
CommandAPI.getInstance().register(String, String[], LinkedHashMap, CommandExecutor) | Register command with an array of aliases |
CommandAPI.getInstance().register(String, CommandPermission, LinkedHashMap, CommandExecutor) | Register command which need certain permissions |
CommandAPI.getInstance().register(String, CommandPermission, String[], LinkedHashMap, CommandExecutor) | Register command with aliases and permission requirements |
Arguments
Arguments are found in the io.github.jorelali.commandapi.api.arguments
package.
Argument class | Bukkit data type | Data type | Notes |
---|---|---|---|
new BooleanArgument() | ❌ | boolean | |
new ChatColorArgument() | ✔️ | ChatColor | |
new ChatComponentArgument() | ✔️* | BaseComponent[] | Requires Spigot, see below for usage notes |
new DoubleArgument() | ❌ | double | |
new EnchantmentArgument() | ✔️ | Enchantment | |
new EntitySelectorArgument(EntitySelector) | ✔️ | Entity , Player , Collection<Entity> , Collection<Player> | See below for usage |
new EntityTypeArgument() | ✔️ | EntityType | |
new FloatArgument() | ❌ | float | |
new GreedyStringArgument() | ❌ | String | Can have any length |
new IntegerArgument() | ❌ | int | |
new ItemStackArgument() | ✔️ | ItemStack | Returns an ItemStack with amount 1 |
new LiteralArgument(String) | ❌ | N/A | See below for usage |
new LocationArgument() | ✔️ | Location | |
new ParticleArgument() | ✔️ | Particle | |
new PlayerArgument() | ✔️ | Player | Always returns 1 player |
new PotionEffectArgument() | ✔️ | PotionEffectType | |
new StringArgument() | ❌ | String | Always consists of 1 word |
new SuggestedStringArgument(String[]) | ❌ | String | See below for usage |
new TextArgument() | ❌ | String | Can have spaces (used for text) |
Argument Casting
To access arguments, they are casted in the order of declaration.
LinkedHashMap<String, ArgumentType> arguments = new LinkedHashMap<>();
arguments.put("arg0", new StringArgument());
arguments.put("arg1", new PotionEffectArgument());
arguments.put("arg2", new LocationArgument());
commandRegister.register("cmd", arguments, (sender, args) -> {
String stringArg = (String) args[0];
PotionEffectType potionArg = (PotionEffectType) args[1];
Location locationArg = (Location) args[2];
});
Ranged Arguments
Numerical arguments (int
, float
and double
) can have ranged values.
Constructor | Expression |
---|---|
new IntegerArgument() | int |
new IntegerArgument(2) | 2 ≤ int |
new IntegerArgument(2, 10) | 2 ≤ int ≤ 10 |
Entity Selector Arguments
Target selectors are implemented using the EntitySelectorArgument
class. This allows you to select specific entities based on certain attributes.
The EntitySelectorArgument
class requires an EntitySelector
argument to determine what type of data to return. There are 4 types of entity selections which are available:
EntitySelector.ONE_ENTITY
- A single entity, which returns aEntity
object.EntitySelector.MANY_ENTITIES
- A collection of many entities, which returns aCollection<Entity>
object.EntitySelector.ONE_PLAYER
- A single player, which returns aPlayer
object.EntitySelector.MANY_PLAYERS
- A collection of players, which returns aCollection<Player>
object.
The return type is the type to be cast when retrieved from the Object[] args
in the command declaration.
//LinkedHashMap to store arguments for the command
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
//Using a collective entity selector to select multiple entities
arguments.put("entities", new EntitySelectorArgument(EntitySelector.MANY_ENTITIES));
CommandAPI.getInstance().register("kill", arguments, (sender, args) -> {
//Parse the argument as a collection of entities (as stated above in the documentation)
Collection<Entity> entities = (Collection<Entity>) args[0];
sender.sendMessage("killed " + entities.size() + "entities");
for(Entity e : entity)
e.remove();
});
You can view an example of using the EntitySelectorArgument here
Chat Component Arguments
The ChatComponentArgument
uses Spigot's BaseComponent
class to accept raw JSON text as a valid input. When raw JSON is inputted, the ChatComponentArgument
will return a BaseComponent[]
which can be used for books and raw messages. You can read more about raw JSON here, and Spigot's Chat Component API here. You can view some examples with the ChatComponentArgument
in the Examples section.
If used on a non-spigot server (e.g. a server running CraftBukkit), the CommandAPI will throw a SpigotNotFoundException
.
Literal Arguments & Suggested String Arguments
Introduced in v1.3 is the LiteralArgument
class. This allows you to basically create "lists" in your commands, or specific commands where a desired text option is required.
LiteralArgument(String literal)
takes a String
input which is the text which the argument represents.
For example, take the /gamemode
command. It takes an argument which is the game mode, which is picked from a list: adventure, creative, spectator or survival.
Literal arguments are "technically" not arguments, however they are declared as you would a regular argument.
//Used as a regular argument, in your LinkedHashMap
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("gamemodearg", new LiteralArgument("adventure"));
CommandAPI.getInstance().register("gamemode", arguments, (sender, args) -> {
if(sender instanceof Player) {
Player player = (Player) sender;
player.setGameMode(GameMode.ADVENTURE);
}
});
Since Literal arguments are not "technically" arguments, LITERAL ARGUMENTS ARE NOT DEFINED IN args
. So, with the example above, args
is an empty array.
To use lists, you can iterate over a list/map to generate multiple commands at once:
//Create a map of gamemode names to their respective objects
HashMap<String, GameMode> gamemodes = new HashMap<>();
gamemodes.put("adventure", GameMode.ADVENTURE);
gamemodes.put("creative", GameMode.CREATIVE);
gamemodes.put("spectator", GameMode.SPECTATOR);
gamemodes.put("survival", GameMode.SURVIVAL);
//Iterate over the map
for(String key : gamemodes.keySet()) {
//Create our arguments as usual, using the LiteralArgument for the name of the gamemode
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put(key, new LiteralArgument(key));
//Register the command as usual
CommandAPI.getInstance().register("gamemode", arguments, (sender, args) -> {
if(sender instanceof Player) {
Player player = (Player) sender;
//Retrieve the object from the map via the key and NOT the args[]
player.setGameMode(gamemodes.get(key));
}
});
}
Be aware that large nested lists are HIGHLY discouraged, as described in this comment
Failing to define a value for a literal (either by using null or an empty String) will result in a BadLiteralException
As of v1.5, the CommandAPI now includes the SuggestedStringArgument
class. This is similar to the LiteralArgument
class, however its usage is very different. The SuggestedStringArgument
uses an array of Strings which are displayed to the user when inputting values.
//Arguments
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
//Populate a list of suggestions with the name of each Material in the Material enum
/* This doesn't have the same effect that LiteralArgument has where each value is
* stored in the command dispatcher's JSON file. The suggestion list is generated
* at runtime and therefore doesn't impact on server memory or cause potential client issues. */
List<String> suggestions = Arrays.stream(Material.values()).map(element -> element.name()).collect(Collectors.toList());
//Create a SuggestedStringArgument
arguments.put("material", new SuggestedStringArgument(suggestions));
//Output the value submitted by the player
CommandAPI.getInstance().register("suggest", arguments, (sender, args) -> {
if (sender instanceof Player) {
Player player = (Player) sender;
player.sendMessage((String) args[0]);
}
});
The SuggestedStringArgument
is a suggestion. This means that the command sender MAY NOT enter a desired value stated in the list. For example:
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("examples", new SuggestedStringArgument("hello", "world", "blah"));
CommandAPI.getInstance().register("mycommand", arguments, (sender, args) -> {
String result = (String) args[0]; //String argument from /mycommand [examples]
});
This code will not guarantee that the user has entered hello
, world
or blah
. The user can type whatever they want, these are just suggestions.
LiteralArgument | SuggestedStringArgument |
---|---|
Forces the user to select a word from the list | The user doesn't have to select a word from the list |
Bloated and uses up more memory to store arguments | Argument list is determined at runtime and doesn't use up as much memory |
Input sanitization is performed before the command is run | Requires extra handling to deal with unintended inputs. This is implemented by you. |
Strings, Greedy Strings and Text arguments
StringArgument
The StringArgument
is used to represent a single word. These words can only contain alphanumeric characters (A-Z, a-z and 0-9), and the underscore character.
Accepted StringArgument
values:
/mycommand hello
/mycommand 123
/mycommand hello123
Rejected StringArgument
values:
/mycommand hello@gmail.com
/mycommand yesn't
Potential Uses
- Entering Strings to identify offline players
TextArgument
The TextArgument
acts similar to any String in Java. These can be single words, like to the StringArgument
, or have additional values such as special characters if quoted. To type quotation marks, you can use \"
(as similar to Java) to escape these special characters.
Accepted TextArgument
values:
/mycommand hello
/mycommand "hello world!"
/mycommand "hello@gmail.com"
/mycommand "this has \" <<-- speech marks! "
Rejected TextArgument
values:
/mycommand hello world
/mycommand 私
/mycommand "speech marks: ""
Potential Uses
- A command to edit the contents on a sign
- Any command that may require multiple text arguments
GreedyStringArgument
The GreedyStringArgument
takes the TextArgument
a step further. Any characters and symbols are allowed and quotation marks are not required. However, the GreedyStringArgument
uses the entirety of the argument array from its position.
For example, say we have a command /msg <target> <message>
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("target", new PlayerArgument());
arguments.put("message", new GreedyStringArgument());
CommandAPI.getInstance().register("msg", arguments, (sender, args) -> {
((Player) args[0]).sendMessage((String) args[1]);
});
Any text entered after the <target>
argument would be sent to the player. For example, the command:
/msg Skepter hello how are you? I'm doing great! Last week I sent an email to blah@mail.com
would send Skepter
a message saying hello how are you? I'm doing great! Last week I sent an email to blah@mail.com
.
Due to the fact that the GreedyStringArgument
has no terminator (it has infinite length), a GreedyStringArgument
must be defined at the end of the LinkedHashMap
(otherwise the CommandAPI will throw a GreedyStringException
)
For example, if the syntax was/msg <message> <target>
, it would not be able to determine where the message ends and the <target>
argument begins.
Potential Uses
- A messaging/whisper command
- A mailing command
- Any command involving lots of text, such as a book writing command*
- Any command which involves an unreasonable/unknown amount of arguments
- Any command where you want to parse arguments similar to how regular Bukkit would
*New feature potentially coming soon!
Multiple command arguments
Sometimes, you'll want one command to have different arguments. For example:
/walkspeed <speed>
to change your walk speed/walkspeed <speed> <target>
to change the walk speed of another player
To accommodate for this, just register the command twice, each with different arguments:
LinkedHashMap<String, ArgumentType> arguments = new LinkedHashMap<>();
arguments.put("speed", new FloatArgument(0.0, 1.0)); //args[0]
//Register the command
CommandAPI.getInstance().register("walkspeed", arguments, (sender, args) -> {
float speed = (float) args[0]; //speed argument
//Code here to change the player's speed
});
//We can still use the arguments variable to add new arguments as the first command has already been registered
arguments.put("target", new PlayerArgument()); //args[1]
//Register the command
CommandAPI.getInstance().register("walkspeed", arguments, (sender, args) -> {
float speed = (float) args[0]; //speed argument
Player target = (Player) args[1]; //target player
//Code here to change the target's speed
});
Permissions
Permissions are created using the CommandPermission
class.
Constructor | Outcome |
---|---|
new CommandPermissions("my.permission") | Requires my.permission to run the command |
new CommandPermissions("my.perm1", "my.perm2") | Requires my.perm1 and my.perm2 to run the command |
new CommandPermissions(PermissionNode.OP) | Requires sender to be an OP to run the command |
new CommandPermissions(PermissionNode.NONE) | Anyone can run the command |
CommandExecutor
The CommandExecutor
class (not to be confused with Bukkit's CommandExecutor class) is a functional interface which executes when the command is performed. It consists of two parameters, CommandSender sender
and Object[] args
.
CommandExecutor format (Java 8 lambda)
CommandExecutor executor = (sender, args) -> {
//Code here
};
CommandExecutor format (Java 7)
CommandExecutor executor = new CommandExecutor() {
@Override
public void run(CommandSender sender, Object[] args) {
//Code here
}
};
Command name checking and permission checks aren't required as these are checked when the player types the command.
ProxiedCommandSenders
As of v1.3, the CommandAPI now has some support for the /execute
command, which is implemented using the ProxiedCommandSender
When using a command generated by the CommandAPI, it will modify the CommandSender
when run depending of the CommandSender
was changed in execution.
For example:
- Running
/mycommand
as a player in game will return a regularCommandSender
, which can be cast to aPlayer
object. (Player player = (Player) sender
) - Running
/execute as @e[type=cow] run mycommand
as a player in game will return aProxiedCommandSender
, with callee as a cow and caller as a player.
Examples examples examples!
Give Command
/give <item> <amount>
/give <target> <item> <amount>
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("item", new ItemStackArgument()); //args[0]
arguments.put("amount", new IntegerArgument(1, 64)); //args[1]
CommandAPI.getInstance().register("give", new CommandPermission(PermissionNode.OP), new String[] {"i", "item"}, arguments, (sender, args) -> {
if(sender instanceof Player) {
Player player = (Player) sender;
ItemStack is = (ItemStack) args[0];
is.setAmount((int) args[1]);
player.getInventory().addItem(is);
}
});
arguments = new LinkedHashMap<>();
arguments.put("target", new PlayerArgument()); //args0[0]
arguments.put("item", new ItemStackArgument()); //args[1]
arguments.put("amount", new IntegerArgument(1, 64)); //args[2]
CommandAPI.getInstance().register("give", new CommandPermission(PermissionNode.OP), new String[] {"i", "item"}, arguments, (sender, args) -> {
Player target = (Player) args[0];
ItemStack is = (ItemStack) args[1];
is.setAmount((int) args[2]);
target.getInventory().addItem(is);
});
Enchant Command
/enchant <level> <force enchant>
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("level", new IntegerArgument()); //args[0]
arguments.put("force enchant", new BooleanArgument()); //args[1]
CommandAPI.getInstance().register("enchant", new CommandPermission("plugin.enchant"), arguments, (sender, args) -> {
if(sender instanceof Player) {
Player player = (Player) sender;
if((boolean) args[1]) {
player.getInventory().getItemInMainHand().addUnsafeEnchantment((Enchantment) args[1], (int) args[0]);
} else {
player.getInventory().getItemInMainHand().addEnchantment((Enchantment) args[1], (int) args[0]);
}
}
});
SetBlock Command
/setblock <location> <type>
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("location", new LocationArgument()); //args[0]
arguments.put("type", new ItemStackArgument()); //args[1]
CommandAPI.getInstance().register("setblock", arguments, (sender, args) -> {
if(sender instanceof Player) {
Player player = (Player) sender;
Material type = ((ItemStack) args[1]).getType();
player.getWorld().getBlockAt((Location) args[0]).setType(type);
}
});
Message Command
/message <target> <message>
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("target", new PlayerArgument()); //args[0]
arguments.put("message", new TextArgument()); //args[1]
CommandAPI.getInstance().register("message", arguments, (sender, args) -> {
Player target = (Player) args[0];
target.sendMessage((String) args[1]);
});
Using the ChatComponentArgument:
Send a message with raw JSON:
/raw <target> <raw message>
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("target", new PlayerArgument()); //args[0]
arguments.put("rawText", new ChatComponentArgument()); //args[1]
CommandAPI.getInstance().register("raw", arguments, (sender, args) -> {
Player target = (Player) args[0];
//Retrieve BaseComponent[] and send to player via spigot() method
BaseComponent[] arr = (BaseComponent[]) args[1];
target.spigot().sendMessage(arr);
});
Create a book with raw JSON:
/makebook <contents>
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("contents", new ChatComponentArgument());
CommandAPI.getInstance().register("makebook", arguments, (sender, args) -> {
if (sender instanceof Player) {
Player player = (Player) sender;
BaseComponent[] arr = (BaseComponent[]) args[0];
//Create book
ItemStack is = new ItemStack(Material.WRITTEN_BOOK);
BookMeta meta = (BookMeta) is.getItemMeta();
meta.spigot().addPage(arr);
is.setItemMeta(meta);
//Give player the book
player.getInventory().addItem(is);
}
});
FAQ
Why does the PlayerArgument only produce one player when @a is an option?
A: Simplicity. The old command system which involved using Bukkit.getPlayer()
and parsing a username only produces one player. To make it as simple to use, this was the best option. Alternatively, you can use the new EntitySelectorArgument
to select multiple players.
What about the other argument types?
Q: There's loads more argument types, including NBT tags, rotational position (i.e. pitch/yaw), inventory slots, scoreboards.... why aren't they implemented?
A: Again, for simplicity. NBT tags would require lots of NMS code to convert (and isn't majorly supported by Bukkit on its own). Rotational position isn't a majorly important argument - floating point arguments work fine. I don't really know much about how scoreboards work (if someone wants to help, I'm open to collaboration)