Shading the CommandAPI in your 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
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(CommandAPIConfig config);
CommandAPI.onEnable();
If you want to handle reloading, the CommandAPI has minimal support for it with the onDisable()
method, which can go in your plugin. This is optional and is not required if you don't plan on reloading the server.
Loading
The onLoad(CommandAPIConfig)
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 CommandAPIConfig
is used to configure how the CommandAPI works. The CommandAPIConfig
class has the following parameters which let you set how the CommandAPI works similar to the config.yml
, which is described here.
public class CommandAPIConfig {
CommandAPIConfig verboseOutput(boolean value); // Enables verbose logging
CommandAPIConfig silentLogs(boolean value); // Disables ALL logging (except errors)
CommandAPIConfig useLatestNMSVersion(boolean value); // Whether the latest NMS implementation should be used or not
CommandAPIConfig beLenientForMinorVersions(boolean value); // Whether the CommandAPI should be more lenient with minor Minecraft versions
CommandAPIConfig missingExecutorImplementationMessage(String value); // Set message to display when executor implementation is missing
CommandAPIConfig dispatcherFile(File file); // If not null, the CommandAPI will create a JSON file with Brigadier's command tree
CommandAPIConfig setNamespace(String namespace); // The namespace to use when the CommandAPI registers a command
CommandAPIConfig usePluginNamespace(); // Whether the CommandAPI should use the name of the plugin passed into the CommandAPIConfig implementation as the default namespace for commands
<T> CommandAPIConfig initializeNBTAPI(Class<T> nbtContainerClass, Function<Object, T> nbtContainerConstructor); // Initializes hooks with an NBT API. See NBT arguments documentation page for more info
}
The CommandAPIConfig
class follows a typical builder pattern (without you having to run .build()
at the end), which lets you easily construct configuration instances.
However, the CommandAPIConfig
class is abstract and cannot be used to configure the CommandAPI directly. Instead, you must use a subclass of CommandAPIConfig
that corresponds to the platform you are developing for. For example, when developing for Bukkit, you should use the CommandAPIBukkitConfig
class.
public class CommandAPIBukkitConfig extends CommandAPIConfig {
CommandAPIBukkitConfig(JavaPlugin plugin);
CommandAPIBukkitConfig shouldHookPaperReload(boolean hooked); // Whether the CommandAPI should hook into the Paper-exclusive ServerResourcesReloadedEvent
CommandAPIBukkitConfig skipReloadDatapacks(boolean skip) // Whether the CommandAPI should reload datapacks on server load
}
In order to create a CommandAPIBukkitConfig
object, you must give it a reference to your JavaPlugin
instance. The CommandAPI always uses this to registers events, so it is required when loading the CommandAPI on Bukkit. There are also Bukkit-specific features, such as the hook-paper-reload
configuration option, which may be configured using a CommandAPIBukkitConfig
instance.
For example, to load the CommandAPI on Bukkit with all logging disabled, you can use the following:
CommandAPI.onLoad(new CommandAPIBukkitConfig(plugin).silentLogs(true));
CommandAPI.onLoad(CommandAPIBukkitConfig(plugin).silentLogs(true))
Enabling & Disabling
The onEnable()
method initializes the CommandAPI's enabling sequence. Similar to the onLoad(CommandAPIConfig)
method, this must be placed in your plugin's onEnable()
method. This isn't as strict as the onLoad(CommandAPIConfig)
method, and can be placed anywhere in your onEnable()
method.
The onDisable()
method disables the CommandAPI gracefully. This should be placed in your plugin's onDisable()
method. This doesn't unregister commands, so commands may persist during reloads - this can be mitigated using the CommandAPI.unregister()
method.
Example - Setting up the CommandAPI in your plugin
public class MyPlugin extends JavaPlugin {
@Override
public void onLoad() {
CommandAPI.onLoad(new CommandAPIBukkitConfig(this).verboseOutput(true)); // Load with verbose output
new CommandAPICommand("ping")
.executes((sender, args) -> {
sender.sendMessage("pong!");
})
.register();
}
@Override
public void onEnable() {
CommandAPI.onEnable();
// Register commands, listeners etc.
}
@Override
public void onDisable() {
CommandAPI.onDisable();
}
}
class MyPlugin : JavaPlugin() {
override fun onLoad() {
CommandAPI.onLoad(CommandAPIBukkitConfig(this).verboseOutput(true)) // Load with verbose output
CommandAPICommand("ping")
.executes(CommandExecutor { sender, _ ->
sender.sendMessage("pong!")
})
.register()
}
override fun onEnable() {
CommandAPI.onEnable()
// Register commands, listeners etc.
}
override fun onDisable() {
CommandAPI.onDisable()
}
}
A note about relocating
By default, the CommandAPI is written in the dev.jorel.commandapi
package. It is highly recommended to "relocate" the shaded copy of the CommandAPI to your own package instead to prevent package clashes with other projects that shade the CommandAPI:
\begin{align} &\qquad\texttt{dev.jorel.commandapi} \rightarrow \texttt{my.custom.package.commandapi} \\ \end{align}
Shading with Maven
To shade the CommandAPI into a maven project, you'll need to use the commandapi-bukkit-shade
dependency, which is optimized for shading and doesn't include plugin-specific files (such as plugin.yml
). Here you have a choice between the Spigot-mapped version and the Mojang-mapped version. You do not need to use commandapi-bukkit-core
if you are shading:
Add the CommandAPI shade dependency:
<dependencies>
<dependency>
<groupId>dev.jorel</groupId>
<artifactId>commandapi-bukkit-shade</artifactId>
<version>9.7.0</version>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>dev.jorel</groupId>
<artifactId>commandapi-bukkit-shade-mojang-mapped</artifactId>
<version>9.7.0</version>
</dependency>
</dependencies>
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.6.0</version>
<executions>
<execution>
<id>shade</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<minimizeJar>true</minimizeJar>
<relocations>
<relocation>
<pattern>dev.jorel.commandapi</pattern>
<!-- TODO: Change this to my own package name -->
<shadedPattern>my.custom.package.commandapi</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
</plugins>
</build>
As we're shading 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 GradleUp Shadow Plugin. Add this to your list of plugins:
plugins {
id 'java'
id 'com.gradleup.shadow' version '8.3.3'
}
plugins {
java
id("com.gradleup.shadow") version "8.3.3"
}
Add our repositories:
repositories {
mavenCentral()
// If you want to shade the NBT API as well
maven { url = "https://repo.codemc.org/repository/maven-public/" }
}
repositories {
mavenCentral()
// If you want to shade the NBT API as well
maven(url = "https://repo.codemc.org/repository/maven-public/")
}
Next, we declare our dependencies:
dependencies {
implementation "dev.jorel:commandapi-bukkit-shade:9.7.0"
}
dependencies {
implementation "dev.jorel:commandapi-bukkit-shade-mojang-mapped:9.7.0"
}
dependencies {
implementation("dev.jorel:commandapi-bukkit-shade:9.7.0")
}
dependencies {
implementation("dev.jorel:commandapi-bukkit-shade-mojang-mapped:9.7.0")
}
Then you just need to relocate the CommandAPI to your desired location in the shadowJar
task configuration:
shadowJar {
// TODO: Change this to my own package name
relocate("dev.jorel.commandapi", "my.custom.package.commandapi")
}
tasks.withType<ShadowJar> {
// TODO: Change this to my own package name
relocate("dev.jorel.commandapi", "my.custom.package.commandapi")
}
Finally, we can build the shaded jar using the following command:
gradlew build shadowJar
As we're shading the CommandAPI into your plugin, we don't need to add depend: [CommandAPI]
to your plugin.yml
file.