Upgrading guide

From 8.8.x to 9.0.0

CommandAPI 9.0.0 is arguably the biggest change in the CommandAPI's project structure and usage. This update was designed to allow the CommandAPI to be generalized for other platforms (e.g. Velocity, Fabric, Sponge), and as a result this update is incompatible with previous versions of the CommandAPI.

All deprecated methods from 8.8.x have been removed in this update. Please ensure that you use the relevant replacement methods (these are described in the JavaDocs for the various deprecated methods) before upgrading to 9.0.0.


Project dependencies

For Bukkit/Spigot/Paper plugins, the commandapi-core and commandapi-shade modules should no longer be used. Instead, use the new commandapi-bukkit-core and commandapi-bukkit-shade modules:

<dependencies>
    <dependency>
        <groupId>dev.jorel</groupId>
        <artifactId>commandapi-core</artifactId>
        <version>9.0.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
<dependencies>
    <dependency>
        <groupId>dev.jorel</groupId>
        <artifactId>commandapi-shade</artifactId>
        <version>9.0.0</version>
    </dependency>
</dependencies>
dependencies {
    compileOnly "dev.jorel:commandapi-core:9.0.0"
}
dependencies {
    compileOnly("dev.jorel:commandapi-core:9.0.0")
}
dependencies {
    implementation "dev.jorel:commandapi-shade:9.0.0"
}
dependencies {
    implementation("dev.jorel:commandapi-shade:9.0.0")
}

$$\downarrow$$

<dependencies>
    <dependency>
        <groupId>dev.jorel</groupId>
        <artifactId>commandapi-bukkit-core</artifactId>
        <version>9.0.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
<dependencies>
    <dependency>
        <groupId>dev.jorel</groupId>
        <artifactId>commandapi-bukkit-shade</artifactId>
        <version>9.0.0</version>
    </dependency>
</dependencies>
dependencies {
    compileOnly "dev.jorel:commandapi-bukkit-core:9.0.0"
}
dependencies {
    compileOnly("dev.jorel:commandapi-bukkit-core:9.0.0")
}
dependencies {
    implementation "dev.jorel:commandapi-bukkit-shade:9.0.0"
}
dependencies {
    implementation("dev.jorel:commandapi-bukkit-shade:9.0.0")
}

Additionally, when using the Kotlin DSL for Bukkit, instead of using commandapi-kotlin, use commandapi-bukkit-kotlin:

<dependencies>
    <dependency>
        <groupId>dev.jorel</groupId>
        <artifactId>commandapi-kotlin</artifactId>
        <version>9.0.0</version>
    </dependency>
</dependencies>
dependencies {
    implementation "dev.jorel:commandapi-kotlin:9.0.0"
}
dependencies {
    implementation("dev.jorel:commandapi-kotlin:9.0.0")
}

$$\downarrow$$

<dependencies>
    <dependency>
        <groupId>dev.jorel</groupId>
        <artifactId>commandapi-bukkit-kotlin</artifactId>
        <version>9.0.0</version>
    </dependency>
</dependencies>
dependencies {
    implementation "dev.jorel:commandapi-bukkit-kotlin:9.0.0"
}
dependencies {
    implementation("dev.jorel:commandapi-bukkit-kotlin:9.0.0")
}

Loading and enabling the CommandAPI when shading

The CommandAPI.onLoad() method has changed in this update. Instead of using the CommandAPIConfig object, use the CommandAPIBukkitConfig and pass in the current plugin reference (this).

and CommandAPI.onEnable() method has also changed, and now no longer requires the plugin reference (this), as it is now included in CommandAPI.onLoad() instead.:

public void onLoad() {
    CommandAPI.onLoad(new CommandAPIConfig());
}

public void onEnable() {
    CommandAPI.onEnable(this);
}

$$\downarrow$$

public void onLoad() {
    CommandAPI.onLoad(new CommandAPIBukkitConfig(this));
}

public void onEnable() {
    CommandAPI.onEnable();
}

Accessing arguments

Arguments for commands are no longer an Object[] and have now been replaced with a more powerful CommandArguments object. This object now lets you access arguments in a number of ways:

Using the args.get(int) method

If you're in a rush and just want to upgrade quickly, call the .get(int) method instead of accessing the arguments using the array access notation:

new CommandAPICommand("cmd")
    .withArguments(new StringArgument("mystring"))
    .withArguments(new PotionEffectArgument("mypotion"))
    .withArguments(new LocationArgument("mylocation"))
    .executes((sender, args) -> {
        String stringArg = (String) args[0];
        PotionEffectType potionArg = (PotionEffectType) args[1];
        Location locationArg = (Location) args[2];
    })
    .register();

$$\downarrow$$

new CommandAPICommand("cmd")
    .withArguments(new StringArgument("mystring"))
    .withArguments(new PotionEffectArgument("mypotion"))
    .withArguments(new LocationArgument("mylocation"))
    .executes((sender, args) -> {
        String stringArg = (String) args.get(0);
        PotionEffectType potionArg = (PotionEffectType) args.get(1);
        Location locationArg = (Location) args.get(2);
    })
    .register();

