A few words to introduce the situation.
Context:. To make my workflow easier when writing Bukkit plugins (basically the de facto API for the Minecraft server until Sponge starts to implement it), I decided to put together a “mini-framework” so I don’t have to repeat the same tasks again and again. (Also, I am trying to develop it so as not to depend too much on Bukkit, so I can continue to use it on Sponge just by changing my implementation)
Intention: Managing teams at Bukkit is, frankly, a mess. You have to define your root command (for example, you want to run / test ingame, "test" is the root directory) in the YML file (instead of calling some factory?), The processing of the child commands is nonexistent and implements the details are hidden, so getting 100 % reliable results are difficult. This is the only part of Bukkit that annoys me, and it was the main initiator of me, who decided to write a structure.
Purpose: Give up the nasty Bukkit management team and replace it with something that clears up.
Work with her:
This will be a long paragraph, where I will explain how Bukkit command processing is initially performed, as it will provide a deeper understanding of important command parameters, etc.
Any user connected to the Minecraft server can start a chat message using '/', which will lead to its analysis as a command.
To offer an example of the situation, any player in Minecraft has a life bar, which by default has a limit of 10 hearts and is depleted when dealing damage. The maximum and current "heart" (read: health) can be set by the server at any time.
Suppose we want to define a command like this:
/sethealth <current/maximum> <player or * for all> <value>
To start implementing this ... oh boy. If you like clean code, I would say skip this ... I will comment to explain, and whenever I feel that Bukkit made a mistake.
Required plugin.yml:
The main class of the plugin:
package com.gmail.zkfreddit.sampleplugin; import org.bukkit.command.PluginCommand; import org.bukkit.plugin.java.JavaPlugin; public class SampleJavaPlugin extends JavaPlugin {
Executor:
package com.gmail.zkfreddit.sampleplugin; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; public class SampleCommandExecutor implements CommandExecutor { private static enum HealthOperationType { CURRENT, MAXIMUM; public void executeOn(Player player, double newHealth) { switch (this) { case CURRENT: player.setHealth(newHealth); break; case MAXIMUM: player.setMaxHealth(newHealth); break; } } } @Override public boolean onCommand( //The sender of the command - may be a player, but might also be the console CommandSender commandSender, //The command object representing this command //Why is this included? We know this is our SetHealth executor, //so why add this as another parameter? Command command, //This is the "label" of the command - when a command gets registered, //it name may have already been taken, so it gets prefixed with the plugin name //(example: 'sethealth' unavailable, our command will be registered as 'SamplePlugin:sethealth') String label, //The command arguments - everything after the command name gets split by spaces. //If somebody would run "/sethealth acb", this would be {"a", "c", "b"}. String[] args) { if (args.length != 3) { //Our command does not match the requested form {"<current/maximum>", "<player>", "<value>"}, //returning false will, ladies and gentleman... //display the usage message defined in plugin.yml. Hooray for some documented code /s return false; } HealthOperationType operationType; double newHealth; try { //First argument: <current/maximum> operationType = HealthOperationType.valueOf(args[0].toUpperCase()); } catch (IllegalArgumentException e) { return false; } try { //Third argument: The new health value newHealth = Double.parseDouble(args[2]); } catch (NumberFormatException e) { return false; } //Second argument: Player to operate on (or all) if (args[1].equalsIgnoreCase("*")) { //Run for all players for (Player player : Bukkit.getOnlinePlayers()) { operationType.executeOn(player, newHealth); } } else { //Run for a specific player Player player = Bukkit.getPlayerExact(args[1]); if (player == null) { //Player offline return false; } operationType.executeOn(player, newHealth); } //Handled successfully, return true to not display usage message return true; } }
Now you can understand why I decided to abstract the processing of commands within my framework. I do not think that I am lonely, thinking that this method is not self-documenting and processing child commands in this way does not seem to be correct .
My intention:
How Bukkit event system works , I want to develop a framework / API to distract it.
My idea annotates the command methods with the corresponding annotation, which includes all unclassified information, and use some kind of registrar to register the command (in case of an event: Bukkit.getPluginManager().registerEvents(Listener, Plugin) ).
Again similar to the event API, command methods will have a specific signature. Since considering several parameters is annoying, I decided to pack it all in the context interface (also, thus, I do not violate all the previous code if I need to add something to the context!). However, I also need a return type if I want to quickly display usage (but I'm not going to choose a boolean, that's for sure!) Or do some other things. So, the signature of my idea comes down to CommandResult <anyMethodName>(CommandContext) .
Then, command registration created command instances for annotated methods and registered them.
My basic plan has taken shape. Please note that I have not come to writing JavaDoc yet, I added some quick comments to non-self-documenting code.
Team Registration:
package com.gmail.zkfreddit.pluginframework.api.command; public interface CommandRegistration { public static enum ResultType { REGISTERED, RENAMED_AND_REGISTERED, FAILURE } public static interface Result { ResultType getType();
Listing the result of a command:
package com.gmail.zkfreddit.pluginframework.api.command; public enum CommandResult {
Command Context:
package com.gmail.zkfreddit.pluginframework.api.command; import org.bukkit.command.CommandSender; import java.util.List; public interface CommandContext { CommandSender getSender(); List<Object> getArguments(); @Deprecated String getLabel(); @Deprecated
The main annotation of a command that should be placed in command methods:
package com.gmail.zkfreddit.pluginframework.api.command; import org.bukkit.permissions.PermissionDefault; public @interface Command { public static final String DEFAULT_STRING = ""; String name(); String description() default DEFAULT_STRING; String usageMessage() default DEFAULT_STRING; String permission() default DEFAULT_STRING; PermissionDefault permissionDefault() default PermissionDefault.TRUE; Class[] autoParse() default {}; }
The goal of autoParse is that I can determine something quickly, and if the parsing fails, it just displays a message about using the command.
Now, as soon as my implementation is written down, I can rewrite the sethealth command executor mentioned above to something like this:
package com.gmail.zkfreddit.sampleplugin; import de.web.paulschwandes.pluginframework.api.command.Command; import de.web.paulschwandes.pluginframework.api.command.CommandContext; import org.bukkit.entity.Player; import org.bukkit.permissions.PermissionDefault; public class BetterCommandExecutor { public static enum HealthOperationType { CURRENT, MAXIMUM; public void executeOn(Player player, double newHealth) { switch (this) { case CURRENT: player.setHealth(newHealth); break; case MAXIMUM: player.setMaxHealth(newHealth); break; } } } @Command( name = "sethealth", description = "Set health values for any or all players", usageMessage = "/sethealth <current/maximum> <player/* for all> <newHealth>", permission = "sampleplugin.sethealth", autoParse = {HealthOperationType.class, Player[].class, Double.class}
I believe that what I most say is that this method feels cleaner.
So where do I ask the question here?
Where am i stuck
Handling child commands.
In this example, I managed to get away with a simple enumeration based on two cases for the first argument.
There may be times when I have to create many child commands, similar to "current / maximum". A good example is that which allows you to bring players together in a team - I need:
/team create ... /team delete ... /team addmember/join ... /team removemember/leave ...
etc .. - I want to be able to create separate classes for these child commands.
How exactly am I going to introduce a clean way of saying, “Hey, when the first argument of this matches something, do this and that!” - hell, the "agreed" part doesn't even have to be a hard-coded string, I might need something like
/team [player] info
at the same time, while retaining all previous child commands.
Not only do I need to bind child command methods, I also need to somehow bind the required object - in the end, my (future) command registration will take an instance of the object (in the example BetterCommandExecutor example) and register This. As I say, "Use this instance of the child command!" to register when passing the facility?
I was thinking about saying "**** everything, a reference to the child command class and just instantiating the no-args constructor", but while this will probably produce the smallest code, it won’t give much insight into how exactly instances of child teams. If I decide to go this way, I just define the childs parameter in my Command annotation and make some @ChildCommand annotation list for it (annotations in the annotation? Yo dawk, why not?).
So, after all this, the question arises: with this setting, can I clearly define the child commands, or do I need to completely change my base? I was thinking of expanding from a kind of abstract BaseCommand (with the abstract getChildCommands () method), but the annotation method has the advantage of being able to process multiple commands from the same class. Besides, as far as I still understood the open source code, I get the impression that extends is 2011 and implements is the taste of the year, so I should not force myself to renew something every time I create some kind of command handler.
I apologize for the long post. This went on longer than I expected: /
Edit # 1:
I just realized that what I basically create is some kind of ... tree? teams. However, just using any CommandTreeBuilder is not necessary, because it contradicts one of the things I wanted from this idea: Ability to define several command handlers in one class. Back to the brainstorming session.