Introduction
Welcome to the documentation for the CommandAPI. The CommandAPI lets you create vanilla Minecraft commands which utilize the new command features which were implemented in Minecraft 1.13, including but not limited to:
- Having commands compatible with the vanilla
/execute
command - Having commands which can be run using Minecraft functions
- Having better auto-completion and suggestions
- Having command type checks before execution (e.g. ensuring a number is within a certain range)
How the CommandAPI works
Developer's Note:
This is a pretty important section, I would recommend reading before implementing the CommandAPI in your own projects. This section tells you about setup which is not stated anywhere else in the documentation. Think of it as the "knowledge you should know before using this API".
The CommandAPI does not follow the "standard" method of registering commands. In other words, commands which are registered with the CommandAPI will be registered as pure vanilla Minecraft commands as opposed to Bukkit or Spigot commands. This means that the following implications exist:
- Commands do not need to be declared in the
plugin.yml
file - Commands are not "linked" to a certain plugin. In other words, you cannot look up which commands are registered by which plugin.
How this documentation works
This documentation is split into the major sections that build up the CommandAPI. It's been designed in such a way that it should be easy to find exactly what you want to help you get started with the CommandAPI, how it's structured and how to make effective use of it. Each step of the way, the documentation will include examples which showcase how to use the CommandAPI.
You can use the side bar on the left to access the various sections of the documentation and can change the theme to your liking using the paintbrush icon in the top left corner.
Using the search icon in the top left corner, typing "Example" will show a list of examples which are included throughout the documentation.
Documentation changelog
Whenever a new version of the CommandAPI comes out, the version number changes (as you'd expect). In the same manner, if any changes to the documentation were made, the documentation version number changes. Ensure you keep up to date on the latest changes to the documentation (You can view the documentation version at the top of the page) when new versions of the CommandAPI are released. This changelog below gives a brief overview of the changes to pages that were made between each version of the documentation, as only the latest version of the documentation is hosted online.
- 2.0 → 2.1
- Click here - Include information about tooltips
- Click here - Adds information on technical arguments
- Click here - Improve documentation for adding dependencies and repositories to the
pom.xml
file
- 1.8 → 2.0
- Click here - Deprecated
SuggestedStringArgument
, use.overrideSuggestions()
instead - Click here - Adds a new argument, the
CustomArgument
- Click here - The
DynamicSuggestedArgument
now has access to theCommandSender
object
- Click here - Deprecated
Installation
Installing the CommandAPI is easy! Just download the CommandAPI.jar file and place it in your server's plugins/
folder!
Download latest CommandAPI.jar
Configuring the CommandAPI
The CommandAPI can be configured in the plugins/CommandAPI/config.yml
file.
The default config.yml
settings are as follows:
verbose-outputs: true
create-dispatcher-json: true
verbose-outputs
- Outputs command registration and unregistration logs in the consolecreate-dispatcher-json
- Creates acommand_registration.json
file showing the mapping of registered commands
Setting up your development environment
To use the CommandAPI in your plugins, there are two methods of adding it to your development environment:
Manually using the .jar
- Download the latest 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]
)
Using Maven (recommended)
Developer's Note:
If you've never used maven before, I highly recommend it! It makes it easier to keep your code updated with the latest dependency updates. For information on how to set up a plugin using maven, you can read Bukkit's plugin tutorial.
-
Add the maven repository to your
pom.xml
file:<repositories> <repository> <id>mccommandapi</id> <url>https://raw.githubusercontent.com/JorelAli/1.13-Command-API/mvn-repo/1.13CommandAPI/</url> </repository> </repositories>
-
Add the dependency to your
pom.xml
:<dependencies> <dependency> <groupId>io.github.jorelali</groupId> <artifactId>commandapi</artifactId> <version>VERSION</version> </dependency> </dependencies>
A list of version numbers can be found here. For example, if you wanted to use version 2.0, you would use
<version>2.0</version>
-
Add the plugin as a dependent in the plugin.yml (
depend: [CommandAPI]
)
Using Gradle
-
Add the repository to your
build.gradle
file:repositories { maven { name = 'mccommandapi' url = 'https://raw.githubusercontent.com/JorelAli/1.13-Command-API/mvn-repo/1.13CommandAPI/' } }
-
Add the dependency to your list of dependencies in your
build.gradle
file:dependencies { compile "io.github.jorelali:commandapi:VERSION" }
A list of version numbers can be found here. For example, if you wanted to use version 2.0, you would use
compile "io.github.jorelali:commandapi:2.0"
-
Add the plugin as a dependent in the plugin.yml (
depend: [CommandAPI]
)
Command registration
To register commands with the CommandAPI, there are several methods which can be used, depending on whether you want your command to have aliases or permissions in order to run it.
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 |
The following fields are as follows:
-
String
- The command nameThe first argument represents the command name which will be registered. For instance, to register the command
/god
, you would use the following:CommandAPI.getInstance().register("god", ...);
-
LinkedHashMap<String, Argument>
- The list of argumentsThe CommandAPI requires a list of arguments which are used for the command. The argument map consists of a key which is the tooltip that is displayed as a prompt to users entering commands, and a value which is an instance of an argument (See the section on arguments). This list of arguments is interpreted in the order that arguments are added to the LinkedHashMap.
-
String[]
- An array of aliases that the command can be run via -
CommandPermission
- The required permission to execute a command. (See the section on permissions).
Command loading order
In order to register commands properly, commands must be registered before the server finishes loading. The CommandAPI will prevent command registration after the server has loaded. This basically means that all command registration must occur during a plugin's onLoad()
or onEnable()
method. With the CommandAPI, depending on which of these functions you load your commands is crutial if your plugin is used with Minecraft's functions.
When to load | What to do |
---|---|
onLoad() method | Register commands to be used in Minecraft functions (see Function section for more info) |
onEnable() method | Register regular commands |
Command unregistration
The CommandAPI has support to unregister commands completely from Minecraft's command list. This includes Minecraft built in commands!
Method | Result |
---|---|
CommandAPI.getInstance().unregister(String cmd) | Unregisters a command from the game |
CommandAPI.getInstance().unregister(String cmd, boolean force) | Attempts to unregister a command from the game by force. This includes /minecraft:cmd , /bukkit:cmd and /spigot:cmd commands as well. |
Example - Replacing Minecraft's /gamemode
command
To replace a command, we can first unregister it and then register our implementation of that command.
//Unregister the gamemode command from the server (by force)
CommandAPI.getInstance().unregister("gamemode", true);
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
/* Arguments for the gamemode command. In this sample, I'm just
* using a simple literal argument which allows for /gamemode survival */
arguments.put("gamemode", new LiteralArgument("survival"));
CommandAPI.getInstance().register("gamemode", arguments, (sender, args) -> {
//Implementation of our /gamemode command
});
Developer's Note:
Command unregistration, although powerful, is highly unrecommended. It is the CommandAPI's most "dangerous" feature as it can cause unexpected sideffects, such as command blocks executing commands you wouldn't expect them to. In almost every case, I'd recommend just creating a new command instead of unregistering one to replace it.
For instance, instead of unregistering
/gamemode
, you could register a command/gm
or/customgamemode
.
Command executors
Developer's Note:
This section can be a little bit difficult to follow. If you only want the bare basic features (executes a command), read the section below on Normal command executors - this behaves very similar to the
onCommand
method in Bukkit.
The CommandAPI provides two separate command executors which are lambdas which execute the code you want when a command is called. These are the classes CommandExecutor
(Not to be confused with Bukkit's CommandExecutor
class), which just runs the contents of a command, and ResultingCommandExecutor
that returns an integral (whole number) result.
Developer's Note:
In general, you need not focus too much on what type of command executor to implement. If you know for certain that you're going to be using your command with command blocks, just ensure you return an integer at the end of your declared command executor. Java will infer the type (whether it's a CommandExecutor or ResultingCommandExecutor) automatically, so feel free to return an integer or not.
Normal command executors
Command executors are of the following format, where sender
is a CommandSender
, and args
is an Object[]
, which represents arguments which are parsed by the CommandAPI.
(sender, args) -> {
//Code here
};
With normal command executors, these do not need to return anything. By default, this will return a success value of 1 if it runs successfully, and a success value of 0 if it runs unsuccessfully, either by throwing an exception (RuntimeException) or by forcing the command to fail.
Forcing commands to fail
Sometimes, you want your command to fail on purpose. This is basically the way to "gracefully" handle errors in your command execution. This is performed using the following method:
CommandAPI.fail("Error message goes here");
When the CommandAPI calls the fail method, it will cause the command to return a success value of 0, to indicate failure.
Example - Command failing for element not in a list
Say we have some list, List<String>
containing a bunch of fruit and the player can choose from it. In order to do that, we can use a StringArgument
and suggest it to the player using .overrideSuggestions(String[])
. However, because this only lists suggestions to the player, it does not stop the player from entering an option that isn't on the list of suggestions.
Therefore, to gracefully handle this with a proper error message, we use CommandAPI.fail(String)
with a meaningful error message which is displayed to the user.
//Array of fruit
List<String> fruit = new ArrayList<>();
fruit.add("banana");
fruit.add("apple");
fruit.add("orange");
//Argument accepting a String, suggested with the list of fruit
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("item", new StringArgument().overrideSuggestions(fruit.toArray(new String[fruit.size()])));
//Register the command
CommandAPI.getInstance().register("getfruit", arguments, (sender, args) -> {
String inputFruit = (String) args[0];
if(fruit.contains(inputFruit)) {
//Do something with inputFruit
} else {
//The player's input is not in the list of fruit
CommandAPI.fail("That fruit doesn't exist!");
}
});
Developer's Note:
In general, it's a good idea to handle unexpected cases with the
CommandAPI.fail()
method. Most arguments used by the CommandAPI will have their own builtin failsafe system (e.g. theEntitySelectorArgument
will not execute the command executor if it fails to find an entity), so this feature is for those extra cases.
Proxied commandsenders
The CommandAPI has extra support for vanilla Minecraft's /execute
command, by allowing the CommandSender to be an instance of the ProxiedCommandSender
class. This allows the CommandSender to contain two extra pieces of information: The "proxied sender" and the original sender.
Example - Running a command as a chicken
Say we have a command which kills the sender of a command. This is easily implemented as follows:
CommandAPI.getInstance().register("killme", new LinkedHashMap<String, Argument>(), (sender, args) -> {
if(sender instanceof Player) {
Player player = (Player) sender;
player.setHealth(0);
}
});
But what if the sender of the command is not a player? By using Minecraft's /execute
command, we could execute the command as any arbitrary entity, as shown with the command below:
/execute as @e[type=chicken] run killme
To handle this case, we can check if the sender is an instance of a ProxiedCommandSender
, and kill the callee
(the entity which is being 'forced' to run the command /killme)
CommandAPI.getInstance().register("killme", new LinkedHashMap<String, Argument>(), (sender, args) -> {
if(sender instanceof Player) {
Player player = (Player) sender;
player.setHealth(0);
} else if(sender instanceof ProxiedCommandSender) {
//Get the proxy CommandSender object from the CommandSender
ProxiedCommandSender proxy = (ProxiedCommandSender) sender;
//Check if the callee is an Entity
if(proxy.getCallee() instanceof Entity) {
//If so, kill the entity
Entity target = (Entity) proxy.getCallee();
entity.setHealth(0);
}
}
});
This allows the command above to run successfully, killing all chickens it can find.
Resulting command executors
Resulting command executors are very similar to normal command executors, the only difference is that they can return an integer result value.
(sender, args) -> {
//Code here
return /*some integer here*/ ;
};
Example - Random number result command
Say we want a command that returns a random number as a result. This can then be used by vanilla Minecraft's /execute store result ...
command, which can be used for other command block chains.
CommandAPI.getInstance().register("randnum", new LinkedHashMap<String, Argument>(), (sender, args) -> {
return new Random().nextInt();
});
This returns a success value of 1 (Because no errors or CommandAPI.fail(String)
was thrown) and a return value of a random number.
Example - Lootbox system with /execute
command
We can store state using /execute store
and we can perform conditional checks using /execute if
. By combining these, we can create a system which can be used with commandblocks to say, give players random lootboxes and redeem them.
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
//Register random number generator command from 1 to 99 (inclusive)
CommandAPI.getInstance().register("randomnumber", arguments, (sender, args) -> {
return ThreadLocalRandom.current().nextInt(1, 100); //Returns random number from 1 <= x < 100
});
//Register reward giving system for a target player
arguments.put("target", new EntitySelectorArgument(EntitySelector.ONE_PLAYER));
CommandAPI.getInstance().register("givereward", arguments, (sender, args) -> {
Player player = (Player) args[0];
player.getInventory().addItem(new ItemStack(Material.DIAMOND, 64));
Bukkit.broadcastMessage(player.getName() + " won a rare 64 diamonds from a loot box!");
}
Store a random number under the scoreboard score randVal
for a player called SomePlayer
, by executing the command /randomnumber
/execute store success score SomePlayer randVal run randomnumber
Check if the random number is equal to 1 (say we have some plugin which gives a reward to a player that happened to get a 1 from a random number command, thus giving them a 1/99 chance of winning)
/execute if score SomePlayer randVal matches 1 run givereward SomePlayer
Arguments
Arguments in the CommandAPI are registered by using a LinkedHashMap<String, Argument>
object. There are two things you need to keep in mind when creating arguments:
- The order which they will be used
- The type of each argument
By definition of a LinkedHashMap, the order of the elements inserted into it are preserved, meaning the order you add arguments to the LinkedHashMap will be the resulting order of which arguments are presented to the user when they run that command.
Adding arguments for registration is simple:
//Create LinkedHashMap
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
//Add an argument called "target", which is a PlayerArgument
arguments.put("target", new PlayerArgument());
The String value is the tooltip that is shown to a player when they are entering the command.
Argument Casting
To access arguments, they have to be casted to the type that the argument represents. The order of the arguments in the args[]
is the same as the order in which the arguments were declared.
LinkedHashMap<String, ArgumentType> arguments = new LinkedHashMap<>();
arguments.put("arg0", new StringArgument());
arguments.put("arg1", new PotionEffectArgument());
arguments.put("arg2", new LocationArgument());
CommandAPI.getInstance().register("cmd", arguments, (sender, args) -> {
String stringArg = (String) args[0];
PotionEffectType potionArg = (PotionEffectType) args[1];
Location locationArg = (Location) args[2];
});
The types of the arguments declared by the CommandAPI are all listed below:
List of arguments
Arguments are found in the io.github.jorelali.commandapi.api.arguments
package.
Argument class | Data type | Description |
---|---|---|
AdvancementArgument | Advancement | |
BooleanArgument | boolean | |
ChatColorArgument | ChatColor | |
ChatComponentArgument | BaseComponent[] | Formatted chat object |
DoubleArgument | double | |
DynamicSuggestedStringArgument | String | Suggested string using a supplier function |
EnchantmentArgument | Enchantment | |
EntitySelectorArgument | Entity , Player , Collection<Entity> , Collection<Player> | Selects an entity (similar to @a or @p ) |
EntityTypeArgument | EntityType | Selects a type of entity (e.g. Pig) |
FloatArgument | float | |
FunctionArgument | FunctionWrapper[] | A declared Minecraft function from a data pack |
GreedyStringArgument | String | A string of any length |
IntegerArgument | int | |
ItemStackArgument | ItemStack | Returns an ItemStack with amount 1 |
LiteralArgument | N/A | A predefined hardcoded argument name |
LocationArgument | Location | |
LootTableArgument | LootTable | |
ParticleArgument | Particle | |
PlayerArgument | Player | Similar to EntitySelector, but always returns 1 player |
PotionEffectArgument | PotionEffectType | |
RecipeArgument | Recipe | |
SoundArgument | Sound | |
StringArgument | String | String consisting of 1 word |
SuggestedStringArgument | String | A list of suggested one word strings |
TextArgument | String | String which can have spaces (used for text) |
Arguments with overrideable suggestions
Some arguments have a feature allowing you to override the list of suggestions they provide. This is achieved by using .overrideSuggestions(String[])
on an instance of an argument, with the String array consisting of suggestions that will be shown to the user whilst they type their command. It's been designed such that this returns the same argument so it can be used inline (handy, eh?)
Example - Friend list by overriding suggestions
Say you have a plugin which has a "friend list" for players. If you want to teleport to a friend in that list, you could use a PlayerArgument
, which has the list of suggestions overridden with the list of friends that that player has.
String[] friends = //Some String array populated with friends
LinkedHashMap<String, ArgumentType> arguments = new LinkedHashMap<>();
arguments.put("friend", new PlayerArgument().overrideSuggestions(friends));
CommandAPI.getInstance().register("friendtp", arguments, (sender, args) -> {
Player target = (Player) args[0];
Player player = (Player) sender;
player.teleport(target);
});
Primitive arguments
Boolean arguments
The BooleanArgument
class represents boolean values true
and false
.
Example - Config editing plugin
String[] configKeys = getConfig().getKeys(true).toArray(new String[getConfig().getKeys(true).size()]);
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("config-key", new TextArgument().overrideSuggestions(configKeys));
arguments.put("value", new BooleanArgument());
CommandAPI.getInstance().register("editconfig", arguments, (sender, args) -> {
getConfig().set((String) args[0], (boolean) args[1]);
});
Numerical arguments
Numbers are represented using the designated number classes:
Class | Description |
---|---|
IntegerArgument | Whole numbers |
DoubleArgument | Double precision floating point numbers |
FloatArgument | Single precision floating point numbers |
Each numerical argument can have ranges applied to them, which restricts the user to only entering numbers from within a certain range. This is done using the constructor, and the range specified:
Constructor | Description |
---|---|
new IntegerArgument() | Any range |
new IntegerArgument(2) | Values greater than or equal to 2 |
new IntegerArgument(2, 10) | Values greater than or equal to 2 and less than or equal to 10 |
Each range is inclusive, so it includes the number given to it.
Location argument
The LocationArgument
class is used to specify a location in the command sender's current world. This allows the user to enter three numbers as coordinates, or use relative coordinates (i.e. the ~
and ^
operators)
The LocationArgument
constructor requires a LocationType
, which specifies the type of location that is accepted by the command.
LocationType.BLOCK_POSITION
Integer block coordinates. The suggested location is the coordinates of block you are looking at when you type the command.
LocationType.PRECISE_POSITION
Exact coordinates. The suggested location is the exact coordinates of where your cursor is pointing at when you type the command.
By default, the LocationArgument
will use PRECISE_POSITION
.
Example - Break block by coordinates command
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
//We want to target blocks in particular, so use BLOCK_POSITION
arguments.put("block", new LocationArgument(LocationType.BLOCK_POSITION));
CommandAPI.getInstance().register("break", arguments, (sender, args) -> {
((Location) args[0]).getBlock().setType(Material.AIR);
});
Chat arguments
Chat color argument
The ChatColorArgument
class is used to represent a given chat color (e.g. red or green)
Example - Username color changing plugin
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("chatcolor", new ChatColorArgument());
CommandAPI.getInstance().register("namecolor", arguments, (sender, args) -> {
Player player = (Player) sender;
ChatColor color = (ChatColor) args[0];
player.setDisplayName(color + player.getDisplayName());
});
Chat component argument
Developer's Note:
The
ChatComponentArgument
class is dependent on a Spigot based server. This means that theChatComponentArgument
will not work on a non-Spigot based server, such as CraftBukkit. If you use this class on a non-Spigot based server, it will throw aSpigotNotFoundException
Spigot based servers include, but are not limited to:
- Spigot
- PaperSpigot
- TacoSpigot
The ChatComponentArgument
class accepts raw JSON as valid input. This is converted into Spigot's BaseComponent[]
which can be used for books and raw messages. You can read more about raw JSON here.
Example - Book made from raw JSON
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);
}
});
String arguments
String argument
The StringArgument
class 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:
Hello
123
hello123
Hello_world
Rejected StringArgument
values:
hello@email.com
yesn't
Potential uses for string arguments
- Entering Strings to identify offline players
Text argument
The TextArgument
acts similar to any String in Java. These can be single words, like to the StringArgument
, or have additional characters (e.g. spaces, symbols) if surrounded by quotes. To type quotation marks, you can use \"
(as similar to Java) to escape these special characters.
Accepted TextArgument
values:
hello
"hello world!"
"hello@gmail.com"
"this has \" <<-- speech marks! "
Rejected TextArgument
values:
hello world
私
"speech marks: ""
Potential uses for text arguments
- A command to edit the contents on a sign
- Any command that may require multiple text arguments
Greedy string argument
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.
Example - Messaging command
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 could be used as follows:
/msg Skepter This is some incredibly long string with "symbols" and $p3c!aL characters~
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 for greedy strings
- 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
Entity & player arguments
Entity selector argument
Minecraft's target selectors (e.g. @a
or @e
) are implemented using the EntitySelectorArgument
class. This allows you to select specific entities based on certain attributes.
The EntitySelectorArgument
constructor 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.
Example - Kill entities command
//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();
});
Example command usage for the above code would be:
- Kill all cows:
/kill @e[type=cow]
- Kill the 10 furthest pigs from the command sender:
/kill @e[type=pig,limit=10,sort=furthest]
Player argument
The PlayerArgument
class is very similar (almost identical) to EntitySelectorArgument
, with the EntitySelector ONE_PLAYER
. It also allows you to select a player based on their UUID.
Developer's Note:
I've not tested the
PlayerArgument
enough to recommend using it over theEntitySelectorArgument(EntitySelector.ONE_PLAYER)
. There may be other advantages to using this than the regular EntitySelectorArgument, but as of writing this documentation, I know not of the advantages nor disadvantages to using this argument type. Internally, thePlayerArgument
uses theGameProfile
class from Mojang's authlib, which may be able to retrieve offline players (untested).
Entity type argument
The EntityTypeArgument
class is used to retrieve a type of entity as defined in the EntityType
enum. In other words, this is an entity type, for example a pig or a zombie.
Example - Spawning entities
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("entity", new EntityTypeArgument());
arguments.put("amount", new IntegerArgument(1, 100)); //Prevent spawning too many entities
CommandAPI.getInstance().register("spawnmob", arguments, (sender, args) -> {
Player player = (Player) sender;
for(int i = 0; i < (int) args[1]; i++) {
player.getWorld().spawnEntity(player.getLocation(), (EntityType) args[0]);
}
});
Literal arguments
Literal arguments are used to represent "forced options" for a command. For instance, take Minecraft's /gamemode
command. The syntax consists of the following:
/gamemode <mode> [player]
It consists of a gamemode, followed by an optional player argument. The list of gamemodes are as follows:
/gamemode survival
/gamemode creative
/gamemode adventure
/gamemode spectator
Unlike regular commands (as those implemented by Bukkit for example), these four options are "hardcoded" - they're not "suggestions". The user can only enter one of these four examples, no other values are allowed.
Literal arguments vs regular arguments
Unlike regular arguments that are shown in this chapter, the literal argument is technically not an argument. Due to this fact, the literal argument is not present in the args[]
for the command declaration.
Example - Literal arguments and regular arguments
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("literal", new LiteralArgument("hello"));
arguments.put("text", new TextArgument());
CommandAPI.getInstance().register("mycommand", arguments, (sender, args) -> {
/* This gives the variable "text" the contents of the
* TextArgument, and not the literal "hello" */
String text = (String) args[0];
});
As you can see from this example, when you retrieve args[0]
, it returns the value of the TextArgument
instead of the LiteralArgument
Example - Gamemode command using literal arguments
This is a demonstration of how you could create a command similar to Minecraft's /gamemode
command by using literal arguments. To do this, we are effectively registering 4 separate commands, each called /gamemode
, but with different literal arguments.
//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));
}
});
}
Literal argument warnings
Literal arguments require a string in the constructor. If the literal is an empty String or is null, the CommandAPI will throw a BadLiteralException
.
Because literal arguments are "hardcoded", each literal is effectively mapped to a single command. This is shown when using the configuration option create-dispatcher-json: true
which shows the JSON result of registered commands. For instance, take the /defaultgamemode
command:
"defaultgamemode": {
"type": "literal",
"children": {
"adventure": {
"type": "literal",
"executable": true
},
"creative": {
"type": "literal",
"executable": true
},
"spectator": {
"type": "literal",
"executable": true
},
"survival": {
"type": "literal",
"executable": true
}
}
},
Each option produces a new "command" in the tree of commands. This means that having exceptionally large lists of literals, or nested literals (e.g. /command <literal1> <literal2>
) can cause very large trees which cannot be sent to the clients (it can cause clients to crash).
Developer's Note:
Take care when using literal arguments. If your list of arguments is exceptionally large, or contains many nested arguments, the server may be unable to send the command information to the client. If many command argument choices are required, consider using a
StringArgument
and using.overrideSuggestions()
to create your own list of required arguments.
Dynamically suggested arguments
Dynamically suggested arguments lets you provide a function to generate arguments dynamically. This is achieved using the DynamicSuggestedStringArgument
class, which has two constuctor formats:
//Constructor requires a function which returns a String[]
new DynamicSuggestedStringArgument(() -> return new String[]);
//Constructor requires a function which returns a String[] and receives a sender input
new DynamicSuggestedStringArgument((sender) -> return new String[]);
The first one simply lets you return a list of strings as suggestions. Whenever a player begins typing a command, a packet is sent to the server and it will return the list of suggestions (similar to tab completion). The second constructor allows you to also use the command sender as an input (of type CommandSender
), which can be used for more intricate suggestions.
Example - Friend command with dynamic suggestions
Say we want a plugin which is a friend system. The friend system allows you to add friends and when you've made a friend, you can message them.
/friend <player> - Adds a player as a friend
/msgf <player> <msg> - Message a friend
//Global mapping of a player to a list of their friends
final Map<String, List<String>> friends = new ArrayList<>();
//Register /friend command
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("friend", new PlayerArgument());
CommandAPI.getInstance().register("friend", arguments, (sender, args) -> {
//Get list of friends from the sender
List<String> senderFriends;
if(friends.containsKey(sender.getName())) {
senderFriends = friends.get(sender.getName());
} else {
senderFriends = new ArrayList<>();
}
//Add friend to the global map
senderFriends.put(((Player) args[0]).getName());
friends.put(sender.getName(), senderFriends);
});
//Register /msgf command
arguments.clear();
/* Use a DynamicSuggestedStringArgument which consists of the names of
* friends for that player. Don't forget that this returns a String */
arguments.put("friend", new DynamicSuggestedStringArgument((sender) -> {
List<String> senderFriends = friends.get(sender.getName());
return senderFriends.toArray(new String[senderFriends.size()]);
}));
arguments.put("message", new GreedyStringArgument());
CommandAPI.getInstance().register("msgf", arguments, (sender, args) -> {
//Parse the target player from the String, failing if it can't find that player
Player target = Bukkit.getPlayer((String) args[0]);
if(target == null) {
CommandAPI.fail("Couldn't find that player!");
} else {
target.sendMessage((String) args[1]);
}
});
A thing to note from the code above: The DynamicSuggestedStringArgument is a String argument, meaning that the resulting type is a String. Despite the fact that we want a player object, the CommandAPI only lets you make dynamic suggestions for Strings. Therefore, it could fail in this situation (if the target player isn't found), and we have to handle that case on our own. If we had used a PlayerArgument
, that argument would automatically fail due to the implementation of the player argument.
Custom arguments
Custom arguments are arguably the most powerful argument that the CommandAPI offers. This argument is used to represent any String, or Minecraft key (Something of the form String:String
, such as minecraft:diamond
)
In order to specify which type of custom argument is being used, the additional flag keyed
can be added by, instead of using the default constructor which requires a lambda, you use the alternate constructor which requires a lambda, followed by true
, which represents a Minecraft key input.
new CustomArgument<T>((input) -> {
/* Code which handles input */
return //Some object of type T;
}, true);
The custom argument requires the type of the target object that the custom argument will return when parsing the arguments for a command. For instance, if you have a CustomArgument<Player>
, then when parsing the arguments for the command, you would cast to a Player object: (Player) args[n]
.
Example - Scoreboard objectives as a custom argument
Here, we aim to create a custom argument that represents the Objective object for a scoreboard.
//Function that returns a CustomArgument<Object>
private CustomArgument<Objective> objectiveArgument() {
Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
/* Create our CustomArgument instance. This takes an input of a
* lambda that takes in a String and returns an Objective object. */
return new CustomArgument<Objective>((input) -> {
//Parse the objective
if(scoreboard.getObjective(input) == null) {
//Throw a custom error to the user if it fails to get the objective
CustomArgument.throwError(new MessageBuilder("Unknown objective: ").appendArgInput());
return null;
} else {
return scoreboard.getObjective(input);
}
//Use .overrideSuggestions to suggest the list of names of objectives
}).overrideSuggestions(scoreboard.getObjectives().stream().map(o -> o.getName()).toArray(String[]::new));
}
From the code above, it uses the CustomArgument.throwError
function which throws an error to the command sender if the command that they input is invalid. The throwError
function can accept one of two parameters:
CustomArgument.throwError(String message)
CustomArgument.throwError(MessageBuilder message)
Message Builders
The MessageBuilder
class is a class to easily create messages to describe errors when a sender sends a command which does not meet the expected syntax for an argument. It acts in a similar way to a StringBuilder
, where you can append content to the end of a String.
The following methods are as follows:
Method | Description |
---|---|
appendArgInput() | Appends the argument that failed that the sender submitted to the end of the builder. E.g. /foo bar will append bar |
appendFullInput() | Appends the full command that a sender submitted to the end of the builder. E.g. /foo bar will append foo bar |
appendHere() | Appends the text <--[HERE] to the end of the builder |
append(Object) | Appends the object to the end of the builder |
Example - Message builder for invalid objective argument
See the code above, which uses the following code snippet:
//Creates a MessageBuilder object that handles an invalid objective.
new MessageBuilder("Unknown objective: ").appendArgInput();
Defined custom arguments
The CommandAPI has a few custom arguments which have been predefined, in the DefinedCustomArguments
class. The methods are as follows:
DefinedCustomArguments.objectiveArgument(); //CustomArgument<Objective>
DefinedCustomArguments.teamArgument(); //CustomArgument<Team>
These are included to help reduce the amount of code required if you were to implement custom arguments for the types stated above.
Technical arguments
As of version 2.1, the CommandAPI has additional support for technical arguments. These consist of Minecraft "Keyed" arguments (something that takes the form of String:String
, such as minecraft:diamond
).
Based on the nature of technical arguments, these are unlikely to survive Minecraft updates and support for these will be implemented as soon as possible when new Minecraft updates are released. (For example, advancement arguments are compatible with Minecraft 1.14, 1.14.1 and 1.14.2, but required a rewrite to enable support for 1.14.3+)
Developer's Note:
As with any future bug, if you notice that a technical argument does not work with a specific version of Minecraft (1.13.2+), I'd be grateful if you could submit a bug report on the CommandAPI's issues page and I'll try to fix it as soon as I can!
Technical arguments are also compatible with data packs which allow you to add additional content to the game. In particular, Advancements, Loot Tables and Recipes are supported to allow extra interaction between Bukkit and data packs.
Sound argument
The SoundArgument
class allows a command sender to retrieve the Bukkit Sound
object to represent in-game sound effects (such as mob sounds or ambient sound effects), as well as in-game music.
Example - Playing sound to yourself
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("sound", new SoundArgument());
CommandAPI.getInstance().register("sound", arguments, (sender, args) -> {
Sound sound = (Sound) args[0];
Player player = (Player) sender;
player.getWorld().playSound(player.getLocation(), sound, 100.0f, 1.0f);
});
Advancement argument
The AdvancementArgument
class is used to represent Bukkit Advancement
objects. This can be used to get the advancement progress for a player and a specific advancement, as well as awarding advancements programmatically.
Example - Awarding an advancement to a player
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("advancement", new AdvancementArgument());
CommandAPI.getInstance().register("giveadvancement", arguments, (sender, args) -> {
Advancement advancement = (Advancement) args[0];
Player player = (Player) sender;
advancement.getCriteria().forEach(criteria -> {
player.getAdvancementProgress(advancement).awardCriteria(criteria);
});
});
Recipe argument
The RecipeArgument
class lets you retrieve Bukkit's Recipe
object.
Example - Giving the result of a recipe
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("recipe", new RecipeArgument());
CommandAPI.getInstance().register("giverecipe", arguments, (sender, args) -> {
Recipe recipe = (Recipe) args[0];
Player player = (Player) sender;
player.getInventory().addItem(recipe.getResult());
});
LootTable argument
The LootTableArgument
class can be used to get a Bukkit LootTable
object.
Example - ¯\_(ツ)_/¯
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("loottable", new LootTableArgument());
CommandAPI.getInstance().register("giveloottable", arguments, (sender, args) -> {
LootTable lootTable = (LootTable) args[0];
Player player = (Player) sender;
LootContext context = /* Some generated LootContext relating to the lootTable*/
lootTable.fillInventory(player.getInventory(), new Random(), context);
});
Developer's Note:
Honestly, I've not managed to get a successful example of using a
LootTable
in practice, due to being unable to generate a suitableLootContext
. If you believe you can supply a suitable example for this page, feel free to send an example on the CommandAPI issues page.
Functions
The CommandAPI has support to use Minecraft's functions within your plugins. This is handled by using a class provided by the CommandAPI called FunctionWrapper
, which allows you to execute functions. The CommandAPI also provides support to let you run your own commands within Minecraft function files.
Using custom commands in functions
In order to use a command from your plugin in a .mcfunction
file, you must register your command in your plugin's onLoad()
method, instead of the onEnable()
method. Failure to do so will not allow the command to be registered for Minecraft functions, causing the function file to fail to load during the server startup phase.
Developer's Note:
In short, if you want to register a command which can be used in Minecraft functions, register it in your plugin's
onLoad()
method.
Example - Registering command for use in a function
public class Main extends JavaPlugin {
@Override
public void onLoad() {
//Commands which will be used in Minecraft functions are registered here
CommandAPI.getInstance().register("killall", new LinkedHashMap<>(), (sender, args) -> {
//Kills all enemies in all worlds
Bukkit.getWorlds()
.forEach(w -> w.getLivingEntities()
.forEach(e -> e.setHealth(0))
);
});
}
@Override
public void onEnable() {
//Register all other commands here
}
}
Setting up functions & tags
Developer's Note:
Most developers can completely skip this section. This is for those that are unfamiliar with functions and tags and is less about how the CommandAPI works.
This section explains how functions are declared and set up for use in a Minecraft server. This is ideal for server owners who've never set up functions, or developers (like the Command API's creator) that has no idea how to set this sort of thing up.
Creating functions
Functions are text files (with the .mcfunction
extension) which contains lists of functions which are executed one after another. Each line of the file is a valid Minecraft command. Say we have text.mcfunction
:
killall
say Killed all living entities on the server
This will run the custom command killall (as declared in Chapter 5 - Functions), and then broadcast a message to all players stating that all entities were killed.
Creating tags
Tags are json files which contain a list of functions. Tags let you run multiple functions at a time. Say we have a tag called mytag.json
:
{
"values": [
"mycustomnamespace:test",
"mycustomnamespace:test2"
]
}
This will run the function test
and the function test2
, which are in the namespace mycustomnamespace
.
Namespaces & where to place everything
The following hierarchy explains where functions and tags go. In this diagram, the two functions test
and test2
are in a directory called functions
. There is also a tag called mytag
which is placed in the tags
directory under functions
. These are all under the namespace called mycustomnamespace
server/
├── world/
│ ├── advancements/
│ ├── data/
│ ├── datapacks/
│ │ └── bukkit/
│ │ ├── pack.mcmeta
│ │ └── data/
│ │ └── mycustomnamespace/
│ │ ├── functions/
│ │ │ ├── test.mcfunction
│ │ │ └── test2.mcfunction
│ │ └── tags/
│ │ └── functions/
│ │ └── mytag.json
│ └── ...
├── world_nether/
├── world_the_end/
├── ...
└── spigot.jar
To execute the test
function, you would run the following command:
/function mycustomnamespace:test
To execute the mytag
tag, you would run the following command:
/function #mycustomnamespace:mytag
The FunctionWrapper
The CommandAPI includes the FunctionWrapper
class which is a wrapper for Minecraft's functions. It allows you to execute the commands that are represented by the respective .mcfunction
file. There most important thing to note with the FunctionWrapper
, and that it does not "store" the functions inside it, instead it just stores what the function does. In other words, you cannot "extract" the list of commands from a FunctionWrapper
object.
FunctionWrapper methods
The FunctionWrapper
class consists of two methods:
Method | Result on execution |
---|---|
run() | Executes the Minecraft function |
runAs(Entity) | Executes the Minecraft function as a specific Entity |
The FunctionWrapper
also implements the Keyed
interface, allowing you to retrieve the NamespacedKey for this function using getKey()
.
Function arguments
The FunctionArgument
class is used to represent a function or a tag in Minecraft. When retrieving an instance of the argument, it will return a FunctionWrapper[]
, where each FunctionWrapper
consists of a Minecraft function.
Therefore, if a user supplies a single function, the FunctionWrapper[]
will be of size 1, and if the user supplies a tag which can consist of multiple functions, the FunctionWrapper[]
will consist of the array of functions as declared by that tag.
Example - Minecraft's /function command
Here, we implement Minecraft's /function
command which is used to execute a function.
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("function", new FunctionArgument());
CommandAPI.getInstance().register("runfunction", arguments, (sender, args) -> {
FunctionWrapper[] functions = (FunctionWrapper[]) args[0];
//Run all functions that the user stated
for(FunctionWrapper function : functions) {
function.run();
}
});
Permissions
Permissions let you control which players are allowed to execute which commands. This is handled using the CommandPermission
class, which has the following uses:
- Requires OP to execute a command:
CommandPermission.OP
- Anyone can execute a command:
CommandPermission.NONE
- Requires a specific permission node to exeucte a command:
CommandPermission.fromString("my.permission")
Registering permissions to commands
To add a permission to a command, you can use any of the following constructors with a valid CommandAPI
instance:
register(String commandName, CommandPermission, LinkedHashMap<String, Argument>, CommandExecutor);
register(String commandName, CommandPermission, String[] aliases, LinkedHashMap<String, Argument>, CommandExecutor)
Example - /god command with permissions
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
//Register the /god command with the permission node "command.god"
CommandAPI.getInstance().register("god", CommandPermission.fromString("command.god"), arguments, (sender, args) -> {
if(sender instanceof Player) {
((Player) sender).setInvulnerable(true);
}
});
//Add a target argument to allow /god <target>
arguments.put("target", new EntitySelectorArgument(EntitySelector.ONE_PLAYER));
//Require "command.god" permission node to execute /god <target>
CommandAPI.getInstance().register("god", CommandPermission.fromString("command.god"), arguments, (sender, args) -> {
((Player) args[0]).setInvulnerable(true);
});
Registering permissions to arguments
For further fine-tuning of permission management, the CommandAPI allows you to add permissions to individual arguments. This prevents the user from executing a command with a specific argument if they do not have a specific permission.
This is done by using the .withPermission(CommandPermission)
method at the end of an argument.
If a player does not have the required permission:
- The argument hover text which suggests what the command is will not be shown
- The player will receive an error if they try to type something in for that argument
- Suggestions, such as a list of materials or players will not be shown
Example - /kill command with argument permissions
LinkedHashMap<String, ArgumentType> arguments = new LinkedHashMap<>();
//Register /kill command normally. Since no permissions are applied, anyone can run this command
CommandAPI.getInstance().register("kill", arguments, (sender, args) -> {
((Player) sender).setHealth(0);
});
//Adds the OP permission to the "target" argument. The sender requires OP to execute /kill <target>
arguments.put("target", new PlayerArgument().withPermission(CommandPermission.OP));
CommandAPI.getInstance().register("kill", arguments, (sender, args) -> {
((Player) args[0]).setHealth(0);
});
Developer's Note:
As you can see, there are multiple ways of applying permissions to commands with arguments. In the /god command shown above, the permission was applied to the whole command. In the /kill command shown above, the permission was applied to the argument.
There's not really much difference between the two methods, but I personally would use argument permissions with
.withPermission()
as it has greater control over arguments.
Aliases
Aliases let you create aliases for commands. They are simply added at command regisration time by using either of the following methods:
CommandAPI.getInstance().register(String, String[], LinkedHashMap, CommandExecutor);
CommandAPI.getInstance().register(String, CommandPermission, String[], LinkedHashMap, CommandExecutor);
The String[]
represents a list of aliases which can be used to execute a command.
Example - Using aliases for /gamemode
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
//populate arguments here
CommandAPI.getInstance().register("gamemode", new String[] {"gm"}, arguments, (sender, args) -> {
//Handle gamemode command here
});
Command conversion
Since the CommandAPI is used to register commands as a vanilla Minecraft command, you may want to use other plugins that are not written with the CommandAPI. For instance, if you want to include a command from a plugin which doesn't use the CommandAPI in a commandblock, (such as the /execute
command), you can use the CommandAPI's command conversion system.
Developer's Note:
The command conversion system is nowhere near perfect. It tries its best to connect Bukkit plugins to vanilla Minecraft commands, but is not guaranteed to run flawlessly. If possible, consider forking/requesting a plugin and writing it with compatibility for the CommandAPI.
Entire plugins
To register all commands that are declared by a plugin, the Converter.convert(Plugin)
method can be used. This attempts to register all commands declared in a plugin's plugin.yml
file, as well as any aliases or permissions stated in the plugin.yml
file.
It is important to note that the plugin must be loaded before your plugin before attempting conversion. (Use loadbefore: [YourPlugin, CommandAPI]
to ensure it loads before your plugin)
Example - Converting commands for a plugin
Say you have some plugin.yml
file for a plugin.
name: myPlugin
main: some.random.package.Main
loadbefore: [CommandAPI]
version: 1.0
commands:
gmc:
aliases: gm1
gms:
i:
permission: item.permission
Then, the following code will register the commands /gmc
, /gm1
, /gms
and /i
:
Converter.convert(Bukkit.getPluginManager().getPlugin("myPlugin"));
Only specific commands
In addition to converting the whole plugin, the CommandAPI allows you to convert single commands at a time using the Converter.convert(Plugin, String)
method, where the String
argument refers to the command name as declared in the plugin's plugin.yml
file.
Troubleshooting
This section basically summarizes the list of things that could go wrong with the CommandAPI and how to mitigate these circumstances.
Server/Plugin reloading
Due to the implementation of the CommandAPI, the CommandAPI does not support plugin reloading for plugins that use the CommandAPI. This includes, but is not limited to:
- The
/reload
command which reloads all plugins on the server - Plugin reloading plugins, such as PlugMan
- Any form of plugin enabling/disabling process for plugins which register commands via the CommandAPI
Developer's Note:
Plugin reloading gets very complicated with respect to the CommandAPI. Since the loading sequence of Minecraft commands is so picky, reloading the CommandAPI or a plugin which registers commands can cause commands to be re-registered. This can lead to very odd effects, such as command collisions (commands just don't work), to duplicate commands being registered under different namespaces (e.g. plugins are registered under Bukkit as well as Minecraft). These effects are not "100% guaranteed" and have only been seen during dodgy tests. In short, do not enable or reload plugins, and absolutely do not reload the server with
/reload
Players cannot connect/timeout when joining
If players cannot connect, this could be due to the size of the command data packet. To see the resultant packet being sent to players when they log in, enable the create-dispatcher-json: true
setting and view the file size of the resultant file. If the file size is abnormally large (Over 2MB is considered very large), consider reducing the number of LiteralArguments
which your plugin uses.
The server just hangs/slows down on my PaperSpigot server
Officially, the CommandAPI does not really support PaperSpigot. As PaperSpigot is a fork of Spigot, it just assumes that it'll work on PaperSpigot if it works on Spigot.
Developer's Note:
There's a few things I personally don't like about PaperSpigot:
- Their developer documentation is non-existant
- Their JavaDocs are very... lacking of documentation
- I personally think it's harder to keep track of new changes between Minecraft upgrades for PaperSpigot compared to Spigot's BuildTools
Command conversion throws a NullPointerException
This is likely caused by the fact that the plugin you want to convert hasn't been loaded yet. Ensure that it loads before your plugin by adding the following to the target plugin's plugin.yml
file:
loadbefore: [YourPlugin, CommandAPI]
Aliases don't work properly (It says unknown command)
This is a persistent bug which has been resolved in version 2.1+ of the CommandAPI.
My issue isn't on here, what do I do?!
If you've found a bug that isn't solvable here, submit a bug report on the CommandAPI's issues page and I'll try my best to resolve the issue!
Afterword
Developer's Note:
Congratulations on making it to the end of the documentation! I hope it was easy to understand everything ... I feel like it's a bit content heavy.
This project is by far my most worked on and probably the project I'm most proud of. You can read about the creation of the CommandAPI in my blog post here.
I'd like to give credit to all of the people that have opened issues on GitHub, that have truly made it what it is now. In particular, I'd like to thank:
- Combustible - My #1 supporter who motivated me to keep this project alive after its release
- DerpNerb - Giving me the idea to change this to a Maven project has made the CommandAPI that much more accessible for everyone
- Draycia - Suggesting that I put CommandSender objects inside DynamicSuggestedStringArguments allow that argument to be that much more powerful
- HielkeMinecraft - Improving the LocationArgument class, adding results and successes for commandsd and being a great bug reporter in general
- i509VCB - Generally a really good bug spotter. Discovered a severe bug that made libraries depending on the CommandAPI depend on Brigadier
- Tinkot - Gave a review of the CommandAPI on its spigot page. This motivated me to fix a 6 month old bug
I never really expected more than 5 or so people to use this API, so it was truly an exciting time creating this for everyone - seeing everyone's responses, issues and problems helped me keep going.
In short, thank you, and everyone else that helped make the commandAPI what it is.