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
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();
CommandAPICommand("remove")
// Using a collective entity selector to select multiple entities
.withArguments(EntitySelectorArgument.ManyEntities("entities"))
.executes(CommandExecutor { sender, args ->
// Parse the argument as a collection of entities (as stated above in the documentation)
val entities = args["entities"] as Collection<Entity>
sender.sendMessage("Removed ${entities.size} entities")
for (e in entities) {
e.remove()
}
})
.register()
commandAPICommand("remove") {
// Using a collective entity selector to select multiple entities
entitySelectorArgumentManyEntities("entities")
anyExecutor { sender, args ->
// Parse the argument as a collection of entities (as stated above in the documentation)
val entities = args["entities"] as Collection<Entity>
sender.sendMessage("Removed ${entities.size} entities")
for (e in entities) {
e.remove()
}
}
}
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.OnePlayer
. It returns a Player
object and requires the player to be online.
Developer's Note:
The
PlayerArgument
internally uses theGameProfile
class from Mojang's authlib, which means that this argument has a slight performance overhead compared to usingEntitySelectorArgument.OnePlayer
Example - PlayerArgument without entity selectors
When registering a PlayerArgument
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 PlayerArgument
which only suggests the actual names, we can define it like this:
Argument<?> noSelectorSuggestions = new PlayerArgument("target")
.replaceSafeSuggestions(SafeSuggestions.suggest(info ->
Bukkit.getOnlinePlayers().toArray(new Player[0])
));
val noSelectorSuggestions = PlayerArgument("target")
.replaceSafeSuggestions(SafeSuggestions.suggest {
Bukkit.getOnlinePlayers().toTypedArray()
})
Now we can define the rest of the command and include our suggestion inside of it like this:
new CommandAPICommand("warp")
.withArguments(noSelectorSuggestions)
.executesPlayer((player, args) -> {
Player target = (Player) args.get("target");
player.teleport(target);
})
.register();
CommandAPICommand("warp")
.withArguments(noSelectorSuggestions)
.executesPlayer(PlayerCommandExecutor { player, args ->
val target = args["target"] as Player
player.teleport(target)
})
.register()
And there we have it! One thing to note is that entity selectors are still a valid input, they are just not included in the suggestions.
OfflinePlayer argument
The OfflinePlayerArgument
class is identical to the PlayerArgument
class, but instead of returning a Player
object, it returns an OfflinePlayer
object. Internally, this argument makes calls to Mojang servers (via Mojang's authlib), meaning it can be slightly slower than alternative methods (such as using a StringArgument
and suggesting a list of existing offline players).
The OfflinePlayerArgument
should be able to retrieve players that have never joined the server before.
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, CommandArguments args) -> {
for (int i = 0; i < (int) args.get("amount"); i++) {
player.getWorld().spawnEntity(player.getLocation(), (EntityType) args.get("entity"));
}
})
.register();
CommandAPICommand("spawnmob")
.withArguments(EntityTypeArgument("entity"))
.withArguments(IntegerArgument("amount", 1, 100)) // Prevent spawning too many entities
.executesPlayer(PlayerCommandExecutor { player, args ->
for (i in 0 until args["amount"] as Int) {
player.world.spawnEntity(player.location, args["entity"] as EntityType)
}
})
.register()
commandAPICommand("spawnmob") {
entityTypeArgument("entity")
integerArgument("amount", 1, 100) // Prevent spawning too many entities
playerExecutor { player, args ->
for (i in 0 until args["amount"] as Int) {
player.world.spawnEntity(player.location, args["entity"] as EntityType)
}
}
}
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.