The CommandAPI introduces a new args.get(String) method to access arguments using the argument node name. This method also makes your code much more compatible with optional arguments:

new CommandAPICommand("cmd")
    .withArguments(new StringArgument("mystring"))
    .withArguments(new PotionEffectArgument("mypotion"))
    .withArguments(new LocationArgument("mylocation"))
    .executes((sender, args) -> {
        String stringArg = (String) args[0];
        PotionEffectType potionArg = (PotionEffectType) args[1];
        Location locationArg = (Location) args[2];
    })
    .register();

$$\downarrow$$

new CommandAPICommand("cmd")
    .withArguments(new StringArgument("mystring"))
    .withArguments(new PotionEffectArgument("mypotion"))
    .withArguments(new LocationArgument("mylocation"))
    .executes((sender, args) -> {
        String stringArg = (String) args.get("mystring");
        PotionEffectType potionArg = (PotionEffectType) args.get("mypotion");
        Location locationArg = (Location) args.get("mylocation");
    })
    .register();

CommandAPI helper methods

The CommandAPI.failWithBaseComponents(message) and CommandAPI.failWithAdventureComponent(message) methods have now been moved from CommandAPI to CommandAPIBukkit, because these methods are Bukkit/Spigot/Paper specific and don't exist for other platforms (e.g. Velocity, Fabric, Sponge):

CommandAPI.failWithBaseComponents(...);
CommandAPI.failWithAdventureComponent(...);

$$\downarrow$$

CommandAPIBukkit.failWithBaseComponents(...);
CommandAPIBukkit.failWithAdventureComponent(...);

Removal of the EnvironmentArgument

The EnvironmentArgument has been removed in this update, as it was implemented incorrectly and is not fit for purpose. Instead, the CommandAPI has the more accurate WorldArgument.


Changes to the TeamArgument

The TeamArgument has been updated to no longer use a String as its return type. Instead, you can now just use a Team object directly:

new CommandAPICommand("team")
    .withArguments(new TeamArgument("team"))
    .executes((sender, args) -> {
        String teamName = (String) args.get("team");
        Team team = Bukkit.getScoreboardManager().getMainScoreboard().getTeam(teamName);
    })
    .register();

$$\downarrow$$

new CommandAPICommand("team")
    .withArguments(new TeamArgument("team"))
    .executes((sender, args) -> {
        Team team = (Team) args.get("team");
    })
    .register();

Changes to the ObjectiveArgument

The ObjectiveArgument has been updated to no longer use a String as its return type. Instead, you can now just use an Objective object directly:

new CommandAPICommand("objective")
    .withArguments(new ObjectiveArgument("objective"))
    .executes((sender, args) -> {
        String objectiveName = (String) args.get("objective");
        Objective objective = Bukkit.getScoreboardManager().getMainScoreboard().getObjective(objectiveName);
    })
    .register();

$$\downarrow$$

new CommandAPICommand("objective")
    .withArguments(new ObjectiveArgument("objective"))
    .executes((sender, args) -> {
        Objective objective = (Objective) args.get("objective");
    })
    .register();

Changes to the ListArgumentBuilder

The ListArgumentBuilder no longer has withList(Function<CommandSender, Collection<T>> list) and instead uses SuggestionInfo to have withList(Function<SuggestionInfo<CommandSender>, Collection<T>> list).

This now allows you to access more information when generating a list dynamically instead of just the command sender. To access the original command sender, you can use the sender() method from SuggestionInfo:

ListArgument<?> arg = new ListArgumentBuilder<>("values", ", ")
    .withList(sender -> List.of("cat", "wolf", "axolotl", sender.getName()))
    .withStringMapper()
    .buildGreedy();

$$\downarrow$$

ListArgument<?> arg = new ListArgumentBuilder<>("values", ", ")
    .withList(info -> List.of("cat", "wolf", "axolotl", info.sender().getName()))
    .withStringMapper()
    .buildGreedy();

Changes to the Rotation wrapper

The Rotation class now uses a constructor which has the yaw first, and the pitch second, instead of the pitch first and the yaw second.

new Rotation(20, 80); // Yaw = 80, Pitch = 20

$$\downarrow$$

new Rotation(20, 80); // Yaw = 20, Pitch = 80

Changes to the ScoreboardSlot wrapper

The ScoreboardSlot wrapper is now an enum that has direct support for sidebar team colors, via the SIDEBAR_TEAM_### enum values, for example SIDEBAR_TEAM_RED;

ScoreboardSlot slot = // Some ScoreboardSlot
DisplaySlot displaySlot = slot.getDisplaySlot(); // Returns PLAYER_LIST, SIDEBAR or BELOW_NAME

// Extract the color if necessary
if (slot.hasTeamColor()) {
    ChatColor color = slot.getTeamColor();
}

$$\downarrow$$

ScoreboardSlot slot = // Some ScoreboardSlot
DisplaySlot displaySlot = slot.getDisplaySlot(); // Returns PLAYER_LIST, BELOW_NAME or SIDEBAR_TEAM_###