This document outlines the new (post 9.0.0) annotations API specification. This describes what will be implemented, what features will be present, how the new annotations will look and other important stuff
The current annotations API was a proof-of-concept showcasing how compile-time command declaration is a feasible task that will work for the CommandAPI. Since the introduction of the annotations API, the CommandAPI has added more and more increasingly more complex features that have not been ported to the annotations API. As such, the annotations API is lacking in important features that make the CommandAPI what it is today.
Notably, this annotation system suffers from requiring everything to be static!
@Command("warp")
public class WarpCommand {
// List of warp names and their locations
static Map<String, Location> warps = new HashMap<>();
@Default
public static void warp(CommandSender sender) {
sender.sendMessage("--- Warp help ---");
sender.sendMessage("/warp - Show this help");
sender.sendMessage("/warp <warp> - Teleport to <warp>");
sender.sendMessage("/warp create <warpname> - Creates a warp at your current location");
}
@Default
public static void warp(Player player, @AStringArgument String warpName) {
player.teleport(warps.get(warpName));
}
@Subcommand("create")
@Permission("warps.create")
public static void createWarp(Player player, @AStringArgument String warpName) {
warps.put(warpName, player.getLocation());
}
}
Features that have been implemented so far:
- Declaring a command in a file (one command per file)
- Declaring a 'default' implementation (e.g.
/warp
) - Declaring subcommands, such as
/warp create <args>
- Permissions with permission nodes
- Permissions for operators (via
@NeedsOp
) - Help, via
@Help
Features that exist in the CommandAPI and haven't been implemented so far:
- Requirements (more powerful permissions)
- Suggestions:
- With tooltips
- Asynchronous
- Custom arguments
- Other argument properties, including:
- Argument listing
- Optional arguments
- TeamArgument, ObjectiveArgument class changes
- Probably also the various other arguments that don't have annotations such as the list and map arguments
Issue CommandAPI#292 requests multiple command executors. The CommandAPI can support multiple command executors via the various .executesPlayer
, .executesConsole
etc. methods.
The annotation API could accommodate that using overloading using a different parameter type:
@Command("warp")
public class WarpCommand {
// List of warp names and their locations
Map<String, Location> warps = new HashMap<>();
@Default
public void warp(Player player, @AStringArgument String warpName) {
player.teleport(warps.get(warpName));
}
+ @Default
+ public void warp(ConsoleCommandSender console, @AStringArgument String warpName) {
+ console.sendMessage("This command can't be run from the console!");
+ }
}
For multiple executors, it may be possible to use the ExecutorType
like this:
@Command("warp")
public class WarpCommand {
// List of warp names and their locations
Map<String, Location> warps = new HashMap<>();
@Default
+ public void warp(@Senders({ ExecutorType.PLAYER, ExecutorType.ENTITY }) CommandSender sender, @AStringArgument String warpName) {
+ ((Entity) sender).teleport(warps.get(warpName));
}
}
- Commands that don't need the
static
modifier on their methods
- An example of a new command using this system can be found here.
Commands consist of an (ideally) public class with the @Command
annotation. The @Command
annotation specifies the command's name. We'll call this the "command class".
Inside the command class, class field members can be specified with argument annotations, for example:
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
// ...
}
These arguments are arguments that are present for all commands in the class. Arguments are accessed in declaration order, so the following will only allow /mycommand <name> <value>
, and not /mycommand <value> <name>
:
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
@AIntegerArgument
int value;
// ...
}
Due to the nature of command class arguments, the declaration location does not matter when it comes to subcommands. The following two code blocks will yield the same command /mycommand <name> <value> subcommand
:
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
@AIntegerArgument
int value;
@Subcommand("subcommand")
class Subcommand {
// Executor
}
}
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
@Subcommand("subcommand")
class Subcommand {
// Executor
}
@AIntegerArgument
int value;
}
In the dev/annotations
example, subcommand executors were implemented using @Subcommand
on the method itself. I think it would be better to use a different annotation to avoid misleading syntax. For example, the following allows /mycommand <name> subcommand
:
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
@Subcommand("subcommand")
class Subcommand {
@Executes
public void myMethod(Player player) {
// ...
}
}
}
Per-method arguments are still valid, allowing /mycommand <name> subcommand <value>
:
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
@Subcommand("subcommand")
class Subcommand {
@Executes
public void myMethod(Player player, @AIntegerArgument int value) {
// ...
}
}
}
It may be desirable to add subcommands without creating a giant tree. This can be done by applying (a repeatable) @Subcommand
annotation to the executable method. This example shows /mycommand <name> subcommand subsubcommand <value>
:
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
@Subcommand("subcommand")
class Subcommand {
@Subcommand("subsubcommand")
@Executes
public void myMethod(Player player, @AIntegerArgument int value) {
// ...
}
}
}
This could also be implemented on the class-level:
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
@Subcommand("subcommand")
@Subcommand("subsubcommand")
class Subcommand {
@Executes
public void myMethod(Player player, @AIntegerArgument int value) {
// ...
}
}
}
Currently not planned unless demand requires this feature.
Since suggestions are implemented as functional interfaces, we can simply bind functional interface implementations to corresponding arguments via class references.
Arguments with suggestions requires @Suggests
, and classes that implement suggestions requires @Suggestion
. These "pairs" will be checked at compile time. Since we can observe all matching pairs (each @Suggests
should have a corresponding @Suggestion
), we can also provide compiler warnings for unused suggestions.
The compiler should error if @Suggestion
does not implement the correct type.
This command represents /mycommand <name>
, and the suggestions for <name>
will be Player1
, Player2
and Player3
:
@Command("mycommand")
public class MyCommand {
@AStringArgument
@Suggests(ListOfNames.class)
String name;
@Executes
public void myExecutor(Player player) {
// ...
}
@Suggestion
class ListOfNames implements Supplier<ArgumentSuggestions> {
@Override
public ArgumentSuggestions get() {
return ArgumentSuggestions.strings("Player1", "Player2", "Player3");
}
}
}
Subcommands need not be declared in the command class. They can be declared in their own class. To link these together, the CommandAPI can use a @ExternalSubcommand
annotation (actual name TBD) which takes in the class reference of the subcommand. The external subcommand must have its corresponding @Subcommand
declaration on the class level. The external subcommand class must inherit the parent command in order to inherit global class variables. For example, Subcommand
can access name
because it extends MyCommand
:
@Command("mycommand")
public class MyCommand {
@AStringArgument
String name;
@ExternalSubcommand(Subcommand.class)
Subcommand subcommand;
}
@Subcommand("subcommand")
public class Subcommand extends MyCommand {
@Executes
public void myMethod(Player player, @AIntegerArgument int value) {
// ...
}
}
Aliases should no longer be provided using the @Alias
annotation - it gets too messy. Consider adding it as a parameter to the @Command
annotation.