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, 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, you can search for anything in this entire documentation. For example, typing "Example" will show a list of examples which are included throughout the documentation.
Documentation changelog
Here's the list of changes to the documentation between each update. You can view the current documentation version at the top of this page.
Documentation changes 5.3 \(\rightarrow\) 5.6:
- Adds a section 4. Skipping proxy senders which describes how to use the
skip-sender-proxy
configuration option.
Documentation changes 5.2 \(\rightarrow\) 5.3:
- Adds a section 6. Using the annotation system on setting up your development environment to use the annotation system
- Adds a whole massive section on using annotations (16. Annotation-based commands, 17. Annotations, 18. Registering annotation-based commands)
- Adds a section on argument suggestion deferral in section 9.1. Argument suggestions
- Improve warning for
LiteralArgument
- instead of it being "obsolete" compared to theMultiLiteralArgument
, it is now "more complex" thanMultiLiteralArgument
s - Fix issue in the section for custom arguments which should have been updated but wasn't
Documentation changes 5.1 \(\rightarrow\) 5.2:
- Adds brief documentation for the CommandAPI's
reloadDatapacks
method in 16. Internal CommandAPI
Documentation changes 5.0 \(\rightarrow\) 5.1:
- Adds a section 10.2. The SimpleFunctionWrapper class which outlines the new
SimpleFunctionWrapper
class - Updates the documentation for 10.3. The FunctionWrapper class
- Update the name of the package from
dev.jorel.commandapi.CommandAPIHandler.Brigadier
todev.jorel.commandapi.Brigadier
in section 17. Brigadier + CommandAPI - Update the documentation for 11. Permissions stating that you can use
withPermission(String)
instead ofwithPermission(CommandPermission)
Documentation changes 4.3 \(\rightarrow\) 5.0:
Developer's Note:
Lots and lots and lots of changes occurred in version 5.0! I highly recommend reading the Upgrading guide section which covers the changes in more detail and how to update your plugin for this version.
Basically every page has been rewritten in this update and checked for errors. In general, this is the list of new additions:
- New section 3. Command conversion dedicated to command conversion via the
config.yml
- New section 8.4. Listed arguments
- New section 9.8.1. Angle arguments
- New section 14. Subcommands
- New section 16. Internal CommandAPI now lists all arguments and their respective Minecraft argument IDs
- Mentions listed arguments in section 9.11.1. Literal arguments
- Section 15. Command conversion has been rewritten
- Executes native is now present in the command registration page
- Section 8.3. Argument suggestions with tooltips now mentions the
IStringTooltip
class
Documentation changes 4.2 \(\rightarrow\) 4.3:
- Improve the documentation for 2. Configuration for server owners by using simple YAML lists (using the
-
symbol) and update the command conversion syntax for all commands using the~
syntax - Adds the command sender priority to 6.1. Normal command executors
- Adds new method and example for converting specific commands internally in 13. Command conversion
- Adds two sneaky little buttons in the main title toolbar at the top of the page
Documentation changes 4.1 \(\rightarrow\) 4.2:
- Adds a warning about using redirected commands for suggestions that depend on previous arguments in 7.1. Argument suggestions
- Adds a new section 6.3. Native commandsenders
- Update documentation for 6.1. Normal command executors to include the
.executesNative()
method for native command senders
Documentation changes 4.0 \(\rightarrow\) 4.1:
- Adds a new section 7.3. Argument suggestions with tooltips
- Adds documentation for the
MultiLiteralArgument
in section 8.11.2. Multi literal arguments - Adds a new section 4. Shading the CommandAPI into your plugins
- Update documentation for 14. Brigadier + CommandAPI with new (overloaded) function
argBuildOf
- Update Afterword
Documentation changes 3.4 \(\rightarrow\) 4.0:
- Update the maven and gradle documentation to state that it is
provided
andcompileOnly
- The project has been renamed from the "1.13 Command API" to simply the "CommandAPI". This has changed a few things, such as various links. See the section Upgrading guide to view the relevant changes with regards to maven.
- Updated 3. Setting up your development environment to include new Maven repository links
- Fixed stronkage with Java versions - there's now no random warning boxes about incompatibility with Java 12!
- Arguments now include pictures that showcase how they work!
- Reorganised the sections - arguments is now split up into two sections: 6. Arguments (in general) and 7. Argument types
- Adds documentation for 6.2. Safe argument suggestions
- Adds documentation for 7.8.3. BlockState arguments
- Adds documentation for new arguments:
UUIDArgument
: 7.8.14. UUID argumentsBlockPredicateArgument
: 7.9.1. Block predicate argumentsItemStackPredicateArgument
: 7.9.2. ItemStack predicate arguments
- Adds page Incompatible version information outlining what parts of the CommandAPI are incompatible with what versions of Minecraft
- Adds
getCommands()
documentation to the 8.2. The FunctionWrapper class page - Adds page 13. Brigadier + CommandAPI which shows how the CommandAPI can be used with Brigadier side-by-side
- Adds page 10. Requirements for the requirements feature to help restrict commands
- Adds page 14. Predicate tips with information on how to reduce the amount of repeated code when using requirements
- Update Afterword
Documentation changes 3.3 \(\rightarrow\) 3.4:
- Moves configuration for server owners to a new section 2. Configuration for server owners. This has the side-effect of also re-numbering all of the sections on the left. Sorry!
- Adds server owner documentation for the CommandAPI's config command conversion system in section 2. Configuration for server owners
- Update the conversion page 10. Command conversion so it should be much easier to follow and understand
- Changed the list of Java versions that the CommandAPI is compatible with in the Troubleshooting section
Documentation changes 3.1 \(\rightarrow\) 3.3:
- Adds information on how functions are loaded in 1.16+ in section 6. Functions & Tags
- Added function error messages to section Troubleshooting
Documentation changes 3.0 \(\rightarrow\) 3.1:
- Adds new section 5.1 Argument suggestions to cover how to override suggestions - Having it all in section 5. Arguments was a bit too content-heavy
- Adds documentation for the new
.overrideSuggestions()
method in section 5.1 Argument suggestions - Simplified the description of the documentation updates
- Changed the artifact ID for the dependency of the CommandAPI. Instead of being
commandapi
, it is nowcommandapi-core
. You can view the changes in section 2 Setting up your development environment - Changed the repository information for gradle in section 2 Setting up your development environment. You now have to include the NBTAPI repository because gradle can't automatically detect this for some reason. Kinda stupid tbh.
- Adds a section on using multiple or optional arguments in section 5 Arguments
Documentation changes 2.1 \(\rightarrow\) 3.0:
Developer's Note:
Lots of changes occurred in version 3.0. I highly recommend reading the Upgrading guide section which covers the changes in more detail and how to update your plugin for this version.
- Sections on the left have been tidied up and should be more "approachable"
- Installation section (1. Installation for server owners) now includes information about additional dependencies
- Dependency section (2. Setting up your development environment) updated to use the new dependency Group ID
- Command registration section (3. Command registration) updated to reflect new API changes
- Command execution section (4. Command Executors) updated to reflect new API changes
- Arguments section (5. Arguments) completely rewritten to reflect new API changes. Adds more detailed examples for each argument
- Function arguments section (6.3 Function Arguments) updated to reflect new API changes
- Permissions section (7. Permissions) updated to reflect new API changes
- Aliases section (8. Aliases) updated to reflect new API changes
- Command conversion section (9. Command conversion) rewrite example to be more detailed
Installation
Installing the CommandAPI is easy! Just download the latest CommandAPI.jar
file using the button below and place it in your server's plugins
folder!
Download latest CommandAPI.jar
Additional dependencies
- If you use NBT data, you'll also need the NBT API. (You can find out from your developers if you need this or not)
- If you are using raw JSON chat data, you'll need to be running Spigot or another spigot-related server such as Paper Spigot or Taco Spigot. (Again, you can find out from your developers if you need this or not)
Configuration for server owners
The CommandAPI has a few configuration options to change how it functions. These options can be configured in the plugins/CommandAPI/config.yml
file, which is generated automatically when the CommandAPI runs for the first time.
The default config.yml
is shown below:
verbose-outputs: false
create-dispatcher-json: false
plugins-to-convert: []
skip-sender-proxy: []
Configuration settings:
verbose-outputs
- Iftrue
, outputs command registration and unregistration logs in the consolecreate-dispatcher-json
- Iftrue
, the CommandAPI creates acommand_registration.json
file showing the mapping of registered commands. This is designed to be used by developers - setting this tofalse
will improve command registration performanceplugins-to-convert
- Controls the list of plugins to process for command conversion. See Command conversion for more information!skip-sender-proxy
- Determines whether the proxy sender should be skipped when converting a command. See Skipping proxy senders for more information!
Command Conversion (for server owners)
The CommandAPI has the ability to convert plugin commands into "Vanilla compatible" commands automatically on startup. This allows you to use /execute
and Minecraft functions/tags for plugins that do not use the CommandAPI. For example, if you want to use the /hat
command from the plugin Essentials
in an /execute
command or from a command block, you can use the CommandAPI's command conversion setting to do so.
The CommandAPI has 3 different conversion methods, each one more specific and powerful than the others. These are the following:
-
Converts all commands from a plugin into Vanilla compatible commands
-
Converts a single command from a plugin into a Vanilla compatible command
-
Single command conversion with custom arguments
Converts a single command from a plugin into a Vanilla compatible command, whilst also declaring what the arguments to the command are
YAML configuration rules
To configure command conversion, the CommandAPI reads this information from the config.yml
file. This file has a bit of a weird structure, so to put it simply, these are the following rules:
config.yml
cannot have tab characters - Theconfig.yml
file must only consist of spaces!- Indentation is important and should be two spaces
Converting all plugin commands
To convert all of the commands that a plugin has, add the name of the plugin, followed by a ~
character to the list of plugins to convert in the config.yml
file.
Example - Converting all commands from EssentialsX
For example, if you wanted to convert all commands that EssentialsX has, you can use the following config.yml
:
verbose-outputs: true
create-dispatcher-json: false
plugins-to-convert:
- Essentials: ~
Single command conversion
Often, you don't want to convert every single command that a plugin declares, and instead you only want to convert a few commands that a plugin has.
To convert a single command, you need to first populate the config.yml
with the name of the plugin and commands to be converted. To illustrate this, we'll use an example:
Example - Converting commands
Say we're using EssentialsX on our server and we want to be able to use /afk
and /hat
in command blocks. This would allow us to use (for example) the following commands in command blocks:
/execute as @p run afk
/execute as @p run hat
To do this, we need to add Essentials
to our config.yml
file, and include the commands afk
and hat
as the commands to be converted from the Essentials plugin. This would then make our config.yml
file look like this:
verbose-outputs: true
create-dispatcher-json: false
plugins-to-convert:
- Essentials:
- hat
- afk
Developer's Note:
Note that the commands
hat
andafk
are used, as opposed to an alias such ashead
. The CommandAPI is only able to convert plugin commands that are declared in a plugin'splugin.yml
file. For example, if we take a look at the EssentialsXplugin.yml
file, we can see the commandsafk
andhat
have been declared and thus, are the commands which must be used in the CommandAPI'sconfig.yml
file:name: Essentials main: com.earth2me.essentials.Essentials version: 2.18.0.0 website: http://tiny.cc/EssentialsCommands description: Provides an essential, core set of commands for Bukkit. softdepend: [Vault, LuckPerms] authors: [Zenexer, ementalo, Aelux, Brettflan, KimKandor, snowleo, ceulemans, Xeology, KHobbits, md_5, Iaccidentally, drtshock, vemacs, SupaHam, md678685] api-version: "1.13" commands: afk: description: Marks you as away-from-keyboard. usage: /<command> [player/message...] aliases: [eafk,away,eaway] # (other config options omitted) hat: description: Get some cool new headgear. usage: /<command> [remove] aliases: [ehat,head,ehead] # (other config options omitted)
Single command conversion (with arguments)
For even finer control when converting a single command, you can provide the list of arguments that are required to run the command! This lets you use the command UI in converted commands as you see fit. Before we explain how to do this in detail, let's first take a look at an example of this in action.
Example - Converting EssentialsX's /speed command
EssentialsX includes a command /speed
which lets you change the current speed that a player can move at. The command format is the following:
/speed <speed>
/speed <speed> <target>
/speed <walk/fly> <speed>
/speed <walk/fly> <speed> <target>
Which means you can run any of the following commands:
/speed 5
/speed 2 Notch
/speed fly 6
/speed walk 3 Notch
By looking at this, we can see that:
<speed>
is a number. By using the command, we can determine that the range of values is between 0 and 10 (inclusive).<target>
is a player<walk/fly>
don't change - these are "fixed" values.
We can represent this using the following config.yml
file:
verbose-outputs: false
create-dispatcher-json: false
plugins-to-convert:
- Essentials:
- speed <speed>[0..10]
- speed <target>[minecraft:game_profile]
- speed (walk|fly) <speed>[0..10]
- speed (walk|fly) <speed>[0..10] <target>[minecraft:game_profile]
Using this, we can display options, such as "fly" and "walk", as well as optional targets ("Skepter"):
Additionally, we can apply limits to the numbers that can be provided. For example, here we limit the number to a value between 0 to 10. If a value is outside of that range, and error is shown to the user:
Command argument syntax
The argument syntax is a little tricky to get the hang of at the beginning, but it should be fairly straight forward. There are two main types of arguments that you can have:
Literal arguments
Literal arguments are arguments with "fixed" values, such as walk
or fly
from our example above. To declare a literal value, place brackets around the value. For example:
(walk)
To have multiple different literals, place a pipe symbol |
between each entry within the brackets. For example:
(walk|fly)
Named arguments
Named arguments must have a name, declared in angled brackets <name>
, followed by the type of the argument in square brackets [type]
. In the example above, we had a named argument <target>
, with the argument type as a player: [minecraft:game_profile]
.
The name in the argument can basically be whatever you want, but it is highly recommended to keep it as a lowercase value consisting only of letters.
The following argument types are highly recommended and are very likely to be compatible with every plugin command that you may want to convert:
Type | Description |
---|---|
api:greedy_string | An unlimited amount of text. This can only be used as the last entry of a list of arguments |
brigadier:bool | A Boolean value true or false |
brigadier:double | A decimal number |
brigadier:float | A decimal number |
brigadier:integer | A whole number |
brigadier:long | A whole number |
brigadier:string | A single word |
minecraft:block_pos | A location of x, y and z coordinates (whole numbers) |
minecraft:entity | An entity (e.g. Notch ) |
minecraft:game_profile | A player (e.g. Notch ) |
In the example above, we used the a "range type" in the form [0..10]
. This is a special argument type that will conform to brigader:long
or brigader:double
and apply a limit to the values that can be entered.
Example - Declaring"range type" arguments
To declare the range \(10 \le x \le 50\) (a value must be between 10 and 50 (inclusive)):
<name>[10..50]
To declare the range \(10 \le x\) (a value must be bigger than or equal to 10):
<name>[10..]
To declare the range \(x \le 50\) (a value must be less than or equal to 50):
<name>[..50]
To declare the range \(0 \le x \le 1\), where \(x\) is a decimal value:
<name>[0.0..1.0]
To declare a value \(x\) that can take any range of values and is a decimal number:
<name>[brigadier:double]
List of all supported argument types
The list of types are based on the list of argument types from the Minecraft Wiki, with a few changes. The complete list that the CommandAPI supports is as follows:
Type | Description |
---|---|
api:advancement | An advancement |
api:biome | A biome |
api:greedy_string | An unlimited amount of text. This can only be used as the last entry of a list of arguments |
api:loot_table | A loot table |
api:recipe | A recipe |
api:sound | A sound effect |
api:text | Text encased in quotes: "text with spaces" |
brigadier:bool | A Boolean value true or false |
brigadier:double | A decimal number |
brigadier:float | A decimal number |
brigadier:integer | A whole number |
brigadier:long | A whole number |
brigadier:string | A single word |
minecraft:angle | A yaw angle in degrees (from -180.0 to 179.9) |
minecraft:block_pos | A location of x, y and z coordinates (whole numbers) |
minecraft:block_predicate | A block predicate |
minecraft:block_state | A block type (e.g. stone ) |
minecraft:color | A chat color (e.g. red , green ) |
minecraft:column_pos | A location of x and z coordinates (whole numbers) |
minecraft:component | Raw JSON text |
minecraft:dimension | A dimension, (e.g. minecraft:overworld ) |
minecraft:entity | An entity (e.g. Notch ) |
minecraft:entity_summon | An entity type (e.g. cow , wither ) |
minecraft:float_range | A range of decimal numbers |
minecraft:function | A datapack function |
minecraft:game_profile | A player (e.g. Notch ) |
minecraft:int_range | A range of whole numbers |
minecraft:item_enchantment | An enchantment (e.g. unbreaking ) |
minecraft:item_predicate | An item predicate |
minecraft:item_stack | An item (e.g. stick ) |
minecraft:message | A plain text message which can have target selectors (e.g. Hello @p ). This can only be used as the last entry of a list of arguments |
minecraft:mob_effect | A potion effect (e.g. speed , jump_boost ) |
minecraft:nbt_compound_tag | Raw compound NBT in SNBT format |
minecraft:objective | An objective name (e.g. temperature ) |
minecraft:objective_criteria | An objective criteria (e.g. deaths ) |
minecraft:operation | An operation symbol (e.g. += , *= ) |
minecraft:particle | A particle (e.g. crit , flame ) |
minecraft:rotation | A rotation of yaw and pitch values (e.g. ~ ~ ) |
minecraft:score_holder | A score holder (e.g. Notch ) |
minecraft:scoreboard_slot | A scoreboard slot (e.g. sidebar ) |
minecraft:swizzle | A collection of axes (e.g. xyz , xz ) |
minecraft:team | A team name (e.g. hunters ) |
minecraft:time | A duration of time (e.g. 2d ) |
minecraft:uuid | A UUID (e.g. dd12be42-52a9-4a91-a8a1-11c01849e498 ) |
minecraft:vec2 | A location of x and z coordinates (decimal numbers) |
minecraft:vec3 | A location of x, y and z coordinates (decimal numbers) |
Skipping proxy senders
When the CommandAPI converts a command, it does some tweaks to the thing running the command (such as the player or console) to improve compatibility with plugins (mostly permissions). This doesn't always work, and can sometimes produce an error which looks like this:
[20:44:01 ERROR]: java.lang.IllegalArgumentException: object is not an instance of declaring class
[20:44:01 ERROR]: at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[20:44:01 ERROR]: at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
[20:44:01 ERROR]: at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
[20:44:01 ERROR]: at java.lang.reflect.Method.invoke(Unknown Source)
[20:44:01 ERROR]: at dev.jorel.commandapi.Converter.lambda$mergeProxySender$3(Converter.java:151)
[20:44:01 ERROR]: at com.sun.proxy.$Proxy33.getInventory(Unknown Source)
To fix this, add the plugin which the command is registered from to the list of plugins under skip-sender-proxy
.
Example - Improving compatibility with the SkinsRestorer plugin
SkinsRestorer (not associated or sponsored by the CommandAPI in any way) is a plugin that lets you change the skin for a player. This suffers from the above issue and is not compatible with the CommandAPI's conversion compatibility tweaks. To do this, we'll add SkinsRestorer
to the list of plugins which should be skipped:
verbose-outputs: true
create-dispatcher-json: false
plugins-to-convert:
- SkinsRestorer: ~
skip-sender-proxy:
- SkinsRestorer
Setting up your development environment
To use the CommandAPI in your plugins, there are a few 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 CommandAPI 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>commandapi</id> <url>https://raw.githubusercontent.com/JorelAli/CommandAPI/mvn-repo/</url> </repository> </repositories>
-
Add the dependency to your
pom.xml
:<dependencies> <dependency> <groupId>dev.jorel</groupId> <artifactId>commandapi-core</artifactId> <version>5.6</version> <scope>provided</scope> </dependency> </dependencies>
-
Add the CommandAPI as a dependent in the
plugin.yml
(depend: [CommandAPI]
)
Using Gradle
-
Add the repositories to your
build.gradle
file (the second repository is required because the CommandAPI depends on the NBT-API):repositories { maven { url = "https://raw.githubusercontent.com/JorelAli/CommandAPI/mvn-repo/" } maven { url = "https://repo.codemc.org/repository/maven-public/" } }
-
Add the dependency to your list of dependencies in your
build.gradle
file:dependencies { compileOnly "dev.jorel:commandapi-core:5.6" }
- Add the CommandAPI as a dependent in the
plugin.yml
(depend: [CommandAPI]
)
Shading the CommandAPI in your plugins
After 2 years, this most requested feature is finally here...
The CommandAPI can now be shaded into your own plugins! "Shading" is the process of including the CommandAPI inside your plugin, rather than requiring the CommandAPI as an external plugin. In other words, if you shade the CommandAPI into your plugin, you don't need to include the CommandAPI.jar
in your server's plugins folder.
Shading vs CommandAPI plugin
The CommandAPI plugin has a few slight differences with the shaded CommandAPI jar file. The CommandAPI plugin has the following extra features that are not present in the shaded version:
- Command conversion via a
config.yml
file - Creation of the
command_registration.json
file to show the Brigadier command graph
Shading requirements
For the CommandAPI to function as normal, you must call the CommandAPI's initializers in the onLoad()
and onEnable()
methods of your plugin:
CommandAPI.onLoad(boolean verbose);
CommandAPI.onEnable(Plugin plugin);
The onLoad(boolean)
method initializes the CommandAPI's loading sequence. This must be called before you start to access the CommandAPI and must be placed in your plugin's onLoad()
method. The argument verbose
is used to enable verbose logging output.
The onEnable(Plugin)
method initializes the CommandAPI's enabling sequence. As with the onLoad(boolean)
method, this one must be placed in your plugin's onEnable()
method. This isn't as strict as the onLoad(boolean)
method, and can be placed anywhere in your onEnable()
method. The argument plugin
is your current plugin instance.
Example - Setting up the CommandAPI in your plugin
public class MyPlugin extends JavaPlugin {
@Override
public void onLoad() {
CommandAPI.onLoad(true); //Load with verbose output
new CommandAPICommand("ping")
.executes((sender, args) -> {
sender.sendMessage("pong!");
})
.register();
}
@Override
public void onEnable() {
CommandAPI.onEnable(this);
//Register commands, listeners etc.
}
}
Shading with Maven
To shade the CommandAPI into a maven project, you'll need to use the commandapi-shade
dependency, which is optimized for shading and doesn't include plugin-specific files (such as plugin.yml
):
<dependencies>
<dependency>
<groupId>dev.jorel</groupId>
<artifactId>commandapi-shade</artifactId>
<version>5.6</version>
</dependency>
</dependencies>
Once you've added this this, you can shade the CommandAPI easily by adding the maven-shade-plugin
to your build sequence:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<id>shade</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<relocations>
<relocation>
<pattern>dev.jorel.commandapi-shade</pattern>
</relocation>
</relocations>
</configuration>
</plugin>
</plugins>
</build>
Of course, if you shade the CommandAPI into your plugin, you don't need to add depend: [CommandAPI]
to your plugin.yml
file.
Shading with Gradle
To shade the CommandAPI into a Gradle project, we'll use the Gradle Shadow Plugin. Add this to your list of plugins:
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '6.0.0'
}
Next, we declare our dependencies:
dependencies {
compile "dev.jorel:commandapi-shade:5.6"
}
Then we add it to the shadowJar
task configuration:
shadowJar {
dependencies {
include dependency("dev.jorel:commandapi-shade:5.6")
}
}
Finally, we can build the shaded jar using the following command:
gradlew build shadowJar
Again, as we're shading the CommandAPI into your plugin, we don't need to add depend: [CommandAPI]
to your plugin.yml
file.
Using the annotation system
The annotation system is a separate part of the CommandAPI, and as a result it needs to be included as an additional dependency to your project.
The annotation system effectively needs to be added twice: Once for compilation and again to invoke the annotation processor itself.
Using Maven
-
If you haven't already done so, add the maven repository to your
pom.xml
file:<repositories> <repository> <id>commandapi</id> <url>https://raw.githubusercontent.com/JorelAli/CommandAPI/mvn-repo/</url> </repository> </repositories>
-
Add the annotation dependency to your
pom.xml
:<dependencies> <dependency> <groupId>dev.jorel</groupId> <artifactId>commandapi-annotations</artifactId> <version>5.3</version> <scope>provided</scope> </dependency> </dependencies>
-
Add the annotation processor as an annotation process to the compile task in the
pom.xml
:<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <annotationProcessorPaths> <path> <groupId>dev.jorel</groupId> <artifactId>commandapi-annotations</artifactId> <version>5.3</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
Using Gradle
-
If you haven't already done so, add the maven repository to your
build.gradle
file:repositories { maven { url = "https://raw.githubusercontent.com/JorelAli/CommandAPI/mvn-repo/" } maven { url = "https://repo.codemc.org/repository/maven-public/" } }
-
Add the dependency and annotation processor to your list of dependencies in your
build.gradle
file:dependencies { compileOnly "dev.jorel:commandapi-annotations:5.3" annotationProcessor "dev.jorel:commandapi-annotations:5.3" }
Command registration
To register commands with the CommandAPI, we use the CommandAPICommand
class. It follows a simple builder pattern to improve readability.
I think the easiest way to explain it is with an example:
// Create our arguments
List<Argument> arguments = new ArrayList<>();
arguments.add(new GreedyStringArgument("message"));
//Create our command
new CommandAPICommand("broadcastmsg")
.withArguments(arguments) // The arguments
.withAliases("broadcast", "broadcastmessage") // Command aliases
.withPermission(CommandPermission.OP) // Required permissions
.executes((sender, args) -> {
String message = (String) args[0];
Bukkit.getServer().broadcastMessage(message);
}).register();
- First, we create our arguments for the command. This is described in more detail in the section on arguments.
- We then create a new
CommandAPICommand
, with the name of the command that the sender must enter to run it. - We then add the arguments to the command with
withArguments
. - In this example, we add an alias, "broadcast", to the command. This allows the sender to use either
/broadcastmsg <message>
or/broadcast <message>
. - By using
withPermission
, we require the sender to be an OP in order to run the command. - We control what the command does using
executes
(this is described in more detail in the section on command executors). - Finally, we register the command to the CommandAPI using
register
.
That's it! This simple snippet of code fully registers the command to the server. No hassling with a plugin instance, no messing with plugin.yml
files.
Throughout this documentation, we will use the various different methods for command registration to give you an idea of when and where certain methods are more suitable than others.
CommandAPICommand
methods
The CommandAPICommand
has various methods, which are outlined below:
Setting the command name
new CommandAPICommand(String commandName)
This constructor creates a new instance of the CommandAPICommand
object. This constructor requires the name of the command.
Setting command properties
CommandAPICommand withArguments(List<Argument> arguments)
The withArguments
method is used to add arguments to your command. The arguments
parameter is appended to the the list of arguments for the command.
CommandAPICommand withArguments(Argument... arguments)
Similar to the other withArguments
method, this method appends the arguments
to the list of arguments for the command. This is purely to make adding one or two arguments nice and easy instead of creating lots of List
objects everywhere.
CommandAPICommand withPermission(CommandPermission)
CommandAPICommand withPermission(String)
The withPermission
method is used to assign a permission that is required to execute the command. (See the section on permissions for more info).
CommandAPICommand withAliases(String... args)
The withAliases
method is used to declare a list of aliases that can be used to run this command via. (See the section on aliases for more info).
CommandAPICommand withSubcommand(CommandAPICommand subcommand)
The withSubcommand
method is used to declare a subcommand that leads on from the current command. (See the section on subcommands for more info).
Setting the command's executor
CommandAPICommand executes((sender, args) -> {})
Executes a command using the CommandSender
object.
CommandAPICommand executesPlayer((player, args) -> {})
Executes a command only if the command sender is a Player
.
CommandAPICommand executesEntity((entity, args) -> {})
Executes a command only if the command sender is an Entity
.
CommandAPICommand executesCommandBlock((cmdblock, args) -> {})
Executes a command only if the command sender is a BlockCommandSender
.
CommandAPICommand executesConsole((console, args) -> {})
Executes a command only if the command sender is a ConsoleCommandSender
.
CommandAPICommand executesProxy((proxy, args) -> {})
Executes a command only if the command sender is a ProxiedCommandSender
.
CommandAPICommand executesNative((proxy, args) -> {})
Executes a command regardless of what the command sender is, using the NativeProxyCommandSender
. Read more about native proxied command senders here.
Developer's Note:
Sometimes, the Java compiler throws an error saying that a method is ambiguous for the type CommandAPICommand. This is due to a limitation in Java's type inference system and is not a fault of the CommandAPI. If we take the following code, used to spawn a pig:
new CommandAPICommand("spawnpigs") .executesPlayer((player, args) -> { for(int i = 0; i < 10; i++) { player.getWorld().spawnEntity(player.getLocation(), (EntityType) args[0]); } }) .register();
The Java type inference system cannot determine what the type of the lambda
(player, args) -> ()
is, therefore it produces the following compilation error:The method executesPlayer(PlayerCommandExecutor) is ambiguous for the type CommandAPICommand
This can easily be resolved by declaring the specific type of the command sender and the arguments. For example:
new CommandAPICommand("spawnpigs") .executesPlayer((Player player, Object[] args) -> { for(int i = 0; i < 10; i++) { player.getWorld().spawnEntity(player.getLocation(), (EntityType) args[0]); } }) .register();
Registering the command
void register()
Registers the command.
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 whether you use onLoad()
or onEnable()
to load your commands depends on whether 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 the 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!
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 /changegamemode
.
Method | Result |
---|---|
CommandAPI.unregister(String cmd) | Unregisters a command from the game |
CommandAPI.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.unregister("gamemode", true);
List<Argument> arguments = new ArrayList<>();
/* Arguments for the gamemode command. In this sample, I'm just
* using a simple literal argument which allows for /gamemode survival */
arguments.add(new LiteralArgument("survival"));
new CommandAPICommand("gamemode")
.withArguments(arguments)
.executes((sender, args) -> {
//Implementation of our /gamemode command
}).register();
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 on Normal command executors - this behaves very similar to the
onCommand
method in Bukkit.
The CommandAPI provides various command executors which are lambdas which execute the code you want when a command is called. With a lot of simplification, there are two main types of command executors:
- Ones that just runs the command (let's call it a normal command executor)
- Ones that returns an integer as a result (let's call it a resulting command executor)
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, and specifically want to state whether a command returns a value, just ensure you return an integer at the end of your declared command executor. Java will infer the type (whether it's a normal command executor or a resulting command executor) automatically, so feel free to return an integer or not.
In addition to these two types of command executors, there are ways to restrict the execution of commands to certain CommandSender
subclasses. In other words, you can make commands executable by players in game only for instance. These restrictions are covered in more detail in Normal command executors.
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.
new CommandAPICommand("...")
.executes((sender, args) -> {
//Code here
})
.register();
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 (See the section on handling command failures.
In short, this is what values are returned when a command is executed from a normal command executor:
Command Works | Command Doesn't Work | |
---|---|---|
Success Value | 1 | 0 |
Result Value | 1 | 0 |
Example - Creating a message broadcasting system
To illustrate this, let's take a look at a simple message broadcasting command. We declare our arguments (in this case, "message"), we provide some aliases and set a permission required to run the command. Then we declare our main command body by using the .executes()
method, before finally registering the command:
//Create our command
new CommandAPICommand("broadcastmsg")
.withArguments(new GreedyStringArgument("message")) // The arguments
.withAliases("broadcast", "broadcastmessage") // Command aliases
.withPermission(CommandPermission.OP) // Required permissions
.executes((sender, args) -> {
String message = (String) args[0];
Bukkit.getServer().broadcastMessage(message);
})
.register();
Note how when we finish up our implementation of .executes()
, we don't return anything. This is unlike commands in the standard Bukkit API where the onCommand
method returns a Boolean value:
boolean onCommand(CommandSender, Command, String, String[])
The returning of this Boolean value is handled automatically by the CommandAPI on a much lower level.
Restricting who can run your command
The CommandAPICommand
has multiple different executes...()
methods that can restrict the command sender to any of the following objects:
CommandSender
- No restriction, players, the console etc. can use this command. This is what Bukkit normally uses.Player
- Only in-game players can run this commandEntity
- Only entities (therefore, players as well) can run this commandBlockCommandSender
- Only command blocks can run this commandConsoleCommandSender
- Only the console can run this commandProxiedCommandSender
- Only proxied command senders (e.g. other entities via the/execute as ...
command)NativeProxyCommandSender
- This type has special rules governing it. See Native commandsenders for more information
This is done using the respective method:
Restricted sender | Method to use |
---|---|
CommandSender | .executes() |
Player | .executesPlayer() |
Entity | .executesEntity() |
BlockCommandSender | .executesCommandBlock() |
ConsoleCommandSender | .executesConsole() |
ProxiedCommandSender | .executesProxy() |
NativeProxyCommandSender | .executesNative() |
Example - A /suicide
command
Say we wanted to create a command /suicide
, which kills the player that executes it. Since this command isn't really "designed" for command senders that are not players, we can restrict it so only players can execute this command (meaning that the console and command blocks cannot run this command). Since it's a player, we can use the .executesPlayer()
method:
new CommandAPICommand("suicide")
.executesPlayer((player, args) -> {
player.setHealth(0);
})
.register();
Multiple command executor implementations
The CommandAPI allows you to chain different implementations of the command depending on the type of CommandSender
. This allows you to easily specify what types of CommandSender
s are required to run a command.
Extending on the suicide example above, we could write another implementation for a different CommandSender
. Here, we write an implementation to make entities (non-player) go out with a bang when they run the command (using /execute as <entity> run <command>
command).
Example - A /suicide
command with different implementations
new CommandAPICommand("suicide")
.executesPlayer((player, args) -> {
player.setHealth(0);
})
.executesEntity((entity, args) -> {
entity.getWorld().createExplosion(entity.getLocation(), 4);
entity.remove();
})
.register();
This saves having to use instanceof
multiple times to check the type of the CommandSender
.
The different command sender priority is the following (from highest priority to lowest priority):
\begin{align} &\quad\texttt{.executesNative()} && \texttt{(Always chosen if used)}\\ &\quad\texttt{.executesPlayer()} \\ &\quad\texttt{.executesEntity()} \\ &\quad\texttt{.executesConsole()} \\ &\quad\texttt{.executesCommandBlock()} \\ &\quad\texttt{.executesProxy()} \\ &\quad\texttt{.executes()} \end{align}
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:
new CommandAPICommand("killme")
.executesPlayer((player, args) -> {
player.setHealth(0);
})
.register();
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 use the .executesProxy()
method to ensure that the command sender is a ProxiedCommandSender
. Then, we can kill the callee
(the entity which is being 'forced' to run the command /killme)
new CommandAPICommand("killme")
.executesPlayer((player, args) -> {
player.setHealth(0);
})
.executesProxy((proxy, args) -> {
//Check if the callee is an Entity
if(proxy.getCallee() instanceof LivingEntity) {
//If so, kill the entity
LivingEntity target = (LivingEntity) proxy.getCallee();
target.setHealth(0);
}
})
.register();
This allows the command above to run successfully, killing all chickens it can find.
Native commandsenders
In a similar way that the ProxiedCommandSender
is used to store information about two command senders: a caller (the one that wrote the command) and a callee (the one that ends up executing the command), the CommandAPI also has a special NativeProxyCommandSender
class which is a more powerful representation of the ProxiedCommandSender
class. In addition to inheriting all of the methods from ProxiedCommandSender
, this class also has the following two methods:
public World getWorld();
public Location getLocation();
These methods contain additional information about the command executor's state, and are primarily designed to be used with Minecraft's /execute
command.
Minecraft's /execute
arguments
The following table represents how the different /execute
arguments affect the NativeProxyCommandSender
class:
/execute argument | How it changes NativeProxyCommandSender |
---|---|
/execute align | Changes getLocation() only |
/execute anchored | Changes nothing |
/execute as | Changes getCallee() only |
/execute at | Changes getLocation() and getWorld() only |
/execute facing | Changes getLocation() only |
/execute in | Changes getWorld() only |
/execute positioned | Changes getLocation() only |
/execute rotated | Changes getLocation() only |
Using the native commandsender
As described in the section about normal command executors, there are multiple methods to register a command executor. For the NativeProxyCommandSender
, the .executesNative()
method should be used.
Note:
The
.executesNative()
method has the highest priority over all over.executesXXX()
methods - if you use the.executesNative()
method, no other execution method will be run.
Example - A really simple "break block" command
Say we wanted to make a command that simply sets the current block to air. For this example, we'll use the following command structure:
/break
As you can see, this command takes no arguments. This is fine, since our "argument" will be the sender's location. We can access the sender's location using the getLocation()
method from the NativeProxyCommandSender
object, available from the .executesNative()
method:
new CommandAPICommand("break")
.executesNative((sender, args) -> {
Location location = (Location) sender.getLocation();
if(location != null) {
location.getBlock().breakNaturally();
}
})
.register();
This can now be used via the following command examples:
/execute positioned 100 62 50 run break
/execute at @e[type=pig] run break
/execute in minecraft:overworld positioned 20 60 -20 run break
Resulting command executors
Resulting command executors are very similar to normal command executors, except they can return an integer result value.
(sender, args) -> {
//Code here
return /*some integer here*/ ;
};
Similarly, these will return a success value of 1 if it runs successfully, and a success value of 0 if it runs unsuccessfully. If a success value of 0 occurs, the result value will be 0. In short:
Command Works | Command Doesn't Work | |
---|---|---|
Success Value | 1 | 0 |
Result Value | result defined in your code | 0 |
The concept of result values are better explained through examples:
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.
new CommandAPICommand("randnum")
.executes((sender, args) -> {
return new Random().nextInt();
})
.register();
This returns a success value of 1 (Because no errors or CommandAPI.fail(String)
was thrown) and a result 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. The concept is to create a command that generates a random number from 1 to 100. If the number is 1 (thus, the chance of being chosen is \(\frac{1}{100}\)), then we award a player with some reward, say 64 diamonds.
To do this, we'll declare two commands:
/randomnumber - returns a random number between 1 and 99 (inclusive)
/givereward <player> - gives a player 64 diamonds and broadcasts it in the chat
Since we're declaring commands that are to be used in /execute
, we must ensure that these commands are registered in your plugin's onLoad()
method. First, we write our implementation for /randomnumber
. It is fairly straight forward using Java's ThreadLocalRandom
to generate a random number:
//Register random number generator command from 1 to 99 (inclusive)
new CommandAPICommand("randomnumber")
.executes((sender, args) -> {
return ThreadLocalRandom.current().nextInt(1, 100); //Returns random number from 1 <= x < 100
})
.register();
Now we write our implementation for /givereward
. In this example, we use the EntitySelectorArgument
to select a single player. We cast it to Player
and then add the items to their inventory.
//Register reward giving system for a target player
new CommandAPICommand("givereward")
.withArguments(new EntitySelectorArgument("target", EntitySelector.ONE_PLAYER))
.executes((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!");
})
.register();
Now that we've declared these commands, we can now use them in practice. We can use a command block to store a random number under the scoreboard score randVal
for a player called SomePlayer
, by executing the command /randomnumber
. Since /randomnumber
returns an integer, this value is stored in the scoreboard score:
/execute store result score SomePlayer randVal run randomnumber
To check if the random number is equal to 1, we can use the /execute if
command. If their score stored in randVal
matches 1, then we run the /givereward
command.
/execute if score SomePlayer randVal matches 1 run givereward SomePlayer
Handling command failures
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 containing 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
String[] fruit = new String[] {"banana", "apple", "orange"};
//Argument accepting a String, suggested with the list of fruit
List<Argument> arguments = new ArrayList<>();
arguments.add(new StringArgument("item").overrideSuggestions(fruit));
//Register the command
new CommandAPICommand("getfruit")
.withArguments(arguments)
.executes((sender, args) -> {
String inputFruit = (String) args[0];
if(Arrays.stream(fruit).anyMatch(inputFruit::equals)) {
//Do something with inputFruit
} else {
//The player's input is not in the list of fruit
CommandAPI.fail("That fruit doesn't exist!");
}
})
.register();
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 built-in 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.
Arguments
Arguments in the CommandAPI are registered by using a List<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 List
, the order of the elements inserted into it are preserved, meaning the order you add arguments to the List
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 a List
List<Argument> arguments = new ArrayList<>();
//Add an argument with the node "target", which is a PlayerArgument
arguments.add(new PlayerArgument("target"));
The String value is the node that is registered into Minecraft's internal command graph. This is name is also used as a prompt 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.
List<ArgumentType> arguments = new ArrayList<>();
arguments.add(new StringArgument("arg0"));
arguments.add(new PotionEffectArgument("arg1"));
arguments.add(new LocationArgument("arg2"));
new CommandAPICommand("cmd")
.withArguments(arguments)
.executes((sender, args) -> {
String stringArg = (String) args[0];
PotionEffectType potionArg = (PotionEffectType) args[1];
Location locationArg = (Location) args[2];
})
.register();
The type to cast each argument (declared in the dev.jorel.commandapi.arguments
package) is listed below:
Argument class | Data type |
---|---|
AngleArgument | float |
AdvancementArgument | org.bukkit.advancement.Advancement |
AxisArgument | java.util.EnumSet<org.bukkit.Axis> |
BiomeArgument | org.bukkit.block.Biome |
BlockPredicateArgument | java.util.function.Predicate<org.bukkit.block.Block> |
BlockStateArgument | org.bukkit.block.data.BlockData |
BooleanArgument | boolean |
ChatArgument | net.md_5.bungee.api.chat.BaseComponent[] |
ChatColorArgument | org.bukkit.ChatColor |
ChatComponentArgument | net.md_5.bungee.api.chat.BaseComponent[] |
CustomArgument<T> | T |
DoubleArgument | double |
EnchantmentArgument | org.bukkit.enchantments.Enchantment |
EntitySelectorArgument | The cast type changes depending on the input parameter:
|
EntityTypeArgument | org.bukkit.entity.EntityType |
EnvironmentArgument | org.bukkit.World.Environment |
FloatArgument | float |
FloatRangeArgument | dev.jorel.commandapi.wrappers.FloatRange |
FunctionArgument | dev.jorel.commandapi.wrappers.FunctionWrapper[] |
GreedyStringArgument | String |
IntegerArgument | int |
IntegerRangeArgument | dev.jorel.commandapi.wrappers.IntegerRange |
ItemStackArgument | org.bukkit.inventory.ItemStack |
ItemStackPredicateArgument | java.util.function.Predicate<org.bukkit.inventory.ItemStack> |
LiteralArgument | N/A |
Location2DArgument | dev.jorel.commandapi.wrappers.Location2D |
LocationArgument | org.bukkit.Location |
LongArgument | long |
LootTableArgument | org.bukkit.loot.LootTable |
MathOperationArgument | dev.jorel.commandapi.wrappers.MathOperation |
MultiLiteralArgument | String |
NBTCompoundArgument | de.tr7zw.nbtapi.NBTContainer |
ObjectiveArgument | String |
ObjectiveCriteriaArgument | String |
ParticleArgument | org.bukkit.Particle |
PlayerArgument | org.bukkit.entity.Player |
PotionEffectArgument | org.bukkit.potion.PotionEffectType |
RecipeArgument | The cast type changes depending on your Minecraft version:
|
RotationArgument | dev.jorel.commandapi.wrappers.Rotation |
ScoreboardSlotArgument | dev.jorel.commandapi.wrappers.ScoreboardSlot |
ScoreHolderArgument | The cast type changes depending on the input parameter:
|
SoundArgument | org.bukkit.Sound |
StringArgument | String |
TeamArgument | String |
TextArgument | String |
TimeArgument | int |
UUIDArgument | java.util.UUID |
Optional/Different Arguments
Sometimes, you want to register a command that has a different effect whether arguments are included or not. For example, take the /kill
command. If you run /kill
on its own, it will kill the command sender. If however you run /kill <target>
, it will kill the target. In other words, we have the following command structure:
/kill - Kills yourself
/kill <target> - Kills a target player
As shown by the command structure, we need to register two commands.
Example - /kill command with two separate arguments
For example, say we're registering a command /kill
:
/kill - Kills yourself
/kill <target> - Kills a target player
We first register the first /kill
command as normal:
new CommandAPICommand("kill")
.executesPlayer((player, args) -> {
player.setHealth(0);
})
.register();
Now we declare our command with arguments for our second command. Then, we can register our second command /kill <target>
as usual:
// Register our second /kill <target> command
new CommandAPICommand("kill")
.withArguments(new PlayerArgument("target"))
.executesPlayer((player, args) -> {
((Player) args[0]).setHealth(0);
})
.register();
This gives us the ability to run both /kill
and /kill <target>
with the same command name "kill", but have different results based on the arguments used.
In this example, we use the simpler, inline .withArguments(Argument... arguments)
method to register our argument. There is no difference to using this method as opposed to explicitly declaring a list and using .withArguments(List<Argument> arguments)
, so feel free to use whichever method you want!
Argument suggestions
Sometimes, you want to override the list of suggestions that are provided by an argument. To handle this, CommandAPI arguments contain three methods to override suggestions:
Argument overrideSuggestions(String... suggestions);
Argument overrideSuggestions(Function<CommandSender, String[]> suggestions);
Argument overrideSuggestions(BiFunction<CommandSender, Object[], String[]> suggestions);
Argument suggestion deferral
Before we go into detail about what the above methods do, we must first describe suggestion deferral. When the server loads, arguments that are provided with a String array are fixed, and do not change. However, arguments that are provided using the function and bifunction parameters are evaluated when they are needed - their execution is deferred until requested by the user. As such, if you wanted to retrieve something that isn't available during server load but is available during normal server running, it is recommended to use either of those functions instead of the String array.
For example, instead of doing the following, which retrieves a list of worlds on the server:
overrideSuggestions(Bukkit.getWorlds().stream().map(World::getName).toArray(String[]::new))
You should defer it using the function parameter:
overrideSuggestions(sender -> Bukkit.getWorlds().stream().map(World::getName).toArray(String[]::new))
Suggestions with a String Array
The first method, overrideSuggestions(String... suggestions)
, allows you to replace the suggestions normally associated with that argument with an array of strings. As described above, this doesn't use suggestion deferral, so this list will not update automatically.
Example - Teleport to worlds by overriding suggestions
Say we're creating a plugin with the ability to teleport to different warps on the server. If we were to retrieve a list of warps, we would be able to override the suggestions of a typical StringArgument
to teleport to that warp. Let's create a command with the following structure:
/warp <warp>
We then implement our warp teleporting command using overrideSuggestions()
on the StringArgument
to provide a list of warps to teleport to:
List<Argument> arguments = new ArrayList<>();
arguments.add(new StringArgument("world").overrideSuggestions("northland", "eastland", "southland", "westland"));
new CommandAPICommand("warp")
.withArguments(arguments)
.executesPlayer((player, args) -> {
String warp = (String) args[0];
player.teleport(warps.get(warp)); // Look up the warp in a map, for example
})
.register();
Suggestions depending on a command sender
The overrideSuggestions(Function<CommandSender, String[]> suggestions)
method allows you to replace the suggestions normally associated with that argument with an array of strings that are evaluated dynamically using information about the command sender.
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. Since the list of friends depends on the sender, we can use the function to determine what our suggestions should be. Let's use the following command to teleport to a friend from our friend list:
/friendtp <friend>
Let's say we have a simple class to get the friends of a command sender:
public class Friends {
static Map<UUID, String[]> friends /* = ... */;
public static String[] getFriends(CommandSender sender) {
if(sender instanceof Player) {
//Look up friends in a database or file
return friends.get(((Player) sender).getUniqueId());
} else {
return new String[0];
}
}
}
We can then use this to generate our suggested list of friends:
List<Argument> arguments = new ArrayList<>();
arguments.add(new PlayerArgument("friend").overrideSuggestions((sender) -> {
return Friends.getFriends(sender);
}));
new CommandAPICommand("friendtp")
.withArguments(arguments)
.executesPlayer((player, args) -> {
Player target = (Player) args[0];
player.teleport(target);
})
.register();
Developer's Note:
The syntax of inlining the
.overrideSuggestions()
method has been designed to work well with Java's lambdas. For example, we could write the above code more consisely, such as:List<Argument> arguments = new ArrayList<>(); arguments.add(new PlayerArgument("friend").overrideSuggestions(Friends::getFriends));
Suggestions depending on previous arguments
The overrideSuggestions(BiFunction<CommandSender, Object[], String[]> suggestions)
method is the most powerful suggestion overriding function that the CommandAPI offers. It has the capability to suggest arguments based on the values of previously inputted arguments.
This method requires a function that takes in a command sender and the list of previous arguments and must return a String[]
of suggestions. The arguments are parsed exactly like any regular CommandAPI
command argument.
Note:
The ability to use previously declared arguments does not work via redirects. This means that any command that comes before it that leads into a command that uses suggestions depending on previous arguments will not work. For example, if we had a command /mycommand <arg1> <arg2> <arg3>
and ran it as normal, it would work as normal:
/mycommand arg1 arg2 arg3
However, if we redirect execution via the /execute
command to have the following:
/execute run mycommand <suggestions>
This won't work, because we make use of a redirect:
\[\texttt{/execute run} \xrightarrow{redirect} \texttt{mycommand arg1 arg2 arg3}\]
To clarify, by "does not work", I mean that it is not possible to access the Object[]
of previously declared arguments. If a command occurs via a redirect, the Object[]
of previously declared arguments will be null.
Example - Sending a message to a nearby player
Say we wanted to create a command that lets you send a message to a specific player in a given radius. (This is a bit of a contrived example, but let's roll with it). To do this, we'll use the following command structure:
/localmsg <radius> <target> <message>
When run, this command will send a message to a target player within the provided radius. To help identify which players are within a radius, we can override the suggestions on the <target>
argument to include a list of players within the provided radius. We do this with the following code:
// Declare our arguments as normal
List<Argument> arguments = new ArrayList<>();
arguments.add(new IntegerArgument("radius"));
// Override the suggestions for the PlayerArgument, using (sender, args) as the parameters
// sender refers to the command sender that is running this command
// args refers to the Object[] of PREVIOUSLY DECLARED arguments (in this case, the IntegerArgument radius)
arguments.add(new PlayerArgument("target").overrideSuggestions((sender, args) -> {
// Cast the first argument (radius, which is an IntegerArgument) to get its value
int radius = (int) args[0];
// Get nearby entities within the provided radius
Player player = (Player) sender;
Collection<Entity> entities = player.getWorld().getNearbyEntities(player.getLocation(), radius, radius, radius);
// Get player names within that radius
return entities.stream()
.filter(e -> e.getType() == EntityType.PLAYER)
.map(Entity::getName)
.toArray(String[]::new);
}));
arguments.add(new GreedyStringArgument("message"));
// Declare our command as normal
new CommandAPICommand("localmsg")
.withArguments(arguments)
.executesPlayer((player, args) -> {
Player target = (Player) args[1];
String message = (String) args[2];
target.sendMessage(message);
})
.register();
As shown in this code, we use the (sender, args) -> ...
lambda to override suggestions with previously declared arguments. In this example, our variable args
will be { int }
, where this int
refers to the radius. Note how this object array only has the previously declared arguments (and not for example { int, Player, String }
).
Safe argument suggestions
So far, we've covered how to override suggestions using the overrideSuggestions()
method. The issue with using Strings for suggestion listings is that they are prone to errors. As a result, some arguments include the safeOverrideSuggestions()
, which provides type-safety checks for argument suggestions, as well as automatic "Bukkit-to-suggestion" conversion.
The whole point of the safe argument suggestions method is that parameters entered in this method are guaranteed to work.
The use of the safe override suggestions function is basically the same as overrideSuggestions()
from the previous section, except instead of returning a String[]
, you now return a T[]
, where T
is the class corresponding to the argument. This is described in more detail in the table below.
Argument safeOverrideSuggestions(T... suggestions);
Argument safeOverrideSuggestions(Function<CommandSender, T[]> suggestions);
Argument safeOverrideSuggestions(BiFunction<CommandSender, Object[], T[]> suggestions);
Supported arguments
Not all arguments support safe suggestions. This is mostly due to implementation constraints or inadequate support by the Bukkit API.
The list of supported arguments are displayed in the following table. The parameter T
(shown in the method signatures above) are also provided for each argument. This parameter is the same as the cast argument described in Argument Casting, except for a few exceptions which are outlined in bold.
Argument | Class (T) |
---|---|
AdvancementArgument | org.bukkit.advancement.Advancement |
AxisArgument | java.util.EnumSet<org.bukkit.Axis> |
BiomeArgument | org.bukkit.block.Biome |
BooleanArgument | Boolean |
ChatColorArgument | org.bukkit.ChatColor |
DoubleArgument | Double |
EnchantmentArgument | org.bukkit.enchantments.Enchantment |
EntityTypeArgument | org.bukkit.entity.EntityType |
EnvironmentArgument | org.bukkit.World.Environment |
FloatArgument | Float |
FloatRangeArgument | dev.jorel.commandapi.wrappers.FloatRange |
FunctionArgument | org.bukkit.NamespacedKey |
GreedyStringArgument | String |
IntegerArgument | Integer |
IntegerRangeArgument | dev.jorel.commandapi.wrappers.IntegerRange |
ItemStackArgument | org.bukkit.inventory.ItemStack |
Location2DArgument | dev.jorel.commandapi.wrappers.Location2D |
LocationArgument | org.bukkit.Location |
LongArgument | Long |
LootTableArgument | org.bukkit.loot.LootTable |
MathOperationArgument | dev.jorel.commandapi.wrappers.MathOperation |
NBTCompoundArgument | de.tr7zw.nbtapi.NBTContainer |
ObjectiveArgument | org.bukkit.scoreboard.Objective |
ParticleArgument | org.bukkit.Particle |
PlayerArgument | org.bukkit.entity.Player |
PotionEffectArgument | org.bukkit.potion.PotionEffectType |
RecipeArgument | org.bukkit.inventory.Recipe |
RotationArgument | dev.jorel.commandapi.wrappers.Rotation |
ScoreboardSlotArgument | dev.jorel.commandapi.wrappers.ScoreboardSlot |
SoundArgument | org.bukkit.Sound |
TeamArgument | org.bukkit.scoreboard.Team |
TimeArgument | dev.jorel.commandapi.wrappers.Time |
Safe time arguments
While most of the arguments are fairly straight forward, I'd like to bring your attention to the TimeArgument
's safe suggestions function. This uses dev.jorel.commandapi.wrappers.Time
as the class for T
to ensure type-safety. The Time
class has three static methods:
Time ticks(int ticks);
Time days(int days);
Time seconds(int seconds);
These create representations of ticks (e.g. 40t
), days (e.g. 2d
) and seconds (e.g. 60s
) respectively.
Safe function arguments
Although all safe arguments are indeed "type-safe", the function argument uses a NamespacedKey
which cannot be checked fully at compile time. As a result, this is argument should be used with caution - providing a NamespacedKey
suggestion that does not exist when the server is running will cause that command to fail if that suggestion is used.
Safe scoreboard slot arguments
Scoreboard slots now include two new static methods so they can be used with safe arguments:
ScoreboardSlot of(DisplaySlot slot);
ScoreboardSlot ofTeamColor(ChatColor color);
This allows you to create ScoreboardSlot
instances which can be used with the safe override suggestions method.
Examples
While this should be fairly straight forward, here's a few examples of how this can be used in practice:
Example - Safe recipe arguments
Say we have a plugin that registers custom items which can be crafted. In this example, we use an "emerald sword" with a custom crafting recipe. Now say that we want to have a command that gives the player the item from our declared recipes. To do this, we first register our custom items:
// Create our itemstack
ItemStack emeraldSword = new ItemStack(Material.DIAMOND_SWORD);
ItemMeta meta = emeraldSword.getItemMeta();
meta.setDisplayName("Emerald Sword");
meta.setUnbreakable(true);
emeraldSword.setItemMeta(meta);
// Create and register our recipe
ShapedRecipe emeraldSwordRecipe = new ShapedRecipe(new NamespacedKey(this, "emerald_sword"), emeraldSword);
emeraldSwordRecipe.shape(
"AEA",
"AEA",
"ABA"
);
emeraldSwordRecipe.setIngredient('A', Material.AIR);
emeraldSwordRecipe.setIngredient('E', Material.EMERALD);
emeraldSwordRecipe.setIngredient('B', Material.BLAZE_ROD);
getServer().addRecipe(emeraldSwordRecipe);
... // Omitted, more itemstacks and recipes
Once we've done that, we can now include them in our command registration. To do this, we use safeOverrideSuggestions(recipes)
and then register our command as normal:
// Safely override with the recipe we've defined
List<Argument> arguments = new ArrayList<>();
arguments.add(new RecipeArgument("recipe").safeOverrideSuggestions(emeraldSwordRecipe, /* Other recipes */));
// Register our command
new CommandAPICommand("giverecipe")
.withArguments(arguments)
.executesPlayer((player, args) -> {
Recipe recipe = (Recipe) args[0];
player.getInventory().addItem(recipe.getResult());
})
.register();
Example - Safe /spawnmob suggestions
Say we have a command to spawn mobs:
/spawnmob <mob>
Now say that we don't want non-op players to spawn bosses. To do this, we'll create a List<EntityType>
which is the list of all mobs that non-ops are allowed to spawn:
EntityType[] forbiddenMobs = new EntityType[] {EntityType.ENDER_DRAGON, EntityType.WITHER};
List<EntityType> allowedMobs = new ArrayList<>(Arrays.asList(EntityType.values()));
allowedMobs.removeAll(Arrays.asList(forbiddenMobs)); //Now contains everything except enderdragon and wither
We then use our safe arguments to return an EntityType[]
as the list of values that are suggested to the player. In this example, we use the Function<CommandSender, EntityType[]>
argument to determine if the sender has permissions to view the suggestions:
List<Argument> arguments = new ArrayList<>();
arguments.add(new EntityTypeArgument("mob").safeOverrideSuggestions(
sender -> {
if(sender.isOp()) {
return EntityType.values(); //All entity types
} else {
return allowedMobs.toArray(new EntityType[0]); //Only allowsMobs
}
})
);
Now we register our command as normal:
new CommandAPICommand("spawnmob")
.withArguments(arguments)
.executesPlayer((player, args) -> {
EntityType entityType = (EntityType) args[0];
player.getWorld().spawnEntity(player.getLocation(), entityType);
})
.register();
Example - Removing a potion effect from a player
Say we wanted to remove a potion effect from a player. To do this, we'll use the following command structure:
/removeeffect <player> <potioneffect>
Now, we don't want to remove a potion effect that already exists on a player, so instead we'll use the safe arguments to find a list of potion effects on the target player and then only suggest those potion effects. To do this, we'll use the BiFunction<CommandSender, Object[], PotionEffectType[]>
parameter, as it allows us to access the previously defined <player>
argument.
List<Argument> arguments = new ArrayList<>();
arguments.add(new EntitySelectorArgument("target", EntitySelector.ONE_PLAYER));
arguments.add(new PotionEffectArgument("potioneffect").safeOverrideSuggestions(
(sender, prevArgs) -> {
Player target = (Player) prevArgs[0];
//Convert PotionEffect[] into PotionEffectType[]
return target.getActivePotionEffects().stream()
.map(PotionEffect::getType)
.toArray(PotionEffectType[]::new);
})
);
And then we can register our command as normal:
new CommandAPICommand("removeeffect")
.withArguments(arguments)
.executesPlayer((player, args) -> {
EntityType entityType = (EntityType) args[0];
player.getWorld().spawnEntity(player.getLocation(), entityType);
})
.register();
Argument suggestions with tooltips
The CommandAPI can also display tooltips for specific argument suggestions. These are shown to the user when they hover over a given suggestion and can be used to provide more context to a user about the suggestions that are shown to them. In this section, we'll outline the two ways of creating suggestions with tooltips:
- Normal (String) suggestions with tooltips
- Safe suggestions with tooltips
Tooltips can have formatting to change how the text is displayed by using the ChatColor
class.
Tooltips with normal (String) suggestions
To use these features, the CommandAPI includes the overrideSuggestionsT
methods for arguments, that accept IStringTooltip
objects instead of String
objects:
Argument overrideSuggestionsT(IStringTooltip... suggestions);
Argument overrideSuggestionsT(Collection<IStringTooltip> suggestions);
Argument overrideSuggestionsT(Function<CommandSender, IStringTooltip[]> suggestions);
Argument overrideSuggestionsT(BiFunction<CommandSender, Object[], IStringTooltip[]> suggestions);
The StringTooltip
class is the CommandAPI's default implementation of IStringTooltip
, which has two static methods to construct it easily:
StringTooltip none(String suggestion);
StringTooltip of(String suggestion, String tooltip);
The first method, StringTooltip.none(String)
creates a normal suggestion entry with no tooltip, whereas the StringTooltip.of(String, String)
method creates a suggestion with the provided tooltip text.
Example - An emotes command with string suggestion tooltips
Say we want to create a simple command to provide ingame emotes between players. For example, if you did /emote wave Bob
, you'll "wave" to the player Bob. For this example, we'll use the following command structure:
/emote <emote> <target>
First, we'll declare our arguments. Here, we'll use the overrideSuggestionsT
method, along with the StringTooltip.of(String, String)
method to create emote suggestions and include suitable descriptions:
List<Argument> arguments = new ArrayList<>();
arguments.add(new StringArgument("emote")
.overrideSuggestionsT(
StringTooltip.of("wave", "Waves at a player"),
StringTooltip.of("hug", "Gives a player a hug"),
StringTooltip.of("glare", "Gives a player the death glare")
)
);
arguments.add(new PlayerArgument("target"));
Finally, we declare our command as normal:
new CommandAPICommand("emote")
.withArguments(arguments)
.executesPlayer((player, args) -> {
String emote = (String) args[0];
Player target = (Player) args[1];
switch(emote) {
case "wave":
target.sendMessage(player.getName() + " waves at you!");
break;
case "hug":
target.sendMessage(player.getName() + " hugs you!");
break;
case "glare":
target.sendMessage(player.getName() + " gives you the death glare...");
break;
}
})
.register();
The IStringTooltip
interface can be implemented by any other class to provide tooltips for custom objects. The IStringTooltip
interface has the following methods:
public interface IStringTooltip {
public String getSuggestion();
public String getTooltip();
}
This is incredibly useful if you are using suggestions with custom objects, such as a plugin that has custom items.
Example - Using IStringTooltip
for custom items
Let's say we've created a simple plugin which has custom items. For a custom item, we'll have a super simple class CustomItem
that sets its name, lore and attached itemstack:
public class CustomItem implements IStringTooltip {
private ItemStack itemstack;
private String name;
public CustomItem(ItemStack itemstack, String name, String lore) {
ItemMeta meta = itemstack.getItemMeta();
meta.setDisplayName(name);
meta.setLore(Arrays.asList(lore));
itemstack.setItemMeta(meta);
this.itemstack = itemstack;
this.name = name;
}
public String getName() {
return this.name;
}
public ItemStack getItem() {
return this.itemstack;
}
@Override
public String getSuggestion() {
return this.itemstack.getItemMeta().getDisplayName();
}
@Override
public String getTooltip() {
return this.itemstack.getItemMeta().getLore().get(0);
}
}
Let's also say that our plugin has registered lots of CustomItem
s and has this stored in a CustomItem[]
in our plugin. We could then use this as our input for suggestions:
CustomItem[] customItems = new CustomItem[] {
new CustomItem(new ItemStack(Material.DIAMOND_SWORD), "God sword", "A sword from the heavens"),
new CustomItem(new ItemStack(Material.PUMPKIN_PIE), "Sweet pie", "Just like grandma used to make")
}; //
new CommandAPICommand("giveitem")
.withArguments(new StringArgument("item").overrideSuggestionsT(customItems)) // We use customItems[] as the input for our suggestions with tooltips
.executesPlayer((player, args) -> {
String itemName = (String) args[0];
//Give them the item
for(CustomItem item : customItems) {
if(item.getName().equals(itemName)) {
player.getInventory().addItem(item.getItem());
}
}
})
.register();
Tooltips with safe suggestions
Using tooltips with safe suggestions is almost identical to the method described above for normal suggestions, except for two things. Firstly, you must use safeOverrideSuggestionsT
method instead of the overrideSuggestionsT
method and secondly, instead of using StringTooltip
, you must use Tooltip<S>
. Let's look at these differences in more detail.
The safeOverrideSuggestionsT
methods are fairly similar to the overrideSuggestionsT
methods, except instead of using StringTooltip
, it simply uses Tooltip<S>
.
Argument safeOverrideSuggestionsT(Tooltip<S>... suggestions);
Argument safeOverrideSuggestionsT(Collection<Tooltip<S>> suggestions);
Argument safeOverrideSuggestionsT(Function<CommandSender, Tooltip<S>[]> suggestions);
Argument safeOverrideSuggestionsT(BiFunction<CommandSender, Object[], Tooltip<S>[]> suggestions);
The Tooltip<S>
class represents a tooltip for a given object S
. For example, a tooltip that is for a LocationArgument
would be a Tooltip<Location>
and a tooltip for an EnchantmentArgument
would be a Tooltip<Enchantment>
.
Just like the StringTooltip
class, the Tooltip<S>
class provides the following static methods, which operate exactly the same as the ones in the StringTooltip
class:
Tooltip<S> none(S object);
Tooltip<S> of(S object, String tooltip);
Tooltip<S>[] arrayOf(Tooltip<S>... tooltips);
The use of arrayOf
is heavily recommended as it provides the necessary type safety for Java code to ensure that the correct types are being passed to the safeOverrideSuggestionsT
method.
Example - Teleportation command with suggestion descriptions
Say we wanted to create a custom teleport command which suggestions a few key locations. In this example, we'll use the following command structure:
/warp <location>
First, we'll declare our arguments. Here, we use a LocationArgument
and use the safeOverrideSuggestionsT
method, with a parameter for the command sender, so we can get information about the world. We populate the suggestions with tooltips using Tooltip.of(Location, String)
and collate them together with Tooltip.arrayOf(Tooltip<Location>...)
:
List<Argument> arguments = new ArrayList<>();
arguments.add(new LocationArgument("location")
.safeOverrideSuggestionsT((sender) -> {
return Tooltip.arrayOf(
Tooltip.of(((Player) sender).getWorld().getSpawnLocation(), "World spawn"),
Tooltip.of(((Player) sender).getBedSpawnLocation(), "Your bed"),
Tooltip.of(((Player) sender).getTargetBlockExact(256).getLocation(), "Target block")
);
}));
In the arguments declaration, we've casted the command sender to a player. To ensure that the command sender is definitely a player, we'll use the executesPlayer
command execution method in our command declaration:
new CommandAPICommand("warp")
.withArguments(arguments)
.executesPlayer((player, args) -> {
player.teleport((Location) args[0]);
})
.register();
Listed arguments
Arguments have a setting which determine whether or not they are present in the Object[] args
that is populated when executing a command.
By default, the LiteralArgument
has this setting set to false
, hence the literal values are not present in the Object[] args
.
This flag is set using the following function:
Argument setListed(boolean listed);
Example - Setting listed arguments
Say we have the following command:
/mycommand <player> <value> <message>
Let's also say that in our implementation of this command, we don't actually perform any processing for <value>
. Hence, listing it in the Object args[]
is unnecessary.
new CommandAPICommand("mycommand")
.withArguments(new PlayerArgument("player"))
.withArguments(new IntegerArgument("value").setListed(false))
.withArguments(new GreedyStringArgument("message"))
.executes((sender, args) -> {
// args == [player, message]
Player player = (Player) args[0];
String message = (String) args[1]; //Note that this is args[1] and NOT args[2]
player.sendMessage(message);
})
.register();
In this scenario, the argument <value>
is not present in the Object args[]
for the executor.
Argument types
Primitive arguments
Primitive arguments are arguments that represent Java primitive types, such as int
, float
, double
, boolean
and long
. These arguments are defined in their respective classes:
Primitive type | CommandAPI class |
---|---|
int | IntegerArgument |
float | FloatArgument |
double | DoubleArgument |
long | LongArgument |
boolean | BooleanArgument |
These arguments simply cast to their primitive type and don't need any extra work.
Boolean arguments
The BooleanArgument
class represents the Boolean values true
and false
.
Example - Config editing plugin
Say we want to create a plugin that lets you edit its own config.yml
file using a command. To do this, let's create a command with the following structure:
/editconfig <config-key> <value>
We first retrieve the keys from the configuration file using the typical Bukkit API. We construct our List
to hold our arguments, with the first parameter being a String key (in the form of a TextArgument
, overridden with an array of suggestions). Finally, we register our command and update the config, ensuring that we cast the BooleanArgument
to boolean
:
// Load keys from config file
String[] configKeys = getConfig().getKeys(true).toArray(new String[0]);
// Create arguments with the config key and a boolean value to set it to
List<Argument> arguments = new ArrayList<>();
arguments.add(new TextArgument("config-key").overrideSuggestions(configKeys));
arguments.add(new BooleanArgument("value"));
// Register our command
new CommandAPICommand("editconfig")
.withArguments(arguments)
.executes((sender, args) -> {
// Update the config with the boolean argument
getConfig().set((String) args[0], (boolean) args[1]);
})
.register();
}
Numerical arguments
Numbers are represented using the designated number classes:
Class | Description |
---|---|
IntegerArgument | Whole numbers between Integer.MIN_VALUE and Integer.MAX_VALUE |
LongArgument | Whole numbers between Long.MIN_VALUE and Long.MAX_VALUE |
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(min) | Values greater than or equal to min |
new IntegerArgument(min, max) | Values greater than or equal to min and less than or equal to max |
Each range is inclusive, so it includes the number given to it. If the minimum value provided is larger than the maximum value, an InvalidRangeException
is thrown.
Ranged arguments
Ranged arguments allow players to provide a range between two numbers, all within a single argument. The CommandAPI provides two ranged arguments, IntegerRangeArgument
for ranges with only integer values, and FloatRangeArgument
for ranged with potential floating point values.
These consist of values such as:
Input | What it means |
---|---|
5 | The number 5 |
5..10 | Numbers between 5 and 10, including 5 and 10 |
5.. | Numbers greater than or equal to 5 (bounded by Java's max number size) |
..5 | Numbers less than or equal to 5 (bounded by Java's min number size) |
This allows you to let users define a range of values, which can be used to limit a value, such as the number of players in a region or for a random number generator.
The IntegerRange & FloatRange class
The CommandAPI returns an IntegerRange
from the IntegerRangeArgument
, and a FloatRange
from the FloatRangeArgument
, which represents the upper and lower bounds of the numbers provided by the command sender, as well as a method to check if a number is within that range.
The IntegerRange
class has the following methods:
class IntegerRange {
public int getLowerBound();
public int getUpperBound();
public boolean isInRange(int);
}
The FloatRange
class has the following methods:
class FloatRange {
public float getLowerBound();
public float getUpperBound();
public boolean isInRange(float);
}
Example - Searching chests for certain items
Say you're working on a plugin for server administrators to help them find restricted items. A method of doing so would be to search chests in a given radius for certain items. As such, we can use the following structure:
/searchchests <range> <item>
Now, we simply create our arguments using IntegerRangeArgument
for our range and ItemStackArgument
as the item to search for. We can then find all chests in a given area and determine if it is within the range provided by the command sender by using range.isInRange(distance)
:
// Declare our arguments for /searchrange <IntegerRange> <ItemStack>
List<Argument> arguments = new ArrayList<>();
arguments.add(new IntegerRangeArgument("range"));
arguments.add(new ItemStackArgument("item"));
new CommandAPICommand("searchrange")
.withArguments(arguments)
.executesPlayer((player, args) -> {
// Retrieve the range from the arguments
IntegerRange range = (IntegerRange) args[0];
ItemStack itemStack = (ItemStack) args[1];
// Store the locations of chests with certain items
List<Location> locations = new ArrayList<>();
// Iterate through all chunks, and then all tile entities within each chunk
for(Chunk chunk : player.getWorld().getLoadedChunks()) {
for(BlockState blockState : chunk.getTileEntities()) {
// The distance between the block and the player
int distance = (int) blockState.getLocation().distance(player.getLocation());
// Check if the distance is within the specified range
if(range.isInRange(distance)) {
// Check if the tile entity is a chest
if(blockState instanceof Chest) {
Chest chest = (Chest) blockState;
// Check if the chest contains the item specified by the player
if(chest.getInventory().contains(itemStack.getType())) {
locations.add(chest.getLocation());
}
}
}
}
}
// Output the locations of the chests, or whether no chests were found
if(locations.isEmpty()) {
player.sendMessage("No chests were found");
} else {
player.sendMessage("Found " + locations.size() + " chests:");
locations.forEach(location -> {
player.sendMessage(" Found at: "
+ location.getX() + ", "
+ location.getY() + ", "
+ location.getZ());
});
}
})
.register();
String arguments
There are three types of arguments that return Java's String
object. Each have their own unique set of features which make them suitable for specific needs.
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
Examples of StringArgument uses:
- 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: ""
Examples of TextArgument uses:
- Editing the contents of a sign
- A command that requires multiple text arguments (say, username and password?)
Greedy string argument
Greedy Arguments:
The
GreedyStringArgument
, similar to theChatArgument
uses the entire argument array from its current position. This means that it never ends, therefore if it is used, it must be the last element of yourList
of arguments.For example, if you have a command
/message <message> <target>
, it would not be able to determine where the message ends and the<target>
argument begins.If a
GreedyStringArgument
orChatArgument
is not declared at the end of theList
of arguments, or multiple of these arguments are used in the sameList
, the CommandAPI throws aGreedyArgumentException
.
The GreedyStringArgument
takes the TextArgument
a step further. Any characters and symbols are allowed and quotation marks are not required.
Example - Messaging command
Say we have a simple message command of the following form:
/message <target> <message>
This would be ideal for a greedy string, since it can consume all text after the player's name:
List<Argument> arguments = new ArrayList<>();
arguments.add(new PlayerArgument("target"));
arguments.add(new GreedyStringArgument("message"));
new CommandAPICommand("message")
.withArguments(arguments)
.executes((sender, args) -> {
((Player) args[0]).sendMessage((String) args[1]);
})
.register();
Any text entered after the <target>
argument would be sent to the player. For example, the command could be used as follows:
/message Skepter This is some incredibly long string with "symbols" and $p3c!aL characters~
Note how this only works if the greedy string argument is at the end. If, say, the command was /message <message> <target>
, it would not be able to determine where the <message>
argument ends and the <target>
argument begins.
Examples of GreedyStringArgument uses:
- A messaging/whisper command (as shown in the example above)
- A mailing command
- Any command involving lots of text, such as a command to write the contents of a book
- Any command which involves an unreasonable/unknown amount of arguments
- Any command where you want to parse arguments similar to how regular Bukkit would
Position-based arguments
Location arguments
In the CommandAPI, there are two arguments used to represent location. The LocationArgument
argument, which represents a 3D location \( (x, y, z) \) and the Location2DArgument
, which represents 2D location \( (x, z) \).
Location (3D space)
The LocationArgument
class is used to specify a location in the command sender's current world, returning a Bukkit Location
object. It 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. The LocationType
enum consists of two values:
LocationType.BLOCK_POSITION
BLOCK_POSITION
refers to integer block coordinates. When in-game as a player, the suggested location is the coordinates of block you are looking at when you type the command.
LocationType.PRECISE_POSITION
PRECISE_PRECISION
uses exact coordinates, using the double
primitive type. When in-game as a player, the suggested location is the exact coordinates of where your cursor is pointing at when you type the command.
If no LocationType
is provided, the LocationArgument
will use PRECISE_POSITION
by default.
Example - Break block using coordinates
We can declare a simple command to break a block:
/break <location>
Simply put, given the coordinates provided to the command, "break" the block by setting it's type to Material.AIR
. For this example, we're referring to block specific coordinates, so we want to use LocationType.BLOCK_POSITION
:
new CommandAPICommand("break")
//We want to target blocks in particular, so use BLOCK_POSITION
.withArguments(new LocationArgument("block", LocationType.BLOCK_POSITION))
.executesPlayer((player, args) -> {
((Location) args[0]).getBlock().setType(Material.AIR);
})
.register();
Location (2D space)
The Location2DArgument
is pretty much identical in use to the LocationArgument
for 3D coordinates, except instead of returning a Location
object, it instead returns a Location2D
object that extends Location
(thus, being compatible anywhere you would normally be able to use Location
).
Note:
The Location2DArgument
cannot be used with LocationType.PRECISE_POSITION
in Minecraft 1.13. However, it can be used normally with LocationType.PRECISE_POSITION
in Minecraft versions 1.13.1 and later.
Rotation arguments
The RotationArgument
allows users to specify a pair of pitch and yaw coordinates. By default (using the ~
symbol), this refers to the player's current pitch and yaw of where they are looking at.
The RotationArgument
class returns a Rotation
object, which consists of the following methods:
Method name | What it does |
---|---|
float getPitch() | Returns a player's pitch (up and down rotation) |
float getYaw() | Returns a player's yaw (left and right rotation) |
float getNormalizedPitch() | Returns a player's pitch between -90 and 90 degrees |
float getNormalizedYaw() | Returns a player's yaw between -180 and 180 degrees |
Example: Rotate an armor stand head
Say we want to make an armor stand look in a certain direction. To do this, we'll use the following command:
/rotate <rotation> <target>
To do this, we'll use the rotation from the RotationArgument
and select an entity using the EntitySelectorArgument
, with EntitySelector.ONE_ENTITY
. We then check if our entity is an armor stand and if so, we set its head pose to the given rotation.
new CommandAPICommand("rotate")
.withArguments(new RotationArgument("rotation"))
.withArguments(new EntitySelectorArgument("target", EntitySelector.ONE_ENTITY))
.executes((sender, args) -> {
Rotation rotation = (Rotation) args[0];
Entity target = (Entity) args[1];
if(target instanceof ArmorStand) {
ArmorStand a = (ArmorStand) target;
a.setHeadPose(new EulerAngle(Math.toRadians(rotation.getPitch()), Math.toRadians(rotation.getYaw() - 90), 0));
}
})
.register();
Note how the head pose requires an EulerAngle
as opposed to a pitch and yaw. To account for this, we convert our rotation (which is in degrees) into an EulerAngle
in radians.
AxisArgument
The AxisArgument
class refers to the x, y and z axes. When used with the CommandAPI, it returns an EnumSet<Axis>
(You can view the documentation for EnumSet
here).
Examples of AxisArgument uses:
- Reflecting a structure in the x, y or z axis
Chat arguments
The CommandAPI provides three main classes to interact with chat formatting in Minecraft.
Chat color argument
The ChatColorArgument
class is used to represent a given chat color (e.g. red or green)
Example - Username color changing plugin
Say we want to create a plugin to change the color of a player's username. We want to create a command of the following form:
/namecolor <chatcolor>
We then use the ChatColorArgument
to change the player's name color:
new CommandAPICommand("namecolor")
.withArguments(new ChatColorArgument("chatcolor"))
.executesPlayer((player, args) -> {
ChatColor color = (ChatColor) args[0];
player.setDisplayName(color + player.getName());
})
.register();
Spigot-based chat arguments
Developer's Note:
The two following classes,
ChatComponentArgument
andChatArgument
depend on a Spigot based server. This means that these arguments 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:
Chat component argument
The ChatComponentArgument
class accepts raw chat-based JSON as valid input. Despite being regular JSON, it must conform to the standard declared here, which consists of JSON that has a limited subset of specific keys (In other words, you can have a JSON object that has the key text
, but not one that has the key blah
).
This is converted into Spigot's BaseComponent[]
, which can be used for the following:
-
Broadcasting messages to all players on the server using:
Bukkit.getServer().spigot().broadcast(BaseComponent[]);
-
Adding and setting pages to books using
BookMeta
:BookMeta meta = // ... meta.spigot().setPages(BaseComponent[]);
-
Sending messages to
Player
objects:Player player = // ... player.spigot().sendMessage(BaseComponent[]);
-
Sending messages to
CommandSender
objects:CommandSender sender = // ... sender.spigot().sendMessage(BaseComponent[]);
Example - Book made from raw JSON
Say we want to generate a book using raw JSON. For this example, we'll use the following JSON (generated from minecraftjson.com) to generate our book:
["", {
"text": "Once upon a time, there was a guy call "
}, {
"text": "Skepter",
"color": "light_purple",
"hoverEvent": {
"action": "show_entity",
"value": "Skepter"
}
}, {
"text": " and he created the "
}, {
"text": "CommandAPI",
"underlined": true,
"clickEvent": {
"action": "open_url",
"value": "https://github.com/JorelAli/CommandAPI"
}
}]
Since we're writing a book, we must ensure that all quotes have been escaped. This can also be performed on the minecraftjson.com website by selecting "book":
["[\"\",{\"text\":\"Once upon a time, there was a guy call \"},{\"text\":\"Skepter\",\"color\":\"light_purple\",\"hoverEvent\":{\"action\":\"show_entity\",\"value\":\"Skepter\"}},{\"text\":\" and he created the \"},{\"text\":\"CommandAPI\",\"underlined\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://github.com/JorelAli/CommandAPI\"}}]"]
Now let's define our command. Since book text is typically very large - too large to be entered into a chat, we'll make a command block compatible command by providing a player parameter:
/makebook <player> <contents>
Now we can create our book command. We use the player as the main target by using their name for the author field, as well as their inventory to place the book. We finally construct our book using the .setPages(BaseComponent[])
method:
new CommandAPICommand("makebook")
.withArguments(new PlayerArgument("player"))
.withArguments(new ChatComponentArgument("contents"))
.executes((sender, args) -> {
Player player = (Player) args[0];
BaseComponent[] arr = (BaseComponent[]) args[1];
//Create book
ItemStack is = new ItemStack(Material.WRITTEN_BOOK);
BookMeta meta = (BookMeta) is.getItemMeta();
meta.setTitle("Custom Book");
meta.setAuthor(player.getName());
meta.spigot().setPages(arr);
is.setItemMeta(meta);
//Give player the book
player.getInventory().addItem(is);
})
.register();
Chat argument
Developer's Note:
It has been observed that the ChatArgument
does not work on Spigot 1.16.1. This is not the case however for Spigot versions 1.15.2 and below, as well as Spigot 1.16.2.
Note:
The
ChatArgument
class is an argument similar to theGreedyStringArgument
, in the sense that it has no terminator and must be defined at the end of yourList
of arguments. For more information on this, please read the section on Greedy arguments.
The ChatArgument
is basically identical to the GreedyStringArgument
, with the added functionality of enabling entity selectors, such as @e
, @p
and so on. The ChatArgument
also returns a BaseComponent[]
, similar to the ChatComponentArgument
.
Example - Sending personalized messages to players
Say we wanted to broadcast a "personalized" message to players on the server. By "personalized", we mean a command which changes its output depending on who we are sending the output to. Simply put, we want a command of the following structure:
/pbroadcast <message>
Say we're on a server with 2 players: Bob and Michael. If I were to use the following command:
/pbroadcast Hello @p
Bob would receive the message "Hello Bob", whereas Michael would receive the message "Hello Michael". We can use the ChatArgument
to create this "personalized" broadcast:
new CommandAPICommand("pbroadcast")
.withArguments(new ChatArgument("message"))
.executes((sender, args) -> {
BaseComponent[] message = (BaseComponent[]) args[0];
//Broadcast the message to everyone on the server
Bukkit.getServer().spigot().broadcast(message);
})
.register();
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 - Remove entities command
Say we want a command to remove certain types of entities. Typically, this would be implemented using a simple command like:
/remove <player>
/remove <mob type>
/remove <radius>
Instead, we can combine all of these into one by using the EntitySelectorArgument
. We want to be able to target multiple entities at a time, so we want to use the EntitySelector.MANY_ENTITIES
value in our constructor. We can simply retrieve the Collection<Entity>
from this argument and iteratively remove each entity:
new CommandAPICommand("remove")
//Using a collective entity selector to select multiple entities
.withArguments(new EntitySelectorArgument("entities", EntitySelector.MANY_ENTITIES))
.executes((sender, args) -> {
//Parse the argument as a collection of entities (as stated above in the documentation)
@SuppressWarnings("unchecked")
Collection<Entity> entities = (Collection<Entity>) args[0];
sender.sendMessage("Removed " + entities.size() + " entities");
for(Entity e : entities) {
e.remove();
}
})
.register();
We could then use this to target specific entities, for example:
- To remove all cows:
/remove @e[type=cow]
- To remove the 10 furthest pigs from the command sender:
/remove @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).(Of course, if anyone is able to confirm any major differences between the
PlayerArgument
and theEntitySelectorArgument(EntitySelector.ONE_PLAYER)
, I would be more than happy to include your findings in the documentation. If so, feel free to make a documentation amendment here.)
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
Say we want a command to spawn a specific type of entity, similar to the /summon
command in Vanilla Minecraft, with the addition of specifying how many entities to spawn. We want to create a command of the following form:
/spawnmob <entity> <amount>
Since we're trying to specify an entity type, we will use the EntityTypeArgument
as our argument type for <entity>
. We combine this with the IntegerArgument
class with a specified range of \( 1 \le \textit{amount} \le 100 \):
new CommandAPICommand("spawnmob")
.withArguments(new EntityTypeArgument("entity"))
.withArguments(new IntegerArgument("amount", 1, 100)) //Prevent spawning too many entities
.executesPlayer((Player player, Object[] args) -> {
for(int i = 0; i < (int) args[1]; i++) {
player.getWorld().spawnEntity(player.getLocation(), (EntityType) args[0]);
}
})
.register();
Note how in this example above, we have to explicitly state Player player, Object[] args
. This is due to a limitation of Java's type inference system which is discussed here.
Scoreboard arguments
The scoreboard arguments that the CommandAPI provides allows you to interact with various scoreboard elements, such as objectives, teams and score holders. Since commands are registered in the onEnable()
or onLoad()
method of a plugin, this means that these are registered before Bukkit's main scoreboard is loaded.
This means that calling Bukkit.getScoreboardManager().getMainScoreboard()
will always result in a NullPointerException
. To avoid this scenario, try using a lambda which delays the call that gets Bukkit's main scoreboard to the moment when the command is executed, which typically occurs after the server has initialized it. For example, if you wanted to populate a TeamArgument
with a list of all registered teams on the server, you should use the following:
List<Argument> arguments = new ArrayList<>();
arguments.add(new TeamArgument("team").safeOverrideSuggestions(s ->
Bukkit.getScoreboardManager().getMainScoreboard().getTeams().toArray(new Team[0]))
);
Scoreboard arguments
The CommandAPI uses two classes to provide information about a scoreboard:
- The
ScoreHolderArgument
class represents score holder - a player's name or an entity's UUID that has scores in an objective. This is described in more detail on the Minecraft Wiki. - The
ScoreboardSlotArgument
class represents a display slot (sidebar, list or belowName) as well as the team color if the display is the sidebar. This is described in more detail on the Minecraft Wiki.
Score holder argument
The score holder argument can accept either a single entity or a collection of multiple entities. In order to specify which one to use, you must provide a ScoreHolderType
enum value to the ScoreHolderArgument
constructor, which is either ScoreHolderType.SINGLE
or ScoreHolderType.MULTIPLE
:
new ScoreHolderArgument(nodeName, ScoreHolderType.SINGLE);
new ScoreHolderArgument(nodeName, ScoreHolderType.MULTIPLE);
Depending on which constructor is used, the cast type changes. If you use a ScoreHolderType.SINGLE
, the argument must be casted to a String
. Otherwise, if you use ScoreHolderType.MULTIPLE
, the argument must be casted to a Collection<String>
.
Example - Rewarding players with scoreboard objectives
Say we want to reward all players that fit a certain criteria. We want a command with the following structure:
/reward <players>
Since we could have multiple players that fit a certain criterion, we want to use ScoreHolderType.MULTIPLE
as the parameter for the argument's constructor.
To give this example a bit more context, let's say we want to reward all players that have died less than 10 times in the server. To do this, we will use the following command:
/reward @e[type=player,scores={deaths=..9}]
Note how we use ..9
to represent 9 or less deaths (since ranges are inclusive). Also note how we restrict our input to players via the command using type=player
. We can now implement our command:
new CommandAPICommand("reward")
//We want multiple players, so we use ScoreHolderType.MULTIPLE in the constructor
.withArguments(new ScoreHolderArgument("players", ScoreHolderType.MULTIPLE))
.executes((sender, args) -> {
//Get player names by casting to Collection<String>
@SuppressWarnings("unchecked")
Collection<String> players = (Collection<String>) args[0];
for(String playerName : players) {
Bukkit.getPlayer(playerName).getInventory().addItem(new ItemStack(Material.DIAMOND, 3));
}
})
.register();
Developer's Note:
In the example above, we have our user use the
@e[type=player]
entity selector to restrict theCollection<String>
so it only returns player names, which allows us to useBukkit.getPlayer(playerName)
. In practice, we cannot guarantee that such a selector will be used, so we could update the code to accept both entities and players. For example, we can differentiate between players and entities by using theUUID.fromString(String)
method:Collection<String> entitiesAndPlayers = (Collection<String>) args[0]; for(String str : entitiesAndPlayers) { try { UUID uuid = UUID.fromString(str); //Is a UUID, so it must by an entity Bukkit.getEntity(uuid); } catch(IllegalArgumentException exception) { //Not a UUID, so it must be a player name Bukkit.getPlayer(str); } }
Scoreboard slot argument
The ScoreboardSlotArgument
represents where scoreboard information is displayed. Since the Bukkit scoreboard DisplaySlot
is not able to represent the case where team colors are provided, the CommandAPI uses the ScoreboardSlot
wrapper class as the representation of the ScoreboardSlotArgument
.
ScoreboardSlot
wrapper
The ScoreboardSlot
wrapper class has 3 methods:
class ScoreboardSlot {
public DisplaySlot getDisplaySlot();
public ChatColor getTeamColor();
public boolean hasTeamColor();
}
The getDisplaySlot()
method returns the display slot that was chosen. If the display slot is DisplaySlot.SIDEBAR
and hasTeamColor()
returns true, then it is possible to use getTeamColor()
to get the team color provided.
Example - Clearing objectives in a scoreboard slot
Say we want to clear all objectives in a specific scoreboard slot. In this example, we will use the main server scoreboard, which is accessed using Bukkit.getScoreboardManager.getMainScoreboard()
. We want a command with the following structure:
/clearobjectives <slot>
We implement this simply by using the ScoreboardSlotArgument
as our argument, and then we can clear the slot using the scoreboard clearSlot(DisplaySlot)
method.
new CommandAPICommand("clearobjectives")
.withArguments(new ScoreboardSlotArgument("slot"))
.executes((sender, args) -> {
Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
DisplaySlot slot = ((ScoreboardSlot) args[0]).getDisplaySlot();
scoreboard.clearSlot(slot);
})
.register();
Objective arguments
In the CommandAPI, objectives are split into two classes:
- The
ObjectiveArgument
class, which represents objectives as a whole - The
ObjectiveCriteriaArgument
class, which represents objective criteria
Objective argument
The objective argument refers to a single scoreboard objective. Unconventionally, the ObjectiveArgument
must be cast to String
due to implementation limitations.
Developer's Note:
The two classes
ObjectiveArgument
andTeamArgument
must both be cast toString
, as opposed toObjective
andTeam
respectively. This is due to the fact that commands are typically registered in theonLoad()
method during a plugin's initialization. At this point in the server start-up sequence, the main server scoreboard is not initialized, so it cannot be used.
Example - Move objective to sidebar
As an example, let's create a command to move an objective to a player's sidebar. To do this, we will use the following command structure:
/sidebar <objective>
Given that an objective has to be casted to a String, we have to find a way to convert it from its name to a Bukkit Objective
object. We can do that by using the getObjective(String)
method from a Bukkit Scoreboard
:
new CommandAPICommand("sidebar")
.withArguments(new ObjectiveArgument("objective"))
.executes((sender, args) -> {
//The ObjectArgument must be casted to a String
String objectiveName = (String) args[0];
//An objective name can be turned into an Objective using getObjective(String)
Objective objective = Bukkit.getScoreboardManager().getMainScoreboard().getObjective(objectiveName);
//Set display slot
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
})
.register();
Objective criteria argument
The ObjectiveCriteriaArgument
is fairly straight forward - it represents the criteria for an objective. Similar to Bukkit, the objective criteria is simply represented as a String
, so it must be casted to a String
when being used.
Example - Unregister all objectives by criteria
Say we wanted to create a command to unregister all objectives based on a given criteria. Let's create a command with the following form:
/unregisterall <objective critera>
To do this, we're going to take advantage of Bukkit's Scoreboard.getObjectivesByCriteria(String)
method
new CommandAPICommand("unregisterall")
.withArguments(new ObjectiveCriteriaArgument("objective criteria"))
.executes((sender, args) -> {
String objectiveCriteria = (String) args[0];
Set<Objective> objectives = Bukkit.getScoreboardManager().getMainScoreboard().getObjectivesByCriteria(objectiveCriteria);
//Unregister the objectives
for(Objective objective : objectives) {
objective.unregister();
}
})
.register();
Team arguments
The TeamArgument
class interacts with the Minecraft scoreboard and represents a team. Similar to the ObjectiveArgument
class, the TeamArgument
class must be casted to a String.
Example - Toggling friendly fire in a team
Let's say we want to create a command to toggle the state of friendly fire in a team. We want a command of the following form
/togglepvp <team>
To do this, given a team we want to use the setAllowFriendlyFire(boolean)
function. As with the ObjectiveArgument
, we must convert the String
into a Team
object.
new CommandAPICommand("togglepvp")
.withArguments(new TeamArgument("team"))
.executes((sender, args) -> {
//The TeamArgument must be casted to a String
String teamName = (String) args[0];
//A team name can be turned into a Team using getTeam(String)
Team team = Bukkit.getScoreboardManager().getMainScoreboard().getTeam(teamName);
//Toggle pvp
team.setAllowFriendlyFire(team.allowFriendlyFire());
})
.register();
Miscellaneous arguments
Angle arguments
The angle argument is used to represent the yaw (horizontal) angle in degrees. The value returned from this argument range from -180.0 (inclusive) to 180 (exclusive), with -180.0 being due north:
\begin{align} -1&80.0 \\ &\hspace{0.1em}N \\ &\uparrow \\ 90.0\ W \leftarrow &\hspace{0.75em}\rightarrow E\ -90.0 \\ &\downarrow \\ &\hspace{0.2em}S \\ &0.0 \\ \end{align}
The ~
notation can be used to specify a rotation relative to the executor's yaw angle.
Note:
The AngleArgument
is only supported in Minecraft versions 1.16.2 and later, meaning it will not work on Minecraft versions 1.16 or 1.16.1. This is due to the fact that Minecraft added the time argument in 1.16.2. Attempting to use the AngleArgument
on an incompatible version will throw aa AngleArgumentException
.
Advancement arguments
The AdvancementArgument
class represents in-game advancements. As expected, the AdvancementArgument
can be casted to Bukkit's Advancement
class.
Example - Awarding a player an advancement
Say we want to award a player an advancement. First, we need the structure of our command:
/award <player> <advancement>
Since we require a player, we will use the PlayerArgument
for this example. Given a player, we can simply get the AdvancementProgress
for that player, and then award the criteria required to fully complete the provided advancement.
new CommandAPICommand("award")
.withArguments(new PlayerArgument("player"))
.withArguments(new AdvancementArgument("advancement"))
.executes((sender, args) -> {
Player target = (Player) args[0];
Advancement advancement = (Advancement) args[1];
//Award all criteria for the advancement
AdvancementProgress progress = target.getAdvancementProgress(advancement);
for(String criteria : advancement.getCriteria()) {
progress.awardCriteria(criteria);
}
})
.register();
Biome arguments
In Minecraft 1.16, they added the ability to refer to in-game biomes. The CommandAPI implements this using the BiomeArgument
. As expected, this returns Bukkit's Biome
enum when used.
Note:
The BiomeArgument
is only supported in Minecraft versions 1.16 and later. Attempting to use the BiomeArgument
on an incompatible version of Minecraft will throw a BiomeArgumentException
.
Example - Setting the biome of a chunk
Say you want to set the biome of the current chunk that a player is in. We can do this using the World.setBiome(x, y, z, biome)
method for a given world. We will use this command structure to set the biome of our current chunk:
/setbiome <biome>
And we can set the biome of the current chunk as expected:
new CommandAPICommand("setbiome")
.withArguments(new BiomeArgument("biome"))
.executesPlayer((player, args) -> {
Biome biome = (Biome) args[0];
Chunk chunk = player.getLocation().getChunk();
player.getWorld().setBiome(chunk.getX(), player.getLocation().getBlockY(), chunk.getZ(), biome);
})
.register();
BlockState arguments
The BlockStateArgument
is used to represent data about blocks in the world. These refer to any blocks that have data or states, such as dispensers, signs, doors and pistons. The BlockStateArgument
creates a Bukkit BlockData
object when used.
Developer's Note:
Make sure to not confuse the cast type with
BlockState
. The naming of this argument refers to the internal Minecraft vanilla argument naming convention - this argument casts toBlockData
and NOTBlockState
.
Example - Setting a block
Say we want a simple command to set the block that you're looking at. We'll use the following command structure:
/set <block>
And then we can simply set our block using setBlockData()
:
new CommandAPICommand("set")
.withArguments(new BlockStateArgument("block"))
.executesPlayer((player, args) -> {
BlockData blockdata = (BlockData) args[0];
Block targetBlock = player.getTargetBlockExact(256);
// Set the block, along with its data
targetBlock.setType(blockdata.getMaterial());
targetBlock.getState().setBlockData(blockdata);
})
.register();
Enchantment argument
The EnchantmentArgument
class lets users input a specific enchantment. As you would expect, the cast type is Bukkit's Enchantment
class.
Example - Giving a player an enchantment on their current item
Say we want to give a player an enchantment on the item that the player is currently holding. We will use the following command structure:
/enchantitem <enchantment> <level>
Since most enchantment levels range between 1 and 5, we will also make use of the IntegerArgument
to restrict the level of the enchantment by usng its range constructor.
new CommandAPICommand("enchantitem")
.withArguments(new EnchantmentArgument("enchantment"))
.withArguments(new IntegerArgument("level", 1, 5))
.executesPlayer((player, args) -> {
Enchantment enchantment = (Enchantment) args[0];
int level = (int) args[1];
//Add the enchantment
player.getInventory().getItemInMainHand().addEnchantment(enchantment, level);
})
.register();
Environment arguments
The EnvironmentArgument
class allows a command sender to refer to a specific world environment, declared in Bukkit's World.Environment
class. This includes the following three environments: NORMAL
, NETHER
and THE_END
.
Note:
The EnvironmentArgument
is only supported in Minecraft versions 1.13.1 and later, meaning it will not work on Minecraft 1.13. This is due to fact that Minecraft added the environment argument in 1.13.1. Attempting to use the EnvironmentArgument
on Minecraft 1.13 will throw an EnvironmentArgumentException
.
Example - Creating a new world
Say we want to create a new world on our Minecraft server. To do this, we need to know the name of the world, and the type (i.e. overworld, nether or the end). As such, we want to create a command with the following structure:
/createworld <worldname> <type>
Using the world name and the environment of the world, we can use Bukkit's WorldCreator
to create a new world that matches our provided specifications:
new CommandAPICommand("createworld")
.withArguments(new StringArgument("worldname"))
.withArguments(new EnvironmentArgument("type"))
.executes((sender, args) -> {
String worldName = (String) args[0];
Environment environment = (Environment) args[1];
// Create a new world with the specific world name and environment
Bukkit.getServer().createWorld(new WorldCreator(worldName).environment(environment));
sender.sendMessage("World created!");
})
.register();
Itemstack arguments
The ItemStackArgument
class represents in-game items. As expected, this should be casted to Bukkit's ItemStack
object.
Example - Giving a player an itemstack
Say we want to create a command that gives you items. For this command, we will use the following structure:
/item <itemstack>
With this structure, we can easily create our command:
new CommandAPICommand("item")
.withArguments(new ItemStackArgument("itemstack"))
.executesPlayer((player, args) -> {
player.getInventory().addItem((ItemStack) args[0]);
})
.register();
LootTable argument
The LootTableArgument
class can be used to get a Bukkit LootTable
object.
Example - Filling an inventory with loot table contents
new CommandAPICommand("giveloottable")
.withArguments(new LootTableArgument("loottable"))
.executesPlayer((player, args) -> {
LootTable lootTable = (LootTable) args[0];
/* Some generated LootContext relating to the lootTable*/
LootContext context = new LootContext.Builder(player.getLocation()).build();
lootTable.fillInventory(player.getInventory(), new Random(), context);
})
.register();
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.
MathOperation arguments
The CommandAPI's MathOperationArgument
is used to represent the Minecraft scoreboard arithmetic operation to alter scoreboard scores. Since there is no default representation in the Bukkit API, the CommandAPI provides the MathOperation
class to represent each operation:
Symbol (in Minecraft) | MathOperation enum value |
---|---|
\(+=\) | MathOperation.ADD |
\(-=\) | MathOperation.SUBTRACT |
\(*=\) | MathOperation.MULTIPLY |
\(/=\) | MathOperation.DIVIDE |
\(\%=\) | MathOperation.MOD |
\(=\) | MathOperation.ASSIGN |
\(<\) | MathOperation.MIN |
\(>\) | MathOperation.MAX |
\(><\) | MathOperation.SWAP |
The MathOperation
also has two methods:
public int apply(int val1, int val2);
public float apply(float val1, float val2);
These methods are used to provide a basic implementation of these math operations on a given input. Given the values val1
and val2
, these are the operation that the apply(val1, val2)
method performs:
MathOperation enum value | Result |
---|---|
MathOperation.ADD | val1 + val2 |
MathOperation.SUBTRACT | val1 - val2 |
MathOperation.MULTIPLY | val1 * val2 |
MathOperation.DIVIDE | val1 / val2 |
MathOperation.MOD | val1 % val2 |
MathOperation.ASSIGN | val2 |
MathOperation.MIN | Math.min(val1, val2) |
MathOperation.MAX | Math.max(val1, val2) |
MathOperation.SWAP | val2 |
Example - Changing a player's level
Say we wanted to create a player's level. Typically, this is implemented in the following manner:
/xp set <player> <level>
/xp add <player> <levels>
Using the MathOperationArgument
, we can extend the functionality of adding and setting a player's level by allowing the user to choose what operation they desire. To do this, we'll use the following structure:
/changelevel <player> <operation> <value>
As with any command, we declare our arguments, cast them properly and then we write our main code. In this example, we use the apply(int, int)
method from our MathOperation
to calculate the player's new level.
new CommandAPICommand("changelevel")
.withArguments(new PlayerArgument("player"))
.withArguments(new MathOperationArgument("operation"))
.withArguments(new IntegerArgument("value"))
.executes((sender, args) -> {
Player target = (Player) args[0];
MathOperation op = (MathOperation) args[1];
int value = (int) args[2];
target.setLevel(op.apply(target.getLevel(), value));
})
.register();
There are various applications for the changelevel
command based on what the user inputs. For example:
-
To set the player Notch to level 10:
/changelevel Notch = 10
-
To double the player Notch's level:
/changelevel Notch *= 2
-
To set the player Notch's level to 20, or keep it as their current level if it is higher than 20:
/changelevel Notch > 20
Particle arguments
The ParticleArgument
class represents Minecraft particles. As expected, this is casted to the Bukkit Particle
class.
Example - Show particles at a player's location
Say we wanted to have a command that displayed particles at a player's location. We will use the following command structure:
/showparticle <particle>
With this, we can simply spawn the particle using the World.spawnParticle(Particle, Location, int)
method:
new CommandAPICommand("showparticle")
.withArguments(new ParticleArgument("particle"))
.executesPlayer((player, args) -> {
player.getWorld().spawnParticle((Particle) args[0], player.getLocation(), 1);
})
.register();
Potion effect arguments
The PotionEffectArgument
class represents Minecraft potion effects. When used, this argument is casted to Bukkit's PotionEffectType
class.
Example - Giving a player a potion effect
Say we wanted to have a command that gives a player a potion effect. For this command, we'll use the following structure:
/potion <target> <potion> <duration> <strength>
In this example, we utilize some of the other arguments that we've described earlier, such as the PlayerArgument
and TimeArgument
. Since duration for the PotionEffect
constructor is in ticks, this is perfectly fit for the TimeArgument
, which is represented in ticks.
new CommandAPICommand("potion")
.withArguments(new PlayerArgument("target"))
.withArguments(new PotionEffectArgument("potion"))
.withArguments(new TimeArgument("duration"))
.withArguments(new IntegerArgument("strength"))
.executes((sender, args) -> {
Player target = (Player) args[0];
PotionEffectType potion = (PotionEffectType) args[1];
int duration = (int) args[2];
int strength = (int) args[3];
//Add the potion effect to the target player
target.addPotionEffect(new PotionEffect(potion, duration, strength));
})
.register();
Recipe arguments
The RecipeArgument
class lets you retrieve Bukkit's Recipe
object. Unlike the other arguments, this argument has a slightly different implementation for Minecraft 1.15+.
Developer's Note:
In Minecraft 1.15 and onwards, Bukkit now has the
ComplexRecipe
class. This class is a subclass of the typicalRecipe
class, so casting still works when casting toRecipe
. The only major difference is theComplexRecipe
class also inherits theKeyed
interface, allowing you to access itsNamespacedKey
. This is used to grant the recipe to players. (This is shown in the second example below).
Example - Giving a player the result of a recipe
Say we want to give youself the result of a specific recipe. Since Bukkit's Recipe
class contains the getResult()
method, we will use that in our example. We want to create the following command:
/giverecipe <recipe>
As such, we easily implement it by specifying the RecipeArgument
, casting it and adding it to the player's inventory:
new CommandAPICommand("giverecipe")
.withArguments(new RecipeArgument("recipe"))
.executesPlayer((player, args) -> {
Recipe recipe = (Recipe) args[0];
player.getInventory().addItem(recipe.getResult());
})
.register();
Example - Unlocking a recipe for a player (1.15+ only)
Since 1.15 has the ComplexRecipe
class, we will take advantage of this to unlock a recipe for a player. For this command, we'll use the following structure:
/unlockrecipe <player> <recipe>
Since the discoverRecipe(NamespacedKey)
method for a player requires a NamespacedKey
, we have to ensure that this recipe is of type ComplexRecipe
. This is simply done by using instanceof
. If this is the case, then we can unlock the recipe for the player. Otherwise, we cannot, so we fail gracefully using the CommandAPI.fail(String)
method;
new CommandAPICommand("unlockrecipe")
.withArguments(new PlayerArgument("player"))
.withArguments(new RecipeArgument("recipe"))
.executes((sender, args) -> {
Player target = (Player) args[0];
Recipe recipe = (Recipe) args[1];
//Check if we're running 1.15+
if(recipe instanceof ComplexRecipe) {
ComplexRecipe complexRecipe = (ComplexRecipe) recipe;
target.discoverRecipe(complexRecipe.getKey());
} else {
//Error here, can't unlock recipe for player
CommandAPI.fail("Cannot unlock recipe for player (Are you using version 1.15 or above?)");
}
})
.register();
Sound arguments
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
Say we want a simple command that plays a specific sound at your location. To do this, we will make the following command:
/sound <sound>
This command simply plays the provided sound to the current player:
new CommandAPICommand("sound")
.withArguments(new SoundArgument("sound"))
.executesPlayer((player, args) -> {
player.getWorld().playSound(player.getLocation(), (Sound) args[0], 100.0f, 1.0f);
})
.register();
Time arguments
The TimeArgument
class represents in-game time, in the number of in-game ticks. This allows command senders to specify a certain number of ticks in a simpler way, by including the characters d
to specify the numbers of days, s
to specify the number of seconds or t
to specify a number of ticks.
The CommandAPI converts the inputs provided by the command sender into a number of ticks as an integer.
Note:
The TimeArgument
is only supported in Minecraft versions 1.14 and later, meaning it will not work on Minecraft versions 1.13, 1.13.1 or 1.13.2. This is due to the fact that Minecraft added the time argument in 1.14. Attempting to use the TimeArgument
on an incompatible version will throw a TimeArgumentException
.
Developer's Note:
The
TimeArgument
provides inputs such as2d
(2 in-game days),10s
(10 seconds) and20t
(20 ticks), but does not let you combine them, such as2d10s
.
Example - Displaying a server-wide announcement
Say we have a command bigmsg
that displays a title message to all players for a certain duration:
/bigmsg <duration> <message>
new CommandAPICommand("bigmsg")
.withArguments(new TimeArgument("duration"))
.withArguments(new GreedyStringArgument("message"))
.executes((sender, args) -> {
//Duration in ticks
int duration = (int) args[0];
String message = (String) args[1];
for(Player player : Bukkit.getOnlinePlayers()) {
//Display the message to all players, with the default fade in/out times (10 and 20).
player.sendTitle(message, "", 10, duration, 20);
}
})
.register();
UUID arguments
The UUID argument is used to uniquely identify players, entities and attribute modifiers. As a result, its cast type is Java's UUID
.
Note:
The UUIDArgument is only compatible with Minecraft version 1.16 and above, since that is the version this argument was introduced. Attempting to use the UUID argument on a version lower than Minecraft 1.16 will throw a UUIDArgumentException
.
Predicate arguments
Block predicate arguments
The BlockPredicateArgument
is used to represent a "test" for Minecraft blocks. This can consist of tags, such as the ones listed here on the MinecraftWiki, or individual blocks. If a block matches the tag or block, then the predicate is satisfied.
For example, if we were to use the predicate #leaves
, then the following blocks will be satisfied by that predicate: jungle_leaves
, oak_leaves
, spruce_leaves
, dark_oak_leaves
, acacia_leaves
, birch_leaves
.
When used, this argument must be casted to a Predicate<Block>
. As with other similar arguments with parameterized types, you can ignore Java's unchecked cast type safety warning.
Example - Replacing specific blocks in a radius
Say you want to replace blocks in a given radius. To do this, we'll use the following command structure:
/replace <radius> <fromBlock> <toBlock>
Of course, we could simply use a BlockStateArgument
or even an ItemStackArgument
as our <fromBlock>
in order to get the material to replace, but the block predicate argument provides a test for a given block, which if satisfied, allows certain code to be executed.
First, we declare our arguments. We want to use the BlockPredicateArgument
since it also allows us to use Minecraft tags to identify blocks, as well as individual blocks. We then use BlockStateArgument
to set the block to a given type. The BlockStateArgument
also allows the user to provide any block data (e.g. contents of a chest or a stair's orientation).
Argument[] arguments = new Argument[] {
new IntegerArgument("radius"),
new BlockPredicateArgument("fromBlock"),
new BlockStateArgument("toBlock"),
};
We then register our /replace
command. First, we parse the arguments making sure to cast to Predicate<Block>
and BlockData
(and not BlockState
). After that, we use a few simple for loops to find the blocks within a radius sphere from the player.
In our most nested loop, we can then check if the block meets the requirements of our predicate. This is simply performed using predicate.test(block)
, and if satisfied, we can set the block's type.
Lastly, we register our command as normal using the register()
method.
new CommandAPICommand("replace")
.withArguments(arguments)
.executesPlayer((player, args) -> {
// Parse the arguments
int radius = (int) args[0];
@SuppressWarnings("unchecked")
Predicate<Block> predicate = (Predicate<Block>) args[1];
BlockData blockData = (BlockData) args[2];
// Find a (solid) sphere of blocks around the player with a given radius
Location center = player.getLocation();
for (int Y = -radius; Y < radius; Y++) {
for (int X = -radius; X < radius; X++) {
for (int Z = -radius; Z < radius; Z++) {
if (Math.sqrt((X * X) + (Y * Y) + (Z * Z)) <= radius) {
Block block = center.getWorld().getBlockAt(X + center.getBlockX(), Y + center.getBlockY(), Z + center.getBlockZ());
// If that block matches a block from the predicate, set it
if(predicate.test(block)) {
block.setType(blockData.getMaterial());
block.setBlockData(blockData);
}
}
}
}
}
return;
})
.register();
ItemStack predicate arguments
Similar to the BlockPredicateArgument
, the ItemStackPredicateArgument
is a way of performing predicate checks on ItemStack
objects. These can represent tags, such as the ones declared here on the MinecraftWiki, or individual items. The cast type for this argument is Predicate<ItemStack>
.
Example - Removing items in inventories based on predicates
Say we wanted to remove items in your inventory (I know, the /clear
command does this, but this is the only example I could come up with). To do this, we'll use the following command structure:
/rem <item>
We implement this with a simple for loop over the player's inventory and remove items that satisfy the predicate.
// Register our command
new CommandAPICommand("rem")
.withArguments(new ItemStackPredicateArgument("items"))
.executesPlayer((player, args) -> {
// Get our predicate
@SuppressWarnings("unchecked")
Predicate<ItemStack> predicate = (Predicate<ItemStack>) args[0];
for(ItemStack item : player.getInventory()) {
if(predicate.test(item)) {
player.getInventory().remove(item);
}
}
})
.register();
NBT arguments
The CommandAPI includes support for NBT compounds by using the NBT API by tr7zw. To use NBT, use the NBTCompoundArgument
and simply cast the argument to an NBTContainer
.
Since this argument depends on the NBT API, if this is used and the NBT API is not available on the server, an NBTAPINotFoundException
will be thrown.
Example - ???
new CommandAPICommand("award")
.withArguments(new NBTCompoundArgument("nbt"))
.executes((sender, args) -> {
NBTContainer nbt = (NBTContainer) args[0];
//Do something with "nbt" here...
})
.register();
Developer's Note:
I haven't personally explored much with using this argument, so this example isn't great. If you believe you can supply a suitable example for this page, feel free to send an example on the CommandAPI issues page.
Literal arguments
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.
Developer's Note:
There is a simpler alternative to the LiteralArgument
class! Instead of having to deal with arguments not being present in the array of arguments, consider using the much more intuitive MultiLiteralArgument
, which is described in more detail here!
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, literal arguments are unlisted by default. In other words, the literal argument is not present in the args[]
for the command declaration.
Example - Literal arguments and regular arguments
To illustrate the behavior of literal arguments, we create a command of the following form:
/mycommand <literal> <text>
As an example, let's declare the literal "hello" as a valid literal for this command. When we retrieve the result from args[0]
, it returns the value of the TextArgument
, as opposed to the literal "hello":
new CommandAPICommand("mycommand")
.withArguments(new LiteralArgument("hello"))
.withArguments(new TextArgument("text"))
.executes((sender, args) -> {
// This gives the variable "text" the contents of the TextArgument, and not the literal "hello"
String text = (String) args[0];
})
.register();
If I were to run the following command:
/mycommand hello goodbye
The value of text
in the code above would be "goodbye".
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()) {
//Register the command as usual
new CommandAPICommand("changegamemode")
.withArguments(new LiteralArgument(key))
.executesPlayer((player, args) -> {
//Retrieve the object from the map via the key and NOT the args[]
player.setGameMode(gamemodes.get(key));
})
.register();
}
Note how, since we don't have access to the literal from args
, we must access the provided gamemode from elsewhere.
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.
Multi literal arguments
So far, we've described normal arguments and literal arguments. We've described the nuances with literal arguments and how they're not really "arguments", so they don't appear in the args[]
for commands.
Now forget all of that. Multi literal arguments are the same as literal arguments but they do appear in the args[]
for commands (i.e. they are listed). Multi literal arguments are just a way better alternative to literal arguments. The multi literal argument constructor allows you to provide a String[]
of possible values which you can use for your command declaration.
The multi literal argument has all of the same benefits of a regular literal argument - they are hardcoded options that the user must enter - they don't allow other values.
Developer's Note:
The only reason that
LiteralArgument
still exists is for legacy purposes.MultiLiteralArgument
is much more recommended because it's easier to understand and implement. TheLiteralArgument
has a very slight performance improvement over theMultiLiteralArgument
, but it's basically unnoticeable.
Example - Using multi literals to make the gamemode command
In this example, we'll show how to use multi literals to declare Minecraft's /gamemode
command. As you can see from the example code below, the argument declaration and command declaration is the same as if you were declaring any normal argument or command.
new CommandAPICommand("gamemode")
.withArguments(new MultiLiteralArgument("adventure", "creative", "spectator", "survival"))
.executesPlayer((player, args) -> {
// The literal string that the player enters IS available in the args[]
switch((String) args[0]) {
case "adventure":
player.setGameMode(GameMode.ADVENTURE);
break;
case "creative":
player.setGameMode(GameMode.CREATIVE);
break;
case "spectator":
player.setGameMode(GameMode.SPECTATOR);
break;
case "survival":
player.setGameMode(GameMode.SURVIVAL);
break;
}
})
.register();
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
). They basically represent StringArgument
with overrideable suggestions and a built-in parser for any object of your choice. They are designed to be used for multiple commands - you define the argument once and can use it wherever you want when declaring commands.
The CustomArgument<T>
has two constructors, declared as follows:
public CustomArgument(String nodeName, CustomArgumentFunction<T> parser);
public CustomArgument(String nodeName, CustomArgumentFunction<T> parser, boolean keyed);
The second argument is the CustomArgumentFunction
, which is a lambda that takes in a String and returns some custom object of type T
. The first constructor will construct a CustomArgument
which uses the StringArgument
as a base (thus, only simple strings). The second argument has the field keyed
. When this field is set to true
, the CustomArgument
will use a Minecraft key
as a base, allowing you to use Minecraft keys as input.
Developer's Note:
I may have complicated this too much, so let me clarify what I mean. The
CustomArgument
constructor is of the following forms:CustomArgument(nodeName, (String) -> { ... return T; }); CustomArgument(nodeName, (String) -> { ... return T; }, boolean keyed);
Both constructors take in a String as input and return
T
. When enablingkeyed
, it allows the input to be of the form of a Minecraft key, but doesn't change the input type.
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 it to a Player
object.
Example - World argument
Say we want to create an argument to represents the list of available worlds on the server. We basically want to have an argument which always returns a Bukkit World
object as the result. Here, we create a method worldArgument()
that returns our custom argument that returns a World
. First, we retrieve our String[]
of world names to be used for our suggestions. We then write our custom argument that creates a World
object from the input (in this case, we simply convert the String to a World
using Bukkit.getWorld(String)
). We perform error handling before returning our result:
//Function that returns our custom argument
public Argument worldArgument(String nodeName) {
//Construct our CustomArgument that takes in a String input and returns a World object
return new CustomArgument<World>(nodeName, (input) -> {
//Parse the world from our input
World world = Bukkit.getWorld(input);
if(world == null) {
throw new CustomArgumentException(new MessageBuilder("Unknown world: ").appendArgInput());
} else {
return world;
}
}).overrideSuggestions(sender -> {
//List of worlds on the server, as Strings. We use overrideSuggestions(sender -> ...)
//since this evaluates the list of worlds when the player types the command as opposed
//to when the plugin starts up
return Bukkit.getWorlds().stream().map(World::getName).toArray(String[]::new);
});
}
In our error handling step, we check if the world is equal to null (since the Bukkit.getWorld(String)
is @Nullable
). To handle this case, we throw a CustomArgumentException
with an error from a MessageBuilder
. The CustomArgumentException
has two constructors, so a message builder isn't required each time:
new CustomArgumentException(String message);
new CustomArgumentException(MessageBuilder message);
We can use our custom argument like any other argument. Say we wanted to write a command to teleport to a specific world. We will create a command of the following structure:
/tpworld <world>
Since we have defined the method worldArgument()
which automatically generates our argument, we can use it as follows:
new CommandAPICommand("tpworld")
.withArguments(worldArgument("world"))
.executesPlayer((player, args) -> {
player.teleport(((World) args[0]).getSpawnLocation());
})
.register();
By using a CustomArgument
(as opposed to a simple StringArgument
and overriding its suggestions), we are able to provide a much more powerful form of error handling (automatically handled inside the argument), and we can reuse this argument for other commands.
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 an object to the end of the builder |
Example - Message builder for invalid objective argument
To create a MessageBuilder
, simply call its constructor and use whatever methods as you see fit. Unlike a StringBuilder
, you don't have to "build" it when you're done - the CommandAPI does that automatically:
new MessageBuilder("Unknown world: /").appendFullInput().appendHere();
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.
Functions in 1.16+
Developer's Note:
Minecraft 1.16+ change the way that datapacks are loaded on the server, so that they load before plugins are enabled. This means that non-vanilla commands that are declared in functions and tags will be detected as invalid, causing the server to throw a lot of errors at the very start.
The CommandAPI reloads datapacks once the server has finished loading using all declared commands, therefore the error messages at the start of the server can be ignored.
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
Say we have a command /killall
that simply kills all entities in all worlds on the server. If we were to register this in our onLoad()
method, this would allow us to use the /killall
command in Minecraft functions and tags.
public class Main extends JavaPlugin {
@Override
public void onLoad() {
//Commands which will be used in Minecraft functions are registered here
new CommandAPICommand("killall")
.executes((sender, args) -> {
//Kills all enemies in all worlds
Bukkit.getWorlds().forEach(w -> w.getLivingEntities().forEach(e -> e.setHealth(0)));
})
.register();
}
@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 6 - Functions & Tags), 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 SimpleFunctionWrapper class
To represent Minecraft functions and tags, the CommandAPI uses the SimpleFunctionWrapper
class. Simply put, this class represents one Minecraft function, which are defined in .mcfunction
files.
Developer's Note
The
SimpleFunctionWrapper
class only represents on Minecraft function. As a result, to represent a Minecraft "tag", which is a collection of Minecraft functions, the CommandAPI simply uses aSimpleFunctionWrapper[]
.
SimpleFunctionWrapper methods
The SimpleFunctionWrapper
class has the following methods:
class SimpleFunctionWrapper implements Keyed {
// Methods that creates SimpleFunctionWrapper instances
static SimpleFunctionWrapper getFunction(NamespacedKey key);
static SimpleFunctionWrapper[] getTag(NamespacedKey key);
// Methods that query the Minecraft server
static Set<NamespacedKey> getFunctions();
static Set<NamespacedKey> getTags();
// Methods for using the SimpleFunctionWrapper
int run(CommandSender sender);
// Utility functions
String[] getCommands();
NamespacedKey getKey();
}
getTag(NamespacedKey) and getFunction(NamespacedKey)
The getFunction(NamespacedKey)
function is used to get a function that has been declared in a datapack and is loaded on the server.
The getTag(NamespacedKey)
function is used to get a Tag that has been declared in a datapack and is loaded on the server. This returns a SimpleFunctionWrapper[]
, since a tag is simply an ordered collection of functions. When using this method, the #
symbol which is typically used at the start of the tag's name is not needed.
getFunctions() and getTags()
The methods getFunctions()
and getTags()
simply return a set of NamespacedKey
objects which are the names of functions or tags that have been declared by all datapacks on the server.
run(CommandSender)
This method simply runs the current SimpleFunctionWrapper
as the provided command sender. The method will return a numerical result value, stating whether it succeeds or returns a result. This is documented in more detail here and here. For example:
getCommands()
The getCommands()
method returns a String[]
that contains the list of commands that the Minecraft function "holds". In other words, running this Minecraft function is basically as simple as iterating through its commands and running them in order. The commands that this String[]
holds are the raw strings that this function represents - in other words, it can include things such as @p
and ~ ~ ~
instead of "filled in" values.
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.
The FunctionWrapper
class is an extension of the SimpleFunctionWrapper
class. It is basically a SimpleFunctionWrapper
, which has been constructed from an existing command sender when a command is used. This basically means that the command sender has already been "baked into" the FunctionWrapper
object, allowing you to run it without having to provide a command sender.
FunctionWrapper methods
The FunctionWrapper
class has the following methods:
class FunctionWrapper extends SimpleFunctionWrapper {
// Methods specific to this class
int run();
int runAs(Entity e);
// Methods inherited from SimpleFunctionWrapper
static SimpleFunctionWrapper getFunction(NamespacedKey key);
static SimpleFunctionWrapper[] getTag(NamespacedKey key);
static Set<NamespacedKey> getFunctions();
static Set<NamespacedKey> getTags();
int run(CommandSender sender);
String[] getCommands();
NamespacedKey getKey();
}
These methods allow you to interact with the Minecraft function that this class wraps.
run()
The run()
method basically does what it says on the tin - it runs the function. The command executor that runs this function is the command executor that was used to retrieve it. For example, if a player in-game populated this argument, then the player will be filled in for @p
and the player's location would be used for things such as ~ ~ ~
:
new CommandAPICommand("runfunc")
.withArguments(new FunctionArgument("function"))
.executes((sender, args) -> {
FunctionWrapper[] functions = (FunctionWrapper[]) args[0];
for(FunctionWrapper function : functions) {
function.run(); // The command executor in this case is 'sender'
}
})
.register();
runAs(Entity)
The runAs(Entity)
is basically the same as the run()
method, but it allows you to change the command executor to another entity.
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
Since it's a little difficult to demonstrate a custom use for the FunctionArgument
, we will show how you can implement Vanilla Minecraft's /function
command. In this example, we want a command that uses the following structure:
/runfunction <function>
When provided with a function, it will execute that function. If instead a tag is provided, it will execute that tag (i.e. execute all functions declared in that tag).
new CommandAPICommand("runfunction")
.withArguments(new FunctionArgument("function"))
.executes((sender, args) -> {
FunctionWrapper[] functions = (FunctionWrapper[]) args[0];
//Run all functions in our FunctionWrapper[]
for(FunctionWrapper function : functions) {
function.run();
}
})
.register();
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:
Permission | What it does |
---|---|
CommandPermission.OP | Requires OP to execute the command |
CommandPermission.NONE | Anyone can execute the command |
CommandPermission.fromString("my.permission") | Requires a specific permission node to execute the command |
In addition to the CommandPermission
class, there are two different ways to assign permissions (compared to the simple CommandSender.hasPermission()
method that is provided by Bukkit), by using the withPermission
method for arguments or for commands.
The withPermission
method can take two values:
- A
CommandPermission
, which represents a permission such asOP
orNONE
- A
String
, which will be converted automatically to aCommandPermission
usingCommandPermission.fromString()
Adding permissions to commands
To add a permission to a command, you can use the withPermission(CommandPermission)
or withPermission(String)
method when declaring a command.
Example - /god command with permissions
Say we created a command /god
that sets a player as being invulnerable. Since this is a pretty non-survival command, we want to restrict who can run this command. As such, we want our player to have the permission command.god
in order to run this command. To do this, we simply use the withPermission(CommandPermission)
method from our command builder:
// Register the /god command with the permission node "command.god"
new CommandAPICommand("god")
.withPermission(CommandPermission.fromString("command.god"))
.executesPlayer((player, args) -> {
player.setInvulnerable(true);
})
.register();
As stated above, it is possible to assign a permission using a String instead of using CommandPermission.fromString()
:
//Register the /god command with the permission node "command.god", without creating a CommandPermission
new CommandAPICommand("god")
.withPermission("command.god")
.executesPlayer((player, args) -> {
player.setInvulnerable(true);
})
.register();
Adding 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
For example, say we're registering a command /kill
:
/kill - Kills yourself
/kill <target> - Kills a target player
We first declare the command as normal. Nothing fancy is going on here:
//Register the /god command with the permission node "command.god", without creating a CommandPermission
new CommandAPICommand("god")
.withPermission("command.god")
.executesPlayer((player, args) -> {
player.setInvulnerable(true);
})
.register();
Now we declare our command with arguments. We use a PlayerArgument
and apply the permission to the argument. After that, we register our command as normal:
// Adds the OP permission to the "target" argument. The sender requires OP to execute /kill <target>
new CommandAPICommand("kill")
.withArguments(new PlayerArgument("target").withPermission(CommandPermission.OP))
.executesPlayer((player, args) -> {
((Player) args[0]).setHealth(0);
})
.register();
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 as it has greater control over arguments.
Requirements
Requirements is a feature that allows you to put a constraint on commands and arguments. Similar to permissions, a requirement is something that must be fulfilled in order to use a given command or argument.
This section is broken up into four parts:
- Adding requirements to commands
- Adding requirements to arguments
- Updating requirements
- Multiple requirements
Please don't skip the section on updating requirements - the last section is necessary to get requirements to work as you'd want!
Adding requirements to commands
To add a requirement to a command, similar to adding permissions to commands, use the withRequirement
method:
CommandAPICommand withRequirement(Predicate<CommandSender> sender);
The withRequirement
method requires a predicate that determines if the sender is able to run the command - if the predicate is satisfied, then the command sender will be able to execute that command.
Example - Perks based on a player's level
Say we have a perks-based command system that depends on a player's level. For example, if a player has over 30 levels of experience, they would then be able to run a command that lets them repair the item in their hand in exchange for 30 levels. As such, we'll use the following command structure:
/repair
We want to put a requirement on this command that the player needs to have at least 30 levels of experience in order to run the command - if the player has less than 30 levels, the player should not be able to run the command. The easiest way to make the player not able to run the command is to literally tell the user that the command doesn't exist. That's what requirements do in the CommandAPI:
new CommandAPICommand("repair")
.withRequirement(sender -> ((Player) sender).getLevel() >= 30)
.executesPlayer((player, args) -> {
//Repair the item back to full durability
ItemStack is = player.getInventory().getItemInMainHand();
ItemMeta itemMeta = is.getItemMeta();
if(itemMeta instanceof Damageable) {
((Damageable) itemMeta).setDamage(0);
is.setItemMeta(itemMeta);
}
//Subtract 30 levels
player.setLevel(player.getLevel() - 30);
})
.register();
It's important to note that in this example, we case the sender
to a player
for the requirement method. We know that the sender is definitely a player because we use executesPlayer()
, which ensures that this is the case. Now that we've got this, we need to make sure we update the player's requirements when their exp changes. This is covered in more detail in the section about updating requirements below.
Adding requirements to arguments
In a similar way that you can restrict certain arguments by adding permissions to them, you can restrict them by using arbitrary predicates by using the withRequirement
method on the arguments themselves.
Example - A party creation and teleportation system
Let's say that we're working on a plugin that has a system to form groups of players called "parties". If you are not already in a party, you can create one of your own and if you are in a party, you can teleport to any other member in your party.
For this example, we'll use the following command structure:
/party create <partyName>
/party tp <player>
To represent our party in code, we'll use a simple Map
called partyMembers
which maps the player's UUID to the name of their registered party:
Map<UUID, String> partyMembers = new HashMap<>();
To begin with, let's create the /party create <partyName>
command. First, we must declare our arguments:
List<Argument> arguments = new ArrayList<>();
// The "create" literal, with a requirement that a player must have a party
arguments.add(new LiteralArgument("create")
.withRequirement(sender -> {
return !partyMembers.containsKey(((Player) sender).getUniqueId());
}));
arguments.add(new StringArgument("partyName"));
In this argument declaration, we put a requirement on the literal create
, where the player does not have a party. In other words, if the player does not have a party, they are allowed to run /party create <partyName>
. If a player already has a party, then they won't be allowed to run this command.
Now that we've declared our arguments, we can now declare our main command /party create <partyName>
. We populate it with the arguments, and we create an entry in our partyMembers
with the player's UUID and the name of the party that they created. Since this updates the requirements of the player, we'll have to make sure we update it (which is covered in more detail in the section about updating requirements below) - until then, I'll omit this from the code:
new CommandAPICommand("party")
.withArguments(arguments)
.executesPlayer((player, args) -> {
//Get the name of the party to create
String partyName = (String) args[0];
partyMembers.put(player.getUniqueId(), partyName);
})
.register();
So now we've added the ability to create a party if we're not already in it. Now we need to implement our party tp <player>
command. Again, we must start by declaring our arguments:
arguments = new ArrayList<>();
arguments.add(new LiteralArgument("tp")
.withRequirement(sender -> {
return partyMembers.containsKey(((Player) sender).getUniqueId());
}));
arguments.add(new PlayerArgument("player")
.safeOverrideSuggestions((sender) -> {
//Store the list of party members to teleport to
List<Player> playersToTeleportTo = new ArrayList<>();
String partyName = partyMembers.get(((Player) sender).getUniqueId());
//Find the party members
for(UUID uuid : partyMembers.keySet()) {
//Ignore yourself
if(uuid.equals(((Player) sender).getUniqueId())) {
continue;
} else {
//If the party member is in the same party as you
if(partyMembers.get(uuid).equals(partyName)) {
Player target = Bukkit.getPlayer(uuid);
if(target.isOnline()) {
//Add them if they are online
playersToTeleportTo.add(target);
}
}
}
}
return playersToTeleportTo.toArray(new Player[0]);
}));
Notice something here? There's some code repetition for the withRequirement
method - this is the same predicate that we used earlier, except we remove the negation. If you are interested, you can view the section Predicate tips for a method to improve code reuse.
Once the arguments have been declared, we can now implement our party teleportation command:
new CommandAPICommand("party")
.withArguments(arguments)
.executesPlayer((player, args) -> {
Player target = (Player) args[0];
player.teleport(target);
})
.register();
What's important to note in this example is that if you spend the time to set up the arguments properly, it severely decreases the amount of code required to write your command. This makes the commands you declare easier to understand and follow and you don't end up having to put all of these checks in the body of your command executor.
Updating requirements
Finally, the part you've all been waiting for - how to update requirements. With the way requirements work, they need to be updated manually. To illustrate why this is the case, I'll explain using the example of the /repair command:
When a player joins the game, the server tells the client the list of all commands that the client can run (don't worry, this is completely normal, as declared here). Let's say that the player has joined and has less than 30 levels.
When a player has less than 30 levels, they are unable to execute the /repair
command, because the list of commands that the server sent to the client did not contain the /repair
command. Eventually, the player will fight some mobs or mine some ores and eventually will reach 30 levels. Despite this, the player's client doesn't actually know that they're now able to use the /repair
command until the server tells them. As such, the server needs to somehow update the requirements that a player has so a player knows they can run the command.
The CommandAPI handles this in a very simple method call:
CommandAPI.updateRequirements(player);
Developer's Note:
The CommandAPI.updateRequirements(player);
method can be used anywhere, except for the withRequirement
method. Using it inside this method will crash the server. This is by design - just make sure you don't use it within the withRequirement
method and everything will be fine!
To illustrate how to use this, we'll go over the two examples above:
Example - Updating requirements for xp changes
In the example of requirements with the /repair command, we needed to ensure that the player's requirements update when their experience changes. To do this, we'll simply use a normal event to check this:
@EventHandler
public void onExpChange(PlayerExpChangeEvent event) {
CommandAPI.updateRequirements(event.getPlayer());
}
And of course, you have to ensure that this event is registered in your onEnable()
method:
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
}
Developer's Note:
I'm assuming you already know how to register events and don't need me to go into great detail how to do so, take the code above with a pinch of salt - I know how much everyone likes to divide their event handlers and listeners to organise their code.
Example - Updating requirements for the party creation example
In the example for a party creation, we declared two commands:
/party create <partyName>
/party tp <player>
When a player creates a new party, we need to ensure that their requirements are updated when they create the party. As such, we simply add this to our party creation command executor:
new CommandAPICommand("party")
.withArguments(arguments)
.executesPlayer((player, args) -> {
//Get the name of the party to create
String partyName = (String) args[0];
partyMembers.put(player.getUniqueId(), partyName);
CommandAPI.updateRequirements(player);
})
.register();
That's it!
Multiple requirements
The CommandAPI lets you handle multiple requirements really easily! The withRequirement
method can be called multiple times, so you don't have to worry about shoving everything in one expression.
Example - Using multiple requirements
For example, you can apply multiple requirements for a command by calling the withRequirement
method multiple times:
new CommandAPICommand("someCommand")
.withRequirement(sender -> ((Player) sender).getLevel() >= 30)
.withRequirement(sender -> ((Player) sender).getInventory().contains(Material.DIAMOND_PICKAXE))
.withRequirement(sender -> ((Player) sender).isInvulnerable())
.executesPlayer((player, args) -> {
//Code goes here
})
.register();
Aliases
Aliases for commands can be added by using the withAliases()
method when registering a command. Aliases allow you to run the same command with a different 'name' from the original registered command name.
Example - Using aliases for /getpos
In this example, we register the command /getpos
that returns the command sender's location. We apply the aliases /getposition
, /getloc
, /getlocation
and /whereami
as well, using the withAliases()
method.
new CommandAPICommand("getpos")
// Declare your aliases
.withAliases("getposition", "getloc", "getlocation", "whereami")
//Declare your implementation
.executesEntity((entity, args) -> {
entity.sendMessage(String.format("You are at %d, %d, %d",
entity.getLocation().getBlockX(),
entity.getLocation().getBlockY(),
entity.getLocation().getBlockZ())
);
})
.executesCommandBlock((block, args) -> {
block.sendMessage(String.format("You are at %d, %d, %d",
block.getBlock().getLocation().getBlockX(),
block.getBlock().getLocation().getBlockY(),
block.getBlock().getLocation().getBlockZ())
);
})
//Register the command
.register();
Subcommands
Subcommands is another method for registering commands that makes use of creating multiple different CommandAPICommand
instances. Given a CommandAPICommand
, we can add a subcommand by using the following method:
CommandAPICommand withSubcommand(CommandAPICommand subcommand);
Using subcommands has no disadvantages to using regular commands with the LiteralArgument
or MultiLiteralArgument
, and should be slightly more intuitive to implement if you've used other command frameworks before.
Example - Permission system with subcommands
Say we wanted to write a permission management system. To do this, we'll use the following command structure:
/perm group add <permission> <groupName>
/perm group remove <permission> <groupName>
/perm user add <permission> <userName>
/perm user remove <permission> <userName>
Let's start with the simplest example - the /perm group ...
command. We have one command which is basically the following:
add <permission> <groupName>
We can implement this by creating a CommandAPICommand
with the command name add
:
CommandAPICommand groupAdd = new CommandAPICommand("add")
.withArguments(new StringArgument("permission"))
.withArguments(new StringArgument("groupName"))
.executes((sender, args) -> {
//perm group add code
});
Similarly, we have another part remove <permission> <groupName>
. We can declare this similar to our add
command. Once we've done that, we can now join everything up together. Here, we create a command group
which adds the two other subcommands:
CommandAPICommand groupRemove = new CommandAPICommand("remove")
.withArguments(new StringArgument("permission"))
.withArguments(new StringArgument("groupName"))
.executes((sender, args) -> {
//perm group remove code
});
CommandAPICommand group = new CommandAPICommand("group")
.withSubcommand(groupAdd)
.withSubcommand(groupRemove);
Finally, we can link everything up together to the perm
command and register the whole thing together:
new CommandAPICommand("perm")
.withSubcommand(group)
.register();
Another, more intuitive method, is to shove everything in one go without creating lots of variables all over the place:
new CommandAPICommand("perm")
.withSubcommand(new CommandAPICommand("group")
.withSubcommand(new CommandAPICommand("add")
.withArguments(new StringArgument("permission"))
.withArguments(new StringArgument("groupName"))
.executes((sender, args) -> {
//perm group add code
})
)
.withSubcommand(new CommandAPICommand("remove")
.withArguments(new StringArgument("permission"))
.withArguments(new StringArgument("groupName"))
.executes((sender, args) -> {
//perm group remove code
})
)
)
.withSubcommand(new CommandAPICommand("user")
.withSubcommand(new CommandAPICommand("add")
.withArguments(new StringArgument("permission"))
.withArguments(new StringArgument("userName"))
.executes((sender, args) -> {
//perm user add code
})
)
.withSubcommand(new CommandAPICommand("remove")
.withArguments(new StringArgument("permission"))
.withArguments(new StringArgument("userName"))
.executes((sender, args) -> {
//perm user remove code
})
)
)
.register();
Annotation-based commands
The CommandAPI also includes a very small lightweight annotation-based command framework. This works very differently compared to previous commands shown in this documentation and it is less feature-rich than registering commands using the other methods.
In short, the CommandAPI's annotation-based system:
- Has no runtime overhead compared to using the regular command registration system (unlike other annotation-based frameworks such as ACF).
- Reduces code bloat (to an extent).
- Improves readability since commands are declared declaratively instead of imperatively.
- Is not as powerful as the regular command registration system.
Developer's Note:
Currently, the annotation framework is in its infancy, so any suggestions or improvements are heavily appreciated!
Before we go into too much detail, let's take a look at an example of what this annotation framework looks like, and compare this to the existing method.
Example: A warp command
(So I would put this section in a big green box, but this example is REALLY big and that wouldn't look good)
Let's say we're writing a plugin with the capability to create warps to places on the server. To do this, we'll make a simple command /warp
, defined as follows:
/warp - Shows help
/warp <warp> - Teleports a player to <warp>
/warp create <name> - Creates a new warp <name> at the player's location
Warp command (without annotations)
Using the regular CommandAPI, this is one way we can create this command. In the code below, we use StringArguments to represent the warp names. To teleport to a warp, we also populate it with suggestions (deferred so it updates), and also use a subcommand to represent /warp create
:
Map<String, Location> warps = new HashMap<>();
// /warp
new CommandAPICommand("warp")
.executes((sender, args) -> {
sender.sendMessage("--- Warp help ---");
sender.sendMessage("/warp - Show this help");
sender.sendMessage("/warp <warp> - Teleport to <warp>");
sender.sendMessage("/warp create <warpname> - Creates a warp at your current location");
})
.register();
// /warp <warp>
new CommandAPICommand("warp")
.withArguments(new StringArgument("warp").overrideSuggestions(sender -> {
return warps.keySet().toArray(new String[0]);
}))
.executesPlayer((player, args) -> {
player.teleport(warps.get((String) args[0]));
})
.register();
// /warp create <warpname>
new CommandAPICommand("warp")
.withSubcommand(
new CommandAPICommand("create")
.withPermission("warps.create")
.withArguments(new StringArgument("warpname"))
.executesPlayer((player, args) -> {
warps.put((String) args[0], player.getLocation());
})
)
.register();
Seems fairly straightforward, given everything else covered in this documentation. Now let's compare it to using annotations!
Warp command (with annotations)
I think it's best to show the example and the explain it afterwards:
@Command("warp")
public class WarpCommand {
// List of warp names and their locations
static Map<String, Location> warps = new HashMap<>();
@Default
public static void warp(CommandSender sender) {
sender.sendMessage("--- Warp help ---");
sender.sendMessage("/warp - Show this help");
sender.sendMessage("/warp <warp> - Teleport to <warp>");
sender.sendMessage("/warp create <warpname> - Creates a warp at your current location");
}
@Default
public static void warp(Player player, @AStringArgument String warpName) {
player.teleport(warps.get(warpName));
}
@Subcommand("create")
@Permission("warps.create")
public static void createWarp(Player player, @AStringArgument String warpName) {
warps.put(warpName, player.getLocation());
new IntegerArgument("");
}
}
CommandAPI.registerCommand(WarpCommand.class);
As we can see, the code certainly looks very different to the normal registration method. Let's take it apart piece by piece to see what exactly is going on here.
Command declaration
@Command("warp")
public class WarpCommand {
Firstly, we declare our command warp
. To do this, we use the @Command
annotation and simply state the name of the command in the annotation. This annotation is attached to the class WarpCommand
, which basically indicates that the whole class WarpCommand
will be housing our command.
The annotation framework is designed in such a way that an entire command is represented by a single class. This provides a more modular approach to command declaration that allows you to easily contain the methods of a command in one location.
Default command
@Default
public static void warp(CommandSender sender) {
sender.sendMessage("--- Warp help ---");
sender.sendMessage("/warp - Show this help");
sender.sendMessage("/warp <warp> - Teleport to <warp>");
sender.sendMessage("/warp create <warpname> - Creates a warp at your current location");
}
Here, declare the main command implementation using the @Default
annotation. The @Default
annotation basically informs the CommandAPI that the method it is attached to does not have any subcommands. This is basically the same as registering a regular command without using .withSubcommand()
.
Here, we simply write what happens when no arguments are run (i.e. the user just runs /warp
on its own). As such, we don't include any parameters to our method.
Default command (again!)
@Default
public static void warp(Player player, @AStringArgument String warpName) {
player.teleport(warps.get(warpName));
}
We also have a second @Default
annotated method, which handles our /warp <warp>
command. Because this isn't a subcommand (the warp to teleport to is not a subcommand, it's an argument), we still using the @Default
annotation. In this method, we include an argument with this command by using the @AStringArgument
annotation. This argument uses the StringArgument
class, and the name of this argument is "warpName", which is extracted from the name of the variable. Simply put, the Annotation for an argument is A followed by the name of the argument. This is synonymous with using the following:
new StringArgument("warp")
It's also very important to note the parameters for this method. The first parameter is a Player
object, which represents our command sender. The CommandAPI's annotation system uses the fact that the command sender is a Player
object and automatically ensures that anyone using the command must be a Player
. In other words, non-players (such as the console or command blocks), would be unable to execute this command.
The second argument is a String
object, which represents the result of our argument "warp". The CommandAPI's annotation system can also infer the return type of the argument that is provided to it (in this case, a StringArgument
will produce a String
) and will automatically cast and provide the result to that parameter.
Subcommand
@Subcommand("create")
@Permission("warps.create")
public static void createWarp(Player player, @AStringArgument String warpName) {
warps.put(warpName, player.getLocation());
}
Lastly, we declare a subcommand to allow us to run /warp create <name>
. To do this, we simply use the @Subcommand
annotation. In this example, we also apply a permission node that is required to run the command by using the @Permission
annotation. The rest is fairly straight forward - we declare an argument, in this case it's another StringArgument
, so we use @AStringArgument
and then declare everything else in a similar fashion to the default command executor.
Registering the command
Registering the command is fairly simple and is a one liner:
CommandAPI.registerCommand(WarpCommand.class);
This line can be placed in your onEnable()
or onLoad()
method like you were registering a normal command.
Annotations
This page outlines in detail the list of all annotations that the CommandAPI's annotation-based command system includes.
Annotations that go on classes
@Command
("commandName")
The @Command
annotation is used to declare a command. The parameter is the name of the command that will be registered.
@Command("warp")
public class WarpCommand {
@Alias({...})
The @Alias
annotation is used to declare a list of aliases for a command. The parameter is a list of aliases which can be used for the command.
@Command("teleport")
@Alias({"tp", "tele"})
public class TeleportCommand {
@Permission("permissionNode")
The @Permission
annotation is used to add a permission node to a command. Users that want to run this command must have this permission. The parameter is the permission node required to run the command.
@Command("teleport")
@Permission("myplugin.tp")
class TeleportCommand {
@NeedsOp
The @NeedsOp
annotation is used to indicate that a command needs to have operator privileges to run it. This annotation has no parameters.
@Command("teleport")
@NeedsOp
class TeleportCommand {
Annotations that go on methods
To use annotations on methods, methods must be static.
@Default
The @Default
annotation indicates that the method is not a subcommand. This acts in a similar way to regular Bukkit commands. Commands with the @Default
annotation can be used to run the main code when the command named with the @Command
annotation is stated, such as the following:
@Default
public static void warp(CommandSender sender) {
sender.sendMessage("--- Warp help ---");
sender.sendMessage("/warp - Show this help");
sender.sendMessage("/warp <warp> - Teleport to <warp>");
sender.sendMessage("/warp create <warpname> - Creates a warp at your current location");
}
The @Default
annotation does not mean that the command can't have arguments! Arguments can still be used and declared as shown:
@Default
public static void warp(Player player, @AStringArgument String warpName) {
player.teleport(warps.get(warpName));
}
@Subcommand
The @Subcommand
simply tells the CommandAPI that the declared method is a subcommand. This acts in a similar way to the regular CommandAPI's .withSubcommand()
method. The subcommand annotation can take in a single string which is the name of the subcommand:
@Subcommand("create")
@Permission("warps.create")
public static void createWarp(Player player, @AStringArgument String warpName) {
warps.put(warpName, player.getLocation());
}
Or, it can take in a list of strings which represent the aliases that can also be used for the declared subcommand:
@Subcommand({"teleport", "tp"})
public static void teleport(Player player, @APlayerArgument Player target) {
player.teleport(target);
}
@Permission
The @Permission
annotation can also be used on methods to indicate that a permission is required to execute a command.
@Subcommand("create")
@Permission("warps.create")
public static void createWarp(Player player, @AStringArgument String warpName) {
warps.put(warpName, player.getLocation());
}
@NeedsOp
The @NeedsOp
annotation can also be used on methods to indicate that the user must be an operator to run the command.
Annotations that go on parameters
The annotations for arguments are really simple, there's just two things you need to know:
-
To use an annotation argument, just add the letter
A
(for 'annotation') at the beginning of it! For example:\begin{align} \texttt{StringArgument}&\xrightarrow{A}\texttt{@AStringArgument}\\ \texttt{PlayerArgument}&\xrightarrow{A}\texttt{@APlayerArgument}\\ \texttt{AdvancementArgument}&\xrightarrow{A}\texttt{@AAdvancementArgument}\\ &\hspace{0.75em}\vdots \end{align}
For example, we use
@AStringArgument
to indicate that this command takes aStringArgument
as its first parameter:
@Default
public static void warp(Player player, @AStringArgument String warpName) {
player.teleport(warps.get(warpName));
}
- The name of the argument (referred to as "nodeName" in the normal CommandAPI system) is the name of the variable assigned to the parameter. In the above code, this means that the name of the argument is
warpName
.
Special argument annotations
Certain argument annotations have extra parameters that can be supplied to provide additional customization:
Numerical arguments
The following numerical arguments can take both a min
and max
value. Both of these are completely optional. This indicates the range of values (inclusive) that is valid for this argument. For example:
@Default
public static void command(CommandSender sender,
@ADoubleArgument(min = 0.0, max = 10.0) double someDouble,
@AFloatArgument(min = 5.0f, max = 10.0f) float someFloat,
@AIntegerArgument(max = 100) int someInt,
@ALongArgument(min = -10) long someLong
) {
// Command implementation here
}
Literal arguments
Both the LiteralArgument
and MultiLiteralArgument
can be used. When these are used, the name of the variable assigned to the parameter is ignored and not used as the argument's name.
For the @ALiteralArgument
annotation, the parameter is the literal to be used for the command. For the @AMultiLiteralArgument
, the parameter can be an array of multiple literals to use:
@Default
public static void command(CommandSender sender,
@ALiteralArgument("myliteral") String literal,
@AMultiLiteralArgument({"literal", "anotherliteral"}) String multipleLiterals
) {
// Command implementation here
}
Other arguments
The LocationArgument
, Location2DArgument
, EntitySelectorArgument
and ScoreHolderArgument
can all take an extra parameter in their constructors. As a result, the annotation-equivalent of these arguments also allow you to provide the parameter in the annotation:
@Default
public static void command(CommandSender sender,
@ALocationArgument(LocationType.BLOCK_POSITION) Location location,
@ALocation2DArgument(LocationType.PRECISE_POSITION) Location location2d,
@AEntitySelectorArgument(EntitySelector.MANY_ENTITIES) Collection<Entity> entities,
@AScoreHolderArgument(ScoreHolderType.MULTIPLE) Collection<String> scoreHolders
) {
// Command implementation here
}
Registering annotation-based commands
Registering annotation-based commands is really simple. To do this, we use the following method, where className
is the name of a class with a @Command
annotation:
CommandAPI.registerCommand(className)
Example: Registering a Warp command
Say we have a simple command /warp
that is defined as follows:
@Command("warp")
public class WarpCommand {
// List of warp names and their locations
static Map<String, Location> warps = new HashMap<>();
@Default
public static void warp(CommandSender sender) {
sender.sendMessage("--- Warp help ---");
sender.sendMessage("/warp - Show this help");
sender.sendMessage("/warp <warp> - Teleport to <warp>");
sender.sendMessage("/warp create <warpname> - Creates a warp at your current location");
}
@Default
public static void warp(Player player, @AStringArgument String warpName) {
player.teleport(warps.get(warpName));
}
@Subcommand("create")
@Permission("warps.create")
public static void createWarp(Player player, @AStringArgument String warpName) {
warps.put(warpName, player.getLocation());
new IntegerArgument("");
}
}
We can register this in our onLoad()
method so we can use this command from within Minecraft functions:
class MyPlugin extends JavaPlugin {
@Override
public void onLoad() {
CommandAPI.registerCommand(WarpCommand.class);
}
}
Command conversion
Developer's Note:
If you're a server owner, you're probably lost! This section is for developer command conversion. If you're looking for how to convert plugins with the
config.yml
file, you want 2. Configuration for server owners.
The CommandAPI has the ability to convert plugin commands to vanilla Minecraft commands using its config.yml
's plugins-to-convert
option. Nevertheless, the API for command conversion is not hidden and you're free to use it as you see fit!
Before you continue, let's clear up a few naming conventions which is used in the following sections!
- Target plugin - This refers to a non-CommandAPI plugin which registers normal Bukkit commands. This typically uses the old
boolean onCommand(CommandSender ... )
method - Your plugin - This refers to your plugin, the one that uses the CommandAPI and wants to add compatibility to a target plugin
Entire plugins
To register all commands that are declared by a target plugin, the Converter.convert(Plugin)
method can be used. This attempts to register all commands declared in a target plugin's plugin.yml
file, as well as any aliases or permissions stated in the plugin.yml
file.
Example - Converting commands for a target plugin
Say you have some plugin.yml
file for a target plugin that adds some basic functionality to a server. The target plugin in this example is called "TargetPlugin":
name: TargetPlugin
main: some.random.package.Main
version: 1.0
commands:
gmc:
aliases: gm1
gms:
i:
permission: item.permission
As you can see, it declares 3 commands: /gmc
, /gms
and /i
. We can now begin writing your plugin that uses the CommandAPI converter. We will call this plugin "YourPlugin":
public class YourPlugin extends JavaPlugin {
@Override
public void onEnable() {
Converter.convert(Bukkit.getPluginManager().getPlugin("TargetPlugin"));
//Other code goes here...
}
}
When this is run, the commands /gmc
, /gm1
, /gms
and /i
will all be registered by the CommandAPI.
Only specific commands
In addition to converting all commands from a target plugin, the CommandAPI allows you to convert single commands at a time using the following methods from the Converter
class:
public static convert(Plugin plugin, String cmdName);
public static convert(Plugin plugin, String cmdName, List<Argument> arguments);
public static convert(Plugin plugin, String cmdName, Argument... arguments);
In these commands, the plugin
refers to the plugin which has the command you want to convert and cmdName
is the name of the command declared in the target plugin's plugin.yml
file (just the main command, not the aliases!).
The List<Argument>
or Argument...
can be used to provide argument checks that lets you apply the command UI to a bukkit command.
Example - Converting EssentialsX's speed command
Say we want to convert EssentialsX's /speed
command using the CommandAPI. The plugin.yml
entry for the /speed
command is the following:
speed:
description: Change your speed limits.
usage: /<command> [type] <speed> [player]
aliases: [flyspeed,eflyspeed,fspeed,efspeed,espeed,walkspeed,ewalkspeed,wspeed,ewspeed]
From this, we can determine that there are the following commands, where "walk" and "fly" are the different types that the command can take:
/speed <speed>
/speed <speed> <target>
/speed <walk/fly> <speed>
/speed <walk/fly> <speed> <target>
With the EssentialsX plugin, the <speed>
value can only take numbers between 0 and 10. As such, we'll ensure to apply these limits using the IntegerArgument
. In addition, since the speed type can only be "walk" or "fly", we'll add that to our converter as well using a MultiLiteralArgument
:
Plugin essentials = Bukkit.getPluginManager().getPlugin("Essentials");
// /speed <speed>
Converter.convert(essentials, "speed", new IntegerArgument("speed", 0, 10));
// /speed <target>
Converter.convert(essentials, "speed", new PlayerArgument("target"));
// /speed <walk/fly> <speed>
Converter.convert(essentials, "speed",
new MultiLiteralArgument("walk", "fly"),
new IntegerArgument("speed", 0, 10)
);
// /speed <walk/fly> <speed> <target>
Converter.convert(essentials, "speed",
new MultiLiteralArgument("walk", "fly"),
new IntegerArgument("speed", 0, 10),
new PlayerArgument("target")
);
Internal CommandAPI
The CommandAPI does a lot of stuff "behind the scenes". This internal CommandAPI section will go into detail about what the CommandAPI does, how it's implemented and why it has been implemented like that.
Argument identifiers
The CommandAPI's arguments are basically representations of the different arguments that the Minecraft Command Data protocol handles. These are outlined in the table below:
Identifier | CommandAPI argument |
---|---|
brigadier:bool | BooleanArgument |
brigadier:double | DoubleArgument |
brigadier:float | FloatArgument |
brigadier:integer | IntegerArgument |
brigadier:long | LongArgument |
brigadier:string | StringArgument TextArgument GreedyStringArgument CustomArgument<T> |
minecraft:angle | AngleArgument |
minecraft:block_pos | LocationArgument ( LocationType.BLOCK_POSITION ) |
minecraft:block_predicate | BlockPredicateArgument |
minecraft:block_state | BlockStateArgument |
minecraft:color | ChatColorArgument |
minecraft:column_pos | Location2DArgument ( LocationType.BLOCK_POSITION ) |
minecraft:component | ChatComponentArgument |
minecraft:dimension | EnvironmentArgument |
minecraft:entity | EntitySelectorArgument |
minecraft:entity_anchor | |
minecraft:entity_summon | EntityTypeArgument |
minecraft:float_range | FloatRangeArgument |
minecraft:function | FunctionArgument |
minecraft:game_profile | PlayerArgument |
minecraft:int_range | IntegerRangeArgument |
minecraft:item_enchantment | EnchantmentArgument |
minecraft:item_predicate | ItemStackPredicateArgument |
minecraft:item_slot | |
minecraft:item_stack | ItemStackArgument |
minecraft:message | ChatArgument |
minecraft:mob_effect | PotionEffectArgument |
minecraft:nbt | |
minecraft:nbt_compound_tag | NBTCompoundArgument |
minecraft:nbt_path | |
minecraft:nbt_tag | |
minecraft:objective | ObjectiveArgument |
minecraft:objective_criteria | ObjectiveCriteriaArgument |
minecraft:operation | MathOperationArgument |
minecraft:particle | ParticleArgument |
minecraft:resource_location | AdvancementArgument BiomeArgument CustomArgument<T> LootTableArgument RecipeArgument SoundArgument |
minecraft:rotation | RotationArgument |
minecraft:score_holder | ScoreHolderArgument |
minecraft:scoreboard_slot | ScoreboardSlotArgument |
minecraft:swizzle | AxisArgument |
minecraft:team | TeamArgument |
minecraft:time | TimeArgument |
minecraft:uuid | UUIDArgument |
minecraft:vec2 | Location2DArgument ( LocationType.PRECISE_POSITION ) |
minecraft:vec3 | LocationArgument ( LocationType.PRECISE_POSITION ) |
There are a few arguments that aren't implemented. Here's why:
-
minecraft:entity_anchor
- This argument only has two values:eyes
andfeet
. It's incredibly unnecessary for any other purpose and is easier to implement with aMultiLiteralArgument
. -
minecraft:item_slot
- Bukkit's implementation of item slot numbers differs very wildly to Minecraft's implementation of item slot numbers. This difference makes it near-impossible to have a suitable middle-ground for item slot numbers that ensures that invalid numbers cannot be passed to the wrong inventory type. An implementation of this would require a rewrite of the current system to maintain proper inventory slot access safety. -
minecraft:nbt
,minecraft:nbt_path
,minecraft:nbt_tag
- You've got theNBTCompoundArgument
, that's good enough, right? ¯\_(ツ)_/¯
Reloading datapacks
During the initialization of Minecraft 1.16+ servers, the CommandAPI uses a custom datapack reloading sequence as opposed to the normal Vanilla Minecraft datapack reloading method. The CommandAPI's method uses the server's current command dispatcher object as opposed to a new one, which allows datapacks to use commands registered by the CommandAPI. This can be invoked using the following method:
CommandAPI.reloadDatapacks();
Brigadier + CommandAPI
So far, we've been using only the CommandAPI to register commands. As a result, this makes the CommandAPI's features limited by whatever the CommandAPI has implemented. To push past these limits, the CommandAPI includes some extra methods to help with invoking brigadier methods. Of course, to use these methods, brigadier is required. The brigadier dependency's installation instructions can be found here.
Developer's Note:
For those that are unaware, brigadier is Mojang's command parser and dispatching framework. This is basically what the CommandAPI wraps around and is the main underlying source of its functionality.
The CommandAPI has been designed in such a way that you shouldn't have to access NMS in order to make use of the more "advanced" arguments and features - if you find that NMS is required to do something, please make a new issue!
Brigadier support functions
The CommandAPI offers the following methods in the dev.jorel.commandapi.Brigadier
class:
public static CommandDispatcher getCommandDispatcher();
public static RootCommandNode getRootNode();
public static LiteralArgumentBuilder fromLiteralArgument(LiteralArgument literalArgument);
public static RedirectModifier fromPredicate(BiPredicate<CommandSender, Object[]> predicate, List<Argument> args);
public static Command fromCommand(CommandAPICommand command);
public static RequiredArgumentBuilder fromArgument(List<Argument> args, String nodeName);
public static RequiredArgumentBuilder fromArgument(Argument argument);
public static SuggestionProvider toSuggestions(String nodeName, List<Argument> args);
Briefly, here's what each of these functions do (you can view the JavaDocs for more information):
Method | Description |
---|---|
getCommandDispatcher | Returns the Minecraft command dispatcher graph |
getRootNode | Returns the root node of the command dispatcher. This is equivalent to using getCommandDispatcher().getRoot(); |
fromLiteralArgument | Creates a LiteralArgumentBuilder from a LiteralArgument |
fromPredicate | Converts a predicate and some arguments into a RedirectModifier . This can be used for the fork method in brigadier's ArgumentBuilder |
fromCommand | Converts a CommandAPICommand into a brigadier Command object |
fromArgument | Converts an argument, or a list of arguments, into a RequiredArgumentBuilder |
toSuggestions | Converts an argument's suggestions into brigadier's SuggestionProvider , with a list of previously declared arguments |
Examples
I hope these examples help understand how the CommandAPI can help with registering more "powerful" commands with the use of brigadier as well! Please bear with with it - these examples can be long, but I'm certain that they've been explained well and will be useful!
Example - Adding a predicate to the 'execute' command
Say we wanted to add a predicate to the /execute
command. In this example, we'll create a predicate which handles random chances. To illustrate this, we want to be able to run commands such as:
/execute if randomchance 1 4 run say Hello!
In this scenario, if we ran this command, we would expect "Hello!" to appear in the chat with a \(\frac{1}{4}\) chance. In particular, this is what we're trying to achieve:
- We want to create a predicate (true/false value) for the following syntax:
randomchance <numerator> <denominator>
-
We also want this predicate to come after
execute if
:\[ \texttt{execute}\\ \downarrow\\ \texttt{if}\\ \downarrow\\ \texttt{randomchance <numerator}\texttt{> <denominator}\texttt{>} \]
-
After entering our predicate, we want to route back to
execute
(because the argument afterexecute
isrun
, which is used in our example command above):\[ \texttt{execute}\\ \downarrow\\ \texttt{if}\\ \downarrow\\ \texttt{randomchance <numerator}\texttt{> <denominator}\texttt{>}\\ \downarrow\\ \texttt{execute} \]
Writing the code
Now that we've established what we want, we can finally begin writing the code! First we want to create a literal randomchance
. It's a literal because literal values don't change (similar to say run
or if
from the /execute
command). To create a literal, we'll use the fromLiteralArgument
method described above, and then build it using the .build()
method:
//Register literal "randomchance"
LiteralCommandNode randomChance = Brigadier.fromLiteralArgument(new LiteralArgument("randomchance")).build();
With that completed, we can now create our "argument" to this predicate. To do this, we'll use the regular declaration of arguments that we would normally use for commands. In this example, because we're computing \(\frac{numerator}{denominator}\), we want our numerator to be 0 or greater and our denominator to be 1 or greater (we don't want any negative numbers or division by zero!):
//Declare arguments like normal
List<Argument> arguments = new ArrayList<>();
arguments.add(new IntegerArgument("numerator", 0));
arguments.add(new IntegerArgument("denominator", 1));
Now we're going to get into the very nitty-gritty part - the predicate declaration. First, we'll create some variables numerator
and denominator
to represent the brigadier instances of these arguments. This can be handled by using the Brigadier.argBuildOf
function:
ArgumentBuilder numerator = Brigadier.fromArgument(arguments, "numerator");
ArgumentBuilder denominator = Brigadier.fromArgument(arguments, "denominator")
Now we'll define our predicate. Since this is sort of a "meta-command" (it directly affects the outcome of the run
command), we need to use the ArgumentBuilder
's fork
method. Remember that after we run this predicate, we want to link back to execute
again, so our first argument is the CommandNode
for execute
, which we can get using Brigadier.getRootNode().getChild("execute")
. Then, we can simply use Brigadier.fromPredicate
to finish our declaration:
ArgumentBuilder denominator = Brigadier.fromArgument(arguments, "denominator")
//Fork redirecting to "execute" and state our predicate
.fork(Brigadier.getRootNode().getChild("execute"), Brigadier.fromPredicate((sender, args) -> {
//Parse arguments like normal
int num = (int) args[0];
int denom = (int) args[1];
//Return boolean with a num/denom chance
return Math.ceil(Math.random() * (double) denom) <= (double) num;
}, arguments));
Finally, we can now link everything up. We know that numerator
comes first, then denominator
, so we have to have numerator.then(denominator)
. We also know that these arguments are the children of the randomChance
literal, so we use the following code to state all of this:
//Add <numerator> <denominator> as a child of randomchance
randomChance.addChild(numerator.then(denominator).build());
Finally, we "register" the command. In this case, we're actually just adding the randomChance
node under \(\texttt{execute}\rightarrow\texttt{if}\), which we can add using the following code:
//Add (randomchance <numerator> <denominator>) as a child of (execute -> if)
Brigadier.getRootNode().getChild("execute").getChild("if").addChild(randomChance);
Code summary
So, hopefully that wasn't too confusing! If you're still lost, here's the whole code that we wrote:
//Register literal "randomchance"
LiteralCommandNode randomChance = Brigadier.fromLiteralArgument(new LiteralArgument("randomchance")).build();
//Declare arguments like normal
List<Argument> arguments = new ArrayList<>();
arguments.add(new IntegerArgument("numerator", 0));
arguments.add(new IntegerArgument("denominator", 1));
//Get brigadier argument objects
ArgumentBuilder numerator = Brigadier.fromArgument(arguments, "numerator");
ArgumentBuilder denominator = Brigadier.fromArgument(arguments, "denominator")
//Fork redirecting to "execute" and state our predicate
.fork(Brigadier.getRootNode().getChild("execute"), Brigadier.fromPredicate((sender, args) -> {
//Parse arguments like normal
int num = (int) args[0];
int denom = (int) args[1];
//Return boolean with a num/denom chance
return Math.ceil(Math.random() * (double) denom) <= (double) num;
}, arguments));
//Add <numerator> <denominator> as a child of randomchance
randomChance.addChild(numerator.then(denominator).build());
//Add (randomchance <numerator> <denominator>) as a child of (execute -> if)
Brigadier.getRootNode().getChild("execute").getChild("if").addChild(randomChance);
Predicate tips
In our example for creating a party system, we ended up having lots of code repetition. In our party creation command, we had the following code:
List<Argument> arguments = new ArrayList<>();
// The "create" literal, with a requirement that a player must have a party
arguments.add(new LiteralArgument("create")
.withRequirement(sender -> {
return !partyMembers.containsKey(((Player) sender).getUniqueId());
}));
arguments.add(new StringArgument("partyName"));
And for our party teleportation command, we had the following code:
arguments = new ArrayList<>();
arguments.add(new LiteralArgument("tp")
.withRequirement(sender -> {
return partyMembers.containsKey(((Player) sender).getUniqueId());
}));
We can simplify this code by declaring the predicate:
Predicate<CommandSender> testIfPlayerHasParty = sender -> {
return partyMembers.containsKey(((Player) sender).getUniqueId());
};
Now, we can use the predicate testIfPlayerHasParty
in our code for creating a party. Since we want to apply the "not" (!
) operator to this predicate, we can use .negate()
to invert the result of our predicate:
List<Argument> arguments = new ArrayList<>();
arguments.add(new LiteralArgument("create").withRequirement(testIfPlayerHasParty.negate()));
arguments.add(new StringArgument("partyName"));
And we can use it again for our code for teleporting to party members:
arguments = new ArrayList<>();
arguments.add(new LiteralArgument("tp").withRequirement(testIfPlayerHasParty));
Upgrading guide
From version 4.x to 5.0
Argument registration
LinkedHashMap
is no longer used for argument registration. Instead, use a List
, and put the argument's "prompt" as the first parameter in the argument's constructor. For example:
LinkedHashMap<String, Argument> arguments = new LinkedHashMap<>();
arguments.put("target", new PlayerArgument())
arguments.put("location", new LocationArgument(LocationType.BLOCK_POSITION));
new CommandAPICommand("teleport")
.withArguments(arguments)
.executes((sender, args) -> {
//Teleport <target> to <location>
})
.register();
\[\downarrow\]
List<Argument> arguments = new ArrayList<>();
arguments.add(new PlayerArgument("target"));
arguments.add(new LocationArgument("location", LocationType.BLOCK_POSITION));
new CommandAPICommand("teleport")
.withArguments(arguments)
.executes((sender, args) -> {
//Teleport <target> to <location>
})
.register();
Alternatively, you can declare them directly in the command's declaration so you don't have to construct a list:
new CommandAPICommand("teleport")
.withArguments(new PlayerArgument("target"))
.withArguments(new LocationArgument("location", LocationType.BLOCK_POSITION))
.executes((sender, args) -> {
//Teleport <target> to <location>
})
.register();
Alternatively, you can declare it in one line:
new CommandAPICommand("teleport")
.withArguments(new PlayerArgument("target"), new LocationArgument("location", LocationType.BLOCK_POSITION))
.executes((sender, args) -> {
//Teleport <target> to <location>
})
.register();
Method changes
Some of the Brigadier
methods were changed:
LiteralCommandNode registerNewLiteral(String name);
RequiredArgumentBuilder argBuildOf(LinkedHashMap<String, Argument> args, String value);
RequiredArgumentBuilder argBuildOf(String prompt, Argument argument);
\[\downarrow\]
LiteralArgumentBuilder fromLiteralArgument(LiteralArgument literalArgument);
RequiredArgumentBuilder fromArgument(List<Argument> args, String nodeName);
RequiredArgumentBuilder fromArgument(Argument argument);
In particular, the fromLiteralArgument
now takes in a LiteralArgument
and returns a LiteralArgumentBuilder
. To convert from a LiteralArgumentBuilder
to the LiteralCommandNode
, you can run the .build()
method.
From version 3.x to 4.0
The maven repository url has changed:
Instead of being:
https://raw.githubusercontent.com/JorelAli/1.13-Command-API/mvn-repo/1.13CommandAPI/
You must now use:
https://raw.githubusercontent.com/JorelAli/CommandAPI/mvn-repo/
This information can be viewed in section 3. Setting up your development environment. (Don't worry if you forget, it should work as normal nonetheless!)
From version 2.3 to 3.0
The CommandAPI's upgrade from version 2.3 to 3.0 is very intense and various refactoring operations took place, which means that plugins that implement the CommandAPI version 2.3 or will not to work with the CommandAPI version 3.0. This page outlines the few major changes and points you to the various pages in the documentation that covers how to use version 3.0.
Imports & Renaming
The default package name has been changed. Instead of being registered under the io.github.jorelali
package, the CommandAPI has been moved to the dev.jorel
package:
\[\texttt{io.github.jorelali.commandapi.api}\rightarrow\texttt{dev.jorel.commandapi}\]
To organise classes with other classes of similar functions, new packages have been introduced. These can be fully explored using the new JavaDocs
Removed classes & Alternatives
To reduce redundancies, the CommandAPI removed a few classes:
Removed class | Alternative |
---|---|
SuggestedStringArgument | Use .overrideSuggestions(String[]) for the relevant argument, as described here |
DefinedCustomArguments for Objectives | Use ObjectiveArgument |
DefinedCustomArguments for Teams | Use TeamArgument |
Command registration
The way that commands are registered has been completely changed. It is highly recommended to switch to the new system, which is described here.
The following methods have been removed:
CommandAPI.getInstance().register(String, LinkedHashMap, CommandExecutor);
CommandAPI.getInstance().register(String, String[], LinkedHashMap, CommandExecutor);
CommandAPI.getInstance().register(String, CommandPermission, LinkedHashMap, CommandExecutor);
CommandAPI.getInstance().register(String, CommandPermission, String[], LinkedHashMap, CommandExecutor);
CommandAPI.getInstance().register(String, LinkedHashMap, ResultingCommandExecutor);
CommandAPI.getInstance().register(String, String[], LinkedHashMap, ResultingCommandExecutor);
CommandAPI.getInstance().register(String, CommandPermission, LinkedHashMap, ResultingCommandExecutor);
CommandAPI.getInstance().register(String, CommandPermission, String[], LinkedHashMap, ResultingCommandExecutor);
Additionally, the CommandAPI is no longer accessed by using CommandAPI.getInstance()
. This has been replaced with static methods that can be accessed without an instance of the CommandAPI, so you can use the following:
CommandAPI.fail(String command);
CommandAPI.canRegister();
CommandAPI.unregister(String command);
CommandAPI.unregister(String command, boolean force);
Incompatible version information
There are a few arguments that are incompatible with various versions of Minecraft. This page outlines the full list of incompatibilities that the CommandAPI has with what versions of Minecraft.
Argument changes with respect to Minecraft version
AngleArgument
Incompatible with Minecraft versions less than 1.16.2 (1.13.x, 1.14.x, 1.15.x, 1.16, 1.16.1)
BiomeArgument
Incompatible with Minecraft versions less than 1.16 (1.13.x, 1.14.x, 1.15.x)
ChatArgument
Incompatible with Minecraft version 1.16.1 (Works on 1.16.2)
EnvironmentArgument
Incompatible with Minecraft version 1.13 (Works on 1.13.1 and 1.13.2)
LocationArgument2D
If you're using LocationArgument2D with LocationType.PRECISE_POSITION
, then it is incompatible with Minecraft version 1.13 (Works on 1.13.1 and 1.13.2)
RecipeArgument
If you use Minecraft versions less than 1.15, this argument will return a Recipe
. If you are using Minecraft version 1.15 or greater, this argument will return a ComplexRecipe
(which is a subclass of Recipe
).
TimeArgument
Incompatible with Minecraft versions less than 1.14 (1.13.x)
UUIDArgument
Incompatible with Minecraft versions less than 1.16 (1.13.x, 1.14.x, 1.15.x)
CommandAPI behavior with respect to Minecraft version
Minecraft version 1.16 and beyond
In Minecraft version 1.16, the way datapacks were loaded changed in such a way that the CommandAPI had to put in additional countermeasures to provide full support to it. To illustrate this, this was the previous loading sequence for Bukkit servers in Minecraft 1.15:
\[\texttt{Server loads}\rightarrow\texttt{Plugins load}\rightarrow\texttt{Datapacks load}\rightarrow\texttt{Server finishes loading}\]
Instead however, Minecraft 1.16 changed the loading sequence to the following:
\[\texttt{Server loads}\rightarrow\texttt{Datapacks load}\rightarrow\texttt{Plugins load}\rightarrow\texttt{Server finishes loading}\]
Because the CommandAPI used to register vanilla Minecraft commands before datapacks (and thus, custom Minecraft functions), it was possible to register custom commands that can be used in functions. With this new loading sequence change in Minecraft 1.16, this meant that datapacks load first before the CommandAPI does, so custom commands are not registered and functions with custom commands would fail to load.
To resolve this, the CommandAPI reloads datapacks and recipes at the end:
\begin{align} &\quad\texttt{Server loads} \\ \rightarrow&\quad\texttt{Datapacks load} \\ \rightarrow&\quad\texttt{Plugins load} \\ \rightarrow&\quad\texttt{Server finishes loading} \\ \rightarrow&\quad\texttt{Datapacks are reloaded} && \texttt{(by the CommandAPI)} \\ \rightarrow&\quad\texttt{Recipes are reloaded} && \texttt{(by the CommandAPI)} \end{align}
By doing this, this means:
- Custom functions from datapacks are loaded twice
- Recipes are reloaded twice, including recipes defined by other plugins
Although this sounds pretty bad (since reloading these things twice can be time consuming, thus contributing to the server start-up time), it is the only way to make custom functions work in Minecraft 1.16 and beyond.
Troubleshooting
This section basically summarizes the list of things that could go wrong with the CommandAPI and how to mitigate these circumstances.
My suggestions don't work or update
The suggestions need to be deferred so they are evaluated when the user requests it rather than during server start up. See Argument suggestion deferral which describes this in more detail.
I encounter a NullPointerException
when using Bukkit's scoreboard
Shove the scoreboard access inside a lambda, so it is evaluated when commands are executed rather than when the server loads. For example, use:
List<Argument> arguments = new ArrayList<>();
arguments.add(new TeamArgument("team").safeOverrideSuggestions(s ->
Bukkit.getScoreboardManager().getMainScoreboard().getTeams().toArray(new Team[0]))
);
as opposed to:
List<Argument> arguments = new ArrayList<>();
arguments.add(new TeamArgument("team").safeOverrideSuggestions(
Bukkit.getScoreboardManager().getMainScoreboard().getTeams().toArray(new Team[0]))
);
Server errors when loading datapacks in 1.16+
If you get an error at the very start of the server's startup sequence along the lines of:
[15:57:29] [Worker-Main-5/ERROR]: Failed to load function mycustomnamespace:test
java.util.concurrent.CompletionException: java.lang.IllegalArgumentException: Whilst parsing command on line 2: Unknown or incomplete command, see below for error at position 0: <--[HERE]
at java.util.concurrent.CompletableFuture.encodeThrowable(Unknown Source) ~[?:1.8.0_261]
at java.util.concurrent.CompletableFuture.completeThrowable(Unknown Source) [?:1.8.0_261]
at java.util.concurrent.CompletableFuture$AsyncSupply.run(Unknown Source) [?:1.8.0_261]
at java.util.concurrent.CompletableFuture$AsyncSupply.exec(Unknown Source) [?:1.8.0_261]
at java.util.concurrent.ForkJoinTask.doExec(Unknown Source) [?:1.8.0_261]
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source) [?:1.8.0_261]
at java.util.concurrent.ForkJoinPool.runWorker(Unknown Source) [?:1.8.0_261]
at java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source) [?:1.8.0_261]
Caused by: java.lang.IllegalArgumentException: Whilst parsing command on line 2: Unknown or incomplete command, see below for error at position 0: <--[HERE]
at net.minecraft.server.v1_16_R1.CustomFunction.a(SourceFile:62) ~[spigot-1.16.1.jar:git-Spigot-758abbe-8dc1da1]
at net.minecraft.server.v1_16_R1.CustomFunctionManager.a(SourceFile:84) ~[spigot-1.16.1.jar:git-Spigot-758abbe-8dc1da1]
... 6 more
You can safely ignore it - the CommandAPI fixes this later. This is described in more detail here.
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.
My issue isn't on here, what do I do?!
If you've found a bug that isn't solved here, submit a bug report on the CommandAPI's issues page and I'll try my best to resolve the issue!
Afterword
A message from the CommandAPI's author
Congratulations on making it to the end of the documentation! It's really long, but I did my best to make it the best (Bukkit/Spigot plugin) documentation in existence.
My name is Jorel, commonly known by my Minecraft username Skepter. I started the CommandAPI in the summer holidays between my first and second year at university. On the 19th August, 2018 I made my first commit to the CommandAPI project - just a month and a day after Minecraft 1.13 was released.
At the time, I just decided to call it "The 1.13 Command API" - it wasn't the catchiest name out there, but it sort of said what I wanted it to - it's a Command API for Minecraft 1.13, which was the update when the big overhaul to the command system was introduced.
It all started as a simple idea that can be summarized in 3 bullet points:
- Create an API to use the new command UI that was introduced in Minecraft 1.13
- Make it so the developers don't have to understand/use Mojang's brigadier
- Make it similar to Bukkit's existing API
After the release of version 1.2, two days after the initial release, I received my first GitHub issue. This was quite a shock to me - version 1.2 only had 11 total downloads so it seemed odd that someone managed to stumble upon it despite the fact that I did nothing to promote the CommandAPI. Little did I know that that one issue was the main motivation to keep this API alive after its initial release.
I would never have possible imagined in my wildest dreams that 2 years later, I would still be in contact with them and know that if I had not chosen to create this API, their server would not have survived beyond Minecraft 1.13, let alone Minecraft 1.15, two major Minecraft versions later.
This project has been insane. Absolutely, utterly insane. At over 570 commits and over 450,000 additions (that includes things such as lines of code, lines of generated HTML documentation etc.), I can say without a doubt that this is indeed my biggest project ever.
Anyway, I digress. I'd like to give credit to all of the people that have opened issues on the CommandAPI GitHub, for without these people, the CommandAPI would have only remained a shadow of what it is now. I'd also like to give credit to the people that have starred the CommandAPI on its GitHub page.
I would like to personally give thanks to the following people - these are people that have made a significant contribution to the project in terms of ideas or support:
- Combustible, who kickstarted the project by creating the CommandAPI's first issue. From this issue, this allowed the CommandAPI to have interoperability with Minecraft commands and functions which is by far the CommandAPI's most admirable feature. Additionally, Combustible helped raise awareness of the CommandAPI via the Spigot forums and Spigot issue tracker.
- Draycia, who suggested implementing lazy evaluation for argument suggestions. This has been extended to provide the CommandAPI's context-aware system for argument suggestions based on previously filled arguments.
- HielkeMinecraft, who made three outstanding contributions to the CommandAPI. They created the suggestion of setting the result and success values of commands which improves the interoperability between commands registered with the CommandAPI and vanilla Minecraft commands. They also influenced the implementation of the requirements system to have more powerful command constraints and helped start the CommandAPI Discord server.
- Minenash, who was the driving force for the CommandAPI's 3.0 release, which added a plethora of new arguments to the CommandAPI. Minenash's research, code prototypes, documentation examples, bug testing and code review was a tremendous help to make the 3.0 release such a feature-rich version.
- Michael-Ziluck, who created an amazing pull request that helped greatly improve the performance of the CommandAPI as well as structure the entire CommandAPI project into a multi-module Maven project which significantly improved the maintainability of the CommandAPI for the future.
I'd also like to give a special mention to the following people that have helped find bugs or have supported the project in some way: aianlinb, Baka-Mitai, Checkium, Comeza, DerpNerb, DogeBogey, endrdragon, EnragedRabisu, i509VCB, KieranGateley, lifespan, Loapu, Marvmallow, MatrixTunnel, portlek, Ricky12Awesome, SHsuperCM, SpaceCheetah, The_Gavster, Tinkot, vladfrangu, zedwick
I never really expected more than 5 or so people to use this API, so it was truly a pleasure to see everyone's responses, issues and suggestions that has made the CommandAPI what it is today.
Thank you so much for using the CommandAPI!