Entity & Player arguments
Entity selector argument
Minecraft's target selectors (e.g. @a
or @e
) are implemented using the subclasses of the EntitySelectorArgument
class. This allows you to select specific entities based on certain attributes.
There are four EntitySelectorArgument
subclasses that determine what type of data to return:
EntitySelectorArgument.OneEntity
- A single entity, which returns aEntity
object.EntitySelectorArgument.ManyEntities
- A collection of many entities, which returns aCollection<Entity>
object.EntitySelectorArgument.OnePlayer
- A single player, which returns aPlayer
object.EntitySelectorArgument.ManyPlayers
- A collection of players, which returns aCollection<Player>
object.
The return type is the type to be cast when retrieved from the CommandArguments args
in the command declaration.
Example - Remove entities command
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 EntitySelectorArgument.ManyEntities
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.ManyEntities("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.get("entities");
sender.sendMessage("Removed " + entities.size() + " entities");
for (Entity e : entities) {
e.remove();
}
})
.register();
new CommandAPICommand("remove")
// Using a collective entity selector to select multiple entities
.withArguments(new EntitySelectorArgument.ManyEntities("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.get("entities");
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:
mccmd/remove @e[type=cow]
To remove the 10 furthest pigs from the command sender:
mccmd/remove @e[type=pig,limit=10,sort=furthest]
PlayerProfile argument
The PlayerProfileArgument
can serve a similar purpose as the EntitySelectorArgument.OnePlayer
or EntitySelectorArgument.ManyPlayers
if you only reference online players, but it can also be used to reference players that are offline or have never logged into your server.
Because of this, it has a performance overhead even when the input is an online player or an entity selector.
The PlayerProfileArgument
returns a List<com.destroystokyo.paper.profile.PlayerProfile>
.
The PlayerProfileArgument
returns a List<org.bukkit.profile.PlayerProfile>
.
Example – PlayerProfileArgument without entity selectors
Example – PlayerProfileArgument without entity selectors
When registering a PlayerProfileArgument
you might notice that it includes Entity Selectors
(@a
, @e
, @r
, etc.). If you want to avoid those, you can use argument suggestions to only suggest the player names. For this example, let us create a /warp command:
/warp <player>
To get a PlayerProfileArgument
which only suggests the actual names, we can define it like this:
Argument<?> noSelectorSuggestions = new PlayerProfileArgument("target")
.replaceSafeSuggestions(SafeSuggestions.suggest(info ->
Bukkit.getOnlinePlayers().stream().map(Player::getPlayerProfile).toArray(PlayerProfile[]::new)
));
Argument<?> noSelectorSuggestions = new PlayerProfileArgument("target")
.replaceSafeSuggestions(SafeSuggestions.suggest(info ->
Bukkit.getOnlinePlayers().stream().map(Player::getPlayerProfile).toArray(PlayerProfile[]::new)
));
Now we can define the rest of the command and include our suggestion inside it like this:
new CommandAPICommand("warp")
.withArguments(noSelectorSuggestions)
.executesPlayer((player, args) -> {
Player target = (Player) args.get("target");
player.teleport(target);
})
.register();
new CommandAPICommand("warp")
.withArguments(noSelectorSuggestions)
.executesPlayer((player, args) -> {
Player target = (Player) args.get("target");
player.teleport(target);
})
.register();
And there we have it! One thing to note is that entity selectors are still a valid input; they’re just not included in the suggestions.
AsyncPlayerProfile argument
The AsyncPlayerProfileArgument
class is identical to the PlayerProfileArgument
class, but instead of making the API call synchronously, it makes the API call asynchronously. This means that the command will not block the main thread while waiting for the API call to complete.
Developer's Note:
The AsyncPlayerProfileArgument
returns a CompletableFuture<List<com.destroystokyo.paper.profile.PlayerProfile>>
object, which can be used to retrieve the List<com.destroystokyo.paper.profile.PlayerProfile>
object when the API call is complete.
Developer's Note:
The AsyncPlayerProfileArgument
returns a CompletableFuture<List<org.bukkit.profile.PlayerProfile>>
object, which can be used to retrieve the List<org.bukkit.profile.PlayerProfile>
object when the API call is complete.
Example - Checking if a player has joined before
Example - Checking if a player has joined before
Say we want to create a command that tells us if a player has joined the server before. We can use the AsyncPlayerProfileArgument
to fetch the List<PlayerProfile>
object asynchronously. That way we simply wait for the request to complete, and once it does, we can check if the player has joined the server before. We want to create a command of the following form:
/playedbefore <player>
We now want to get the CompletableFuture<List<PlayerProfile>>
object from the AsyncPlayerProfileArgument
and then use it to get the List<PlayerProfile>
object. We can define it like this:
new CommandAPICommand("playedbefore")
.withArguments(new AsyncPlayerProfileArgument("player"))
.executes((sender, args) -> {
CompletableFuture<List<PlayerProfile>> profiles = (CompletableFuture<List<PlayerProfile>>) args.get("player");
// Directly sends a message to the sender, indicating that the command is running to prevent confusion
sender.sendMessage("Checking if the player has played before...");
profiles.thenAccept(profileList -> {
if (Bukkit.getOfflinePlayer(profileList.getFirst().getId()).hasPlayedBefore()) {
sender.sendMessage("Player has played before");
} else {
sender.sendMessage("Player has never played before");
}
}).exceptionally(throwable -> {
// We have to partly handle exceptions ourselves, since we are using a CompletableFuture
Throwable cause = throwable.getCause();
Throwable rootCause = cause instanceof RuntimeException ? cause.getCause() : cause;
sender.sendMessage(rootCause.getMessage());
return null;
});
})
.register();
new CommandAPICommand("playedbefore")
.withArguments(new AsyncPlayerProfileArgument("player"))
.executes((sender, args) -> {
CompletableFuture<List<PlayerProfile>> profiles = (CompletableFuture<List<PlayerProfile>>) args.get("player");
// Directly sends a message to the sender, indicating that the command is running to prevent confusion
sender.sendMessage("Checking if the player has played before...");
profiles.thenAccept(profileList -> {
if (Bukkit.getOfflinePlayer(profileList.getFirst().getUniqueId()).hasPlayedBefore()) {
sender.sendMessage("Player has played before");
} else {
sender.sendMessage("Player has never played before");
}
}).exceptionally(throwable -> {
// We have to partly handle exceptions ourselves, since we are using a CompletableFuture
Throwable cause = throwable.getCause();
Throwable rootCause = cause instanceof RuntimeException ? cause.getCause() : cause;
sender.sendMessage(rootCause.getMessage());
return null;
});
})
.register();
We now successfully ran a command that asynchronously checks if a player has joined the server before without blocking the main thread despite making an API call.
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
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
new CommandAPICommand("spawnmob")
.withArguments(new EntityTypeArgument("entity"))
.withArguments(new IntegerArgument("amount", 1, 100)) // Prevent spawning too many entities
.executesPlayer((Player player, CommandArguments args) -> {
for (int i = 0; i < (int) args.get("amount"); i++) {
player.getWorld().spawnEntity(player.getLocation(), (EntityType) args.get("entity"));
}
})
.register();
new CommandAPICommand("spawnmob")
.withArguments(new EntityTypeArgument("entity"))
.withArguments(new IntegerArgument("amount", 1, 100)) // Prevent spawning too many entities
.executesPlayer((Player player, CommandArguments args) -> {
for (int i = 0; i < (int) args.get("amount"); i++) {
player.getWorld().spawnEntity(player.getLocation(), (EntityType) args.get("entity"));
}
})
.register();
Note how in this example above, we have to explicitly state Player player, CommandArguments args
. This is due to a limitation of Java's type inference system which is discussed here.