Advanced Usage - LilBroCodes/commander GitHub Wiki
This page covers advanced techniques and patterns for using Commander in more complex scenarios. Once you're comfortable with the basics, these approaches can help you build more sophisticated command systems.
While the standard CommanderCommand.register()
method works for most cases, you can extend CommanderCommand
to customize the initialization process.
public class MyCustomCommand extends CommanderCommand {
private final MyPlugin plugin;
public MyCustomCommand(ExecutorNode<CommandGroupNode> root, boolean tabComplete, MyPlugin plugin) {
super(root, tabComplete);
this.plugin = plugin;
}
@Override
public void initialize(ExecutorNode<CommandGroupNode> root) {
// Add standard help command
CommandActionNode helpNode = createHelpCommand();
((CommandGroupNode) root).addChild(helpNode);
// Add other standard commands
// ...
}
private CommandActionNode createHelpCommand() {
// Custom help command implementation
// ...
}
}
You can build command trees dynamically based on configuration, permissions, or other factors.
public CommandGroupNode buildCommandTree() {
CommandGroupNode rootNode = new CommandGroupNode(
"myplugin",
"MyPlugin commands",
"MyPlugin"
);
// Add standard commands
rootNode.addChild(createVersionCommand());
// Add commands based on configuration
if (config.isFeatureEnabled("economy")) {
rootNode.addChild(createEconomyCommands());
}
// Add admin commands if server has permission system
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
rootNode.addChild(createAdminCommands());
}
return rootNode;
}
Commander doesn't directly support command aliases within its tree structure, but you can implement them by registering multiple commands that share the same executor node.
// Create the command tree
CommandGroupNode rootNode = new CommandGroupNode(
"myplugin",
"MyPlugin commands",
"MyPlugin"
);
// Add subcommands
// ...
// Register the main command
CommanderCommand mainCommand = new CommanderCommand(rootNode, true);
mainCommand.register(this, "myplugin");
// Register aliases
CommanderCommand aliasCommand1 = new CommanderCommand(rootNode, true);
aliasCommand1.register(this, "mp");
CommanderCommand aliasCommand2 = new CommanderCommand(rootNode, true);
aliasCommand2.register(this, "mplugin");
To create aliases for subcommands, you can add multiple command nodes that execute the same logic.
// Create the main subcommand
CommandActionNode teleportCommand = new CommandActionNode(
"teleport",
"Teleport to a location",
"MyPlugin",
parameters,
executor
);
// Create an alias with the same executor
CommandActionNode tpCommand = new CommandActionNode(
"tp",
"Teleport to a location (alias)",
"MyPlugin",
parameters,
executor
);
// Add both to the parent
rootNode.addChild(teleportCommand);
rootNode.addChild(tpCommand);
For commands that share similar parameters, you can define them once and reuse them.
// Define common parameters
public static final TypedParameter PLAYER_PARAM = new TypedParameter(
"player",
ParameterType.STRING,
() -> Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList())
);
public static final TypedParameter AMOUNT_PARAM = new TypedParameter(
"amount",
ParameterType.INT,
() -> List.of("1", "5", "10", "50", "100")
);
// Use them in multiple commands
CommandActionNode giveMoneyCommand = new CommandActionNode(
"give",
"Give money to a player",
"MyPlugin",
List.of(PLAYER_PARAM, AMOUNT_PARAM),
(sender, args) -> {
// Command logic
}
);
CommandActionNode takeMoneyCommand = new CommandActionNode(
"take",
"Take money from a player",
"MyPlugin",
List.of(PLAYER_PARAM, AMOUNT_PARAM),
(sender, args) -> {
// Command logic
}
);
For plugins with many similar commands, you can create factory methods to reduce repetition.
public CommandActionNode createToggleCommand(String name, String description, String permission, Consumer<Player> action) {
return new CommandActionNode(
name,
description,
"MyPlugin",
List.of(),
(sender, args) -> {
if (!(sender instanceof Player player)) {
sender.sendMessage("This command can only be used by players");
return;
}
action.accept(player);
}
).withPermission(permission);
}
// Usage
CommandGroupNode togglesNode = new CommandGroupNode("toggle", "Toggle features", "MyPlugin");
toggleNode.addChild(createToggleCommand(
"fly",
"Toggle flight mode",
"myplugin.toggle.fly",
player -> {
player.setAllowFlight(!player.getAllowFlight());
player.sendMessage("Flight mode: " + (player.getAllowFlight() ? "enabled" : "disabled"));
}
));
toggleNode.addChild(createToggleCommand(
"god",
"Toggle god mode",
"myplugin.toggle.god",
player -> {
boolean godMode = !player.isInvulnerable();
player.setInvulnerable(godMode);
player.sendMessage("God mode: " + (godMode ? "enabled" : "disabled"));
}
));
While Commander doesn't directly support custom parameter types, you can implement them by using the existing types and performing additional validation in your executor.
// Define a UUID parameter
TypedParameter uuidParam = new TypedParameter(
"uuid",
ParameterType.STRING,
() -> {
// Provide some example UUIDs for tab completion
return Bukkit.getOnlinePlayers().stream()
.map(Player::getUniqueId)
.map(UUID::toString)
.collect(Collectors.toList());
}
);
// Create a command that uses the UUID parameter
CommandActionNode lookupCommand = new CommandActionNode(
"lookup",
"Look up a player by UUID",
"MyPlugin",
List.of(uuidParam),
(sender, args) -> {
String uuidString = (String) args.get(0);
// Validate the UUID format
try {
UUID uuid = UUID.fromString(uuidString);
// Look up the player
OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
sender.sendMessage("Player: " + player.getName());
} catch (IllegalArgumentException e) {
sender.sendMessage("Invalid UUID format");
}
}
);
Commander can be integrated with other plugins to extend its functionality.
public boolean hasPermission(CommandSender sender, String permission) {
if (vaultPermissions != null) {
if (sender instanceof Player player) {
return vaultPermissions.has(player, permission);
}
}
// Fall back to Bukkit permissions
return sender.hasPermission(permission);
}
// Create a command with custom permission checking
CommandActionNode adminCommand = new CommandActionNode(
"admin",
"Admin commands",
"MyPlugin",
List.of(),
(sender, args) -> {
// Command logic
}
) {
@Override
public boolean hasPermission(CommandSender sender) {
return MyPlugin.getInstance().hasPermission(sender, "myplugin.admin");
}
};
public String parsePlaceholders(Player player, String message) {
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
return PlaceholderAPI.setPlaceholders(player, message);
}
return message;
}
// Create a command that uses placeholders
CommandActionNode infoCommand = new CommandActionNode(
"info",
"Show player info",
"MyPlugin",
List.of(),
(sender, args) -> {
if (!(sender instanceof Player player)) {
sender.sendMessage("This command can only be used by players");
return;
}
String message = "Your health: %player_health%, Your level: %player_level%";
player.sendMessage(parsePlaceholders(player, message));
}
);
For large command trees, you can use lazy initialization to improve startup performance.
public class MyPlugin extends JavaPlugin {
private CommandGroupNode rootNode;
@Override
public void onEnable() {
// Initialize only the root node
rootNode = new CommandGroupNode(
"myplugin",
"MyPlugin commands",
"MyPlugin"
);
// Register the command
CommanderCommand command = new LazyCommanderCommand(rootNode, true, this);
command.register(this, "myplugin");
}
// Lazy initialization of command tree
private class LazyCommanderCommand extends CommanderCommand {
private final MyPlugin plugin;
private boolean initialized = false;
public LazyCommanderCommand(ExecutorNode<CommandGroupNode> root, boolean tabComplete, MyPlugin plugin) {
super(root, tabComplete);
this.plugin = plugin;
}
@Override
public void initialize(ExecutorNode<CommandGroupNode> root) {
if (initialized) return;
// Initialize the full command tree
CommandGroupNode groupNode = (CommandGroupNode) root;
groupNode.addChild(createVersionCommand());
groupNode.addChild(createAdminCommands());
// Add more commands...
initialized = true;
}
}
}
For expensive tab completion operations, you can cache the results.
public class TabCompletionCache {
private final Map<String, List<String>> cache = new HashMap<>();
private final Map<String, Long> lastUpdated = new HashMap<>();
private final long cacheTimeMs;
public TabCompletionCache(long cacheTimeMs) {
this.cacheTimeMs = cacheTimeMs;
}
public List<String> getOrCompute(String key, Supplier<List<String>> supplier) {
long now = System.currentTimeMillis();
Long lastUpdate = lastUpdated.get(key);
if (lastUpdate == null || now - lastUpdate > cacheTimeMs) {
List<String> result = supplier.get();
cache.put(key, result);
lastUpdated.put(key, now);
return result;
}
return cache.get(key);
}
public void invalidate(String key) {
cache.remove(key);
lastUpdated.remove(key);
}
public void invalidateAll() {
cache.clear();
lastUpdated.clear();
}
}
// Usage
TabCompletionCache cache = new TabCompletionCache(30000); // 30 seconds
TypedParameter worldParam = new TypedParameter(
"world",
ParameterType.STRING,
() -> cache.getOrCompute("worlds", () -> {
// Expensive operation to get all world names
return Bukkit.getWorlds().stream()
.map(World::getName)
.collect(Collectors.toList());
})
);
Commander provides a solid foundation for building command systems in your Bukkit/Spigot/Paper plugins. By leveraging these advanced techniques, you can create more sophisticated, maintainable, and user-friendly command interfaces for your players.
Remember that good command design is about more than just the technical implementation—it's about creating an intuitive and consistent experience for your users. Take the time to plan your command structure, use clear naming conventions, and provide helpful descriptions and tab completion suggestions.