Annotation-based Hierarchical Plugin Command API

When a player sends a chat message from the client to the server beginning with a forward-slash ("/") the server treats it as a command and is processed accordingly. In vanilla Minecraft, an example might be "/tp ScarabCoder" where the command is "tp" and the first argument (arguments normally start after the command) is the player's name. As far as simple commands like that go, it's easy enough for the server to check for a player by the name of the second argument and if it exists simply teleport the command sender to the player it found.

Sometimes, though, commands aren't that simple. A good example of a much more advanced command is the /scoreboard command in vanilla Minecraft. It handles teams, certain elements on the HUD, and a few aspects around the player. The "root" command is /scoreboard, but it can be considered to have multiple commands with every section being a different command. For example, if you wanted to modify teams in any way you would run "/scoreboard teams <arguments>" (<> is commonly used to specify an argument name) would be the command to modify teams in some way. A more complete example:

/scoreboard teams add Test
/scoreboard teams remove Test
/scoreboard teams join Test

At that point, it's just as though the first argument ("teams") is just a specifying another section inside the scoreboard command. Unfortunately, the programming behind that is not treated as such. In reality, there are a ton of "if" statements checking for each possible argument, sometimes "sections" within sections. It can quickly build up and become a mess. This is a command ("/ticket" base) which I created for a plugin which allowed users to open support tickets. This command is only the moderator side, and contains the following usages:

/ticket close <id>
/ticket tp <id>
/ticket flag <id> <flag>
/ticket comment <id> <message>

The amount of code that I had to write, however, was insane. There were a ton of "if" checks to make sure the amount of arguments passed were in the correct amount, that tickets with a given ID existed, etc. Here's a screenshot of what it looked like, with a much smaller font size. It still doesn't show the entire command class, though.

LongCommands.png

About 80% of that are "if" statements or simply parsing statements to get a ticket object out of the ids given. This is stuff that must be done repeatedly, exactly something a computer should be doing instead of me. 

I've been working on a new large gamemode in which I try to use the best possible practices and create the cleanest code, at the cost of the time it takes to learn and write that code. One thing I was settled on was developing a better API for handling large commands and sections inside those commands.

This new API uses a hierarchy to manage sections, meaning a "teams" section would be inside the "scoreboard" section, in separate classes. Inside each section is a command, which requires arguments that are actually used as arguments and not used to go into another section. For example, the team command would be treated something like this:

scoreboard
    -> teams
        -> create(name)
        -> remove(name)

Where each command is treated something like a programming function. In fact, each command in my API is actually taken from a function, meaning not only is it easy to sort out where commands are it also acts exactly like a function would. For example, inside the TeamsSection class (where all commands inside the teams class is reachable via /scoreboard teams) there would be the create function:

CreateExample.PNG

You can specify pretty much whatever you want the command to have using the annotation's (optional) parameters. When a player executes "/scoreboard teams create test", the function is called with the first argument being the player's name and the second argument being "test" (the team name). Little to no manual processing has to be done, making it extremely easy to add commands. 

It also supports custom type conversion. This means that a parameter in the command function can be an Integer, and if it isn't a message is sent to the player and the function is never called. You don't have to convert the String to a Player as a username, you don't have to make sure that a string can be converted to an integer, it's all automated. You can also add your own converters, which in the ticket example I gave above would allow you to take a Ticket object as a parameter without having to check in every function that a string can be converted to an existing Ticket object. 

The final little feature is that it registers the command with Bukkit automatically, meaning you don't need to add any of the command's details in the plugin.yml. This is especially useful, since the API also allows you to specify aliases and descriptions and it's nice to have that all in one place.