-
Notifications
You must be signed in to change notification settings - Fork 0
Commands API
#Welcome This API easily allows you to create commands for your plugin, without:
- Command and permission entries in your plugin.yml
- Long argument checking and duplicated error messages
Let's get started!
Maybe it'll be easier to just teach by example.
public class MyNewCommand {
@Command ( // Marks the following method as one that handles a specific command
/*
Array of commands that, when run on the server, will run the folowing method.
The first listed will be the main command name; the rest will be aliases.
If this is not set at all, _the method name will be the command name._
*/
names = { "sayhello", "hello", "sh" },
/*
Uses the default permissions implementation (Player#hasPermission()) to check if the player has the specified permission node.
If they do not, they get a permission denied message.
Default: "general.*"
*/
permission = "helloworld.sayhello",
/*
Sets the message a player receives when running a command they don't have permission to run.
Default: "&cRunning the command failed: insufficient permission."
*/
noPerms = "&cRunning the command failed: insufficient permission.",
/*
Sets the message a player receives when they run the command improperly.
Default: "§cThere is no usage information."
*/
usage = "&7Usage: /sayhello # Sends \"Hello, world!\" back to you.",
/*
Sets whether the command is player-only (console cannot run it)
Default: true
*/
playerOnly = true
)
public void sayHello (
@Argument (
/*
This argument will be set with an instance of the object running the command. Required.
*/
dataType = Argument.Data.SENDER,
/*
This argument is guaranteed non-null. Required.
*/
nullable = false
)
/*
The type of instance you want. Must be a Player in this case (playerOnly is set to true), but could also be a CommandSender (in which case you'll need to check type yourself).
*/
Player sender
) {
sender.sendMessage("Hello, world!");
}
}See? Simple! Here's all of that code without the annoying comments.
public class MyNewCommand {
@Command {
names = { "sayhello", "hello", "sh" },
permission = "helloworld.sayhello",
noPerms = "&cRunning the command failed: insufficient permission.",
usage = "&7Usage: /sayhello # Sends \"Hello, world!\" back to you.",
playerOnly = true
}
public void sayHello ( @Argument (dataType = Argument.Data.SENDER, nullable = false) Player sender ) {
sender.sendMessage("Hello, world!");
}
}We need to let Spigot know about our new commands. Let's start by registering this hello command, and then we'll go over custom argument types.
In your main class (or wherever you choose to register commands), you need to make a CommandLoader instance. This keeps track of all commands and handles loading commands. To load our hello command:
new CommandLoader().loadCommands(new MyNewCommand());Now, what happens if you have a command that takes an argument of a type other than CommandSender, Player, Integer, World, or String? You'll need to write your own ComplexArgumentParser. Let's see how we do this for the World type:
class WorldParser extends ComplexArgumentParser {
@Override
public int getRequiredArguments() {
/*
* This argument type only requires one word in the command: the world name.
* If it needed two (maybe for a range of numbers or something), this would return 2, and so on.
*/
return 1;
}
@Override
public Object parseInput(String... args) { // The String[] is what the player has typed.
return Bukkit.getWorld(args[0]); // Perform logic here that converts the String(s) to the desired type
}
}Seems simple enough. Let's look at parsing an enum, for arguments to a command (different modes). The following is an enum I wrote for multiple modes on a /jobs command:
public enum JobsCommandMode {
LOADBAL("loadbal", "loadbalance", "lb"),
LIST("list", "l", "all"),
ACTIVE("active", "enabled", "e"),
JOIN("join", "j", "add", "a"),
LEAVE("leave", "quit", "q", "resign");
ArrayList<String> aliases;
JobsCommandMode(String... aliases) {
this.aliases = new ArrayList<>(Arrays.asList(aliases));
}
public ArrayList<String> getAliases() {
return aliases;
}
}With this enum, we have created multiple modes, each with their own aliases, for this command. Now, let's see what our command function will look like:
public class JobsCommand {
@Command(names = {"jobs"}, usage = "/jobs <arg> &7# Run the main Jobs command")
public void jobs(@Argument(dataType = Argument.Data.SENDER, nullable = false) Player sender,
@Argument(dataType = Argument.Data.ARGUMENT, nullable = false) JobsCommandMode mode,
@Argument(dataType = Argument.Data.ARGSLICE, nullable = true, argSliceStart = 1) String[] args) {
switch (mode) {
case LOADBAL:
// Do stuff
break;
case ACTIVE:
// Do more stuff
break;
case LIST:
// Must I continue?
break;
...What we want is a JobsCommandMode that is already chosen based on the player's input. We don't want to worry about capitalization, aliases, etc. We just want to handle commands. So, without further ado, let's see how we parse this mess:
public class JobsCommandModeParser extends ComplexArgumentParser {
@Override
public int getRequiredArguments() {
return 1; // Again, we only need one word from the player.
}
@Override
public Object parseInput(String... strings) {
return Arrays.stream(JobsCommandMode.values()) // iterate over the enum...
.filter(mode -> mode.aliases.contains(strings[0])).findFirst().orElse(null); // ..and find the first matching command or alias, and if we don't find one, return null.
}
}The awesome part of this is that if our argument is not nullable and we return null from this parser (the player entered an invalid argument), your function will not even be called. The player will instead receive the usage information defined for the command.
Now we still need to make sure our CommandLoader instance is aware of these extra arguments. Let's see how that's done for both our previous /hello command and our new /jobs commands:
ComplexArgumentParserManager capm = new ComplexArgumentParserManager();
/// The first argument is the type we want parsed, i.e. the type we want to get in our command function
// The second is the parser for that type
capm.addParser(JobsCommandMode.class, new JobsCommandModeParser());
new CommandLoader().loadCommands(capm, new JobsCommand(), new MyNewCommand());