Back in the olden days when the Sponge command system (CommandSpec and the like) were just getting started, the SpongeAPI changed the name of one of the classes associated with this system. Being the usual idiot I am, I assumed they removed command functionality and wrote my own parser. Three months later, I re-wrote it using my configuration language. (Basically, my config language will accept values separated by spaces, because reasons (It also accepts JSON input)). Now it’s evolved quite a bit more than I expected, and I was wondering if anyone had any thoughts on how it works or how I could improve it.
My command system, unlike Sponge’s, does not parse values into just any object (although you could do some weird stuff and get it to, it’s not supported in the default implementation). What it does do, however, is allow you to assume certain things are true. For example, you could ask the system to ensure a certain argument is a string representation of a UUID, but you’d still be accessing the String object.
The system uses a “CommandHandler” which defines the aliases, help, and description all in that class. I personally find that easy to work with. Furthermore, usage strings are automatically generated based on expected arguments. The CommandHandlers can then be used for two things, they can either be translated to a sponge CommandCallable, or to a Conversation. Some of you may remember Bukkit’s conversation API, I implemented a basic Conversation API in my plugin. The conversation allows the user to enter each argument individually, this can be incredibly useful for long commands. Another feature is the ease with which command structures can be built; since all the information pertaining to help and aliases exist within the command class itself, a “CommandTree” can be built like so:
CommandTree tree = CommandTree.builder(Text.of("Main StarAPI command"), Text.of("Main StarAPI command"), "star") .node(Text.of("Main StarAPI enchantments command"), Text.of("Main StarAPI enchantments command"), "enchant") .node(new EnchantsCommand()) .node(new ClearEnchantsCommand()) .node(new EnchantCommand()) .parent() .designateHelp() .forcePluginHelp() .build(); CommandTree conversation = CommandTree.builder(Text.of("Main StarAPI Conversation command"), Text.of("Main StarAPI Conversation command"), "conversation") .node(new ConversationStartCommand()) .node(new ConversationEndCommand()) .designateHelp() .build(); PluginCommandTree.builder(this) .node(tree) .node(conversation) .help("help") .build() .register();
The above structure builds two CommandTrees, and one PluginCommandTree. The idea is that a CommandTree represents a single command, with potential sub-commands. In theory, only one PluginCommandTree should exist per plugin, and it servers the purpose of collecting up all the plugin’s commands and registering. In addition, the trees automatically generate help commands wherever you ask them to - the option “designateHelp” on a CommandTree will register a help-command as a sub-command of that specific tree, and “forcePluginHelp” will ensure the help command refers to the root plugin help. The option “help” on PluginCommandTree specifies the aliases to use for the various help commands that are to be registered. You can also register help commands directly on PluginCommandTree, as flat commands (with no parent command). The nice thing about the help commands is the interface the generate for help. I got the idea from @TrenTech’s plugins, but I wrote all the code for my system without looking at his code. An example of the interface is below:
A system like this is not without its problems. A couple are:
- Unlike CommandSpec, MainCommands cannot have their own action if no sub-command was found
- The error messages can sometimes be unclear
- Without CommandTree, it’s hell to write help and main command structures
The End (Or Is It?)
So, if you made it this far, you must be willing to leave some thoughts behind! I wanted to get input on this because I don’t really like deviated from the sponge system, but I like my own system even more. I might make another post like this discussing my configuration language, because, although it accepts JSON input, it’s quite complex.
For anyone interested, the code is on github: https://github.com/SocraticPhoenix/StarAPI/tree/master/src/main/java/com/gmail/socraticphoenix/sponge/star/chat