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 syntax:

/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<Collection<String>>("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();
CommandAPICommand("reward")
    // We want multiple players, so we use ScoreHolderType.MULTIPLE in the constructor
    .withArguments(ScoreHolderArgument<Collection<String>>("players", ScoreHolderType.MULTIPLE))
    .executes(CommandExecutor { _, args ->
        // Get player names by casting to Collection<String>
        val players = args[0] as Collection<String>

        for (playerName in players) {
            Bukkit.getPlayer(playerName)?.inventory!!.addItem(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 the Collection<String> so it only returns player names, which allows us to use Bukkit.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 the UUID.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

A scoreboardslotargument showing a list of suggestions of valid Minecraft scoreboard slot positions

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 syntax:

/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();
CommandAPICommand("clearobjectives")
    .withArguments(ScoreboardSlotArgument("slot"))
    .executes(CommandExecutor { _, args ->
        val scoreboard = Bukkit.getScoreboardManager().mainScoreboard
        val slot = (args[0] as ScoreboardSlot).displaySlot
        scoreboard.clearSlot(slot)
    })
    .register()