Building commands has always been a core concept in many plugins, and, lots of times, a really boring and cumbersome one to pull off: Having to think of all the possible input from the user, all the mistakes they will make, validating input and then finally executing the actual command logic. We aren't supposed to mess our hands up with so much of this. We really shouldn't get ourselves dirty with the highly error-prone string manipulation, nor are we supposed to repeat 3 lines of code a thousand times. We also should not be forced to think of all the edge cases and possible output of the user side. Developers should focus on what's important, not what isn't. Inspired by Aikar's command framework, and made to address its main issues and feature requests, Lamp was born. Lamp on GitHub: https://github.com/Revxrsal/Lamp Wiki: https://github.com/Revxrsal/Lamp/wiki Javadocs: https://revxrsal.github.io/Lamp/ Discord server: https://discord.gg/pEGGF785zp Why Lamp? Glad you asked! Lamp is small in size: The overall size of Lamp will not exceed 150 KB, and is completely dependency-less. Built to be lightweight, Lamp is convenient to package and ship. Lamp is extendable: Lamp has been built thoroughly with this in mind. You can create custom annotations for commands, parameters, permissions and resolvers, with their very own functionality. This gives so much space for your own extendability, and also helps make sure the code you write is minimal. Lamp is portable: Created with a high-level command API and an extendable codebase, Lamp has been produced to provide first-class support to as many platforms as possible. As of now, Lamp supports the following platforms: Bukkit / Spigot BungeeCord VelocityPowered SpongePowered Java Discord API (JDA) Mojang's Brigadier Command line programs (CLI) Lamp is easy: Despite all the powerful features and extendability, getting started with Lamp couldn't be easier. Simply create a command handler for your appropriate platform, then proceed with creating your command with the main @command and @Subcommand annotations, and finally registering it with CommandHandler#register(). Lamp is powerful: Lamp allows you to leverage some of the command features which would be otherwise too burdensome to build: @Switch parameters @Flag (named) parameters Simple dependency injection A quote-aware argument parser Context resolver factories and value resolver factories Simple and powerful auto completions API Built-in command cooldown handler Minecraft's Entity selectors Examples Command to ban a player /epicbans ban <player> <days> <reason> Add -silent to make the ban silent Code (Java): @Command("epicbans ban") public void banPlayer( Player sender, @Range(min = 1) long days, Player toBan, String reason, @Switch("silent") boolean silent ) { if (!silent) Bukkit.broadcastMessage(colorize("Player &6" + toBan.getName() + " &fhas been banned!")); Date expires = new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(days)); Bukkit.getBanList(Type.NAME).addBan(toBan.getName(), reason, expires, sender.getName()); } Commands to switch gamemodes Code (Java): @Command({"gmc", "gamemode creative"}) public void creative(@NotSender @Default("me") Player sender) { sender.setGameMode(GameMode.CREATIVE); } @Command({"gms", "gamemode survival"}) public void survival(@NotSender @Default("me") Player sender) { sender.setGameMode(GameMode.SURVIVAL); } @Command({"gma", "gamemode adventure"}) public void adventure(@NotSender @Default("me") Player sender) { sender.setGameMode(GameMode.ADVENTURE); } @Command({"gmsp", "gamemode spectator"}) public void spectator(@NotSender @Default("me") Player sender) { sender.setGameMode(GameMode.SPECTATOR); } Commands to ping online operators, with 10 minutes delay Code (Java): @Command({"opassist", "opa", "helpop"}) @Cooldown(value = 10, unit = TimeUnit.MINUTES) public void requestAssist(Player sender, String query) { for (Player player : Bukkit.getOnlinePlayers()) { if (player.isOp()) { player.playSound(player.getLocation(), Sound.ENTITY_ARROW_HIT_PLAYER, 1f, 1f); player.sendMessage(colorize("&a" + sender + " &fneeds help: &b" + query)); } } } What issues does Lamp address in other command frameworks? Performance: Most command frameworks with nested commands support provide O(n) performance for looking commands up and routing execution with recursion. As such, the more commands you have, the more time it will take to find the command being executed. Lamp, on the other hand, uses the concept of command paths, which resembles the file paths on your computer. This greatly improves performance and elevates look-up times to O(1). Lamp only uses O(n) look-ups when invalid commands are inputted. No surprises: Many command frameworks work exceptionally well when the input is valid and matches the requirements of the command. However, a lot of things can go wrong when the input is invalid. Creating readable and editable error messages, and finding where the input had exactly gone wrong is usually not a high priority in command frameworks, and is often dismissed and left to the developer to deal with. Lamp addresses this issue through extremely flexible command exceptions and some annotations. Custom annotations: Most command frameworks come with built-in annotations that assist with parsing and controlling the command behavior, however rarely allow you to create your own annotations with their own functionality (for example, a custom annotation that validates a certain argument). Lamp was built with custom annotations in mind, and gives you limitless potential to what you can do with your annotations. Example uses of custom annotations: Creating a @OpOnly annotation on Player arguments, which ensures that the given player is opped. It is also possible to automatically suggest opped players in tab completions whenever the annotation is present Creating a @cooldown annotation, that only allows the command to be run once every X time. Adding a org.bukkit.Location parameter with a @LookingLocation to indicate that this parameter is resolved by retrieving the player's looking location. Out of the box: Lamp comes with many QoL convenience features which enrich your user experience: A quote-aware argument parser. This allows parameters to include spaces or be empty strings. Built-in argument tabs for common parameter types Named parameters that can come anywhere in the command. Support for Bukkit's entity selectors such as @p, @a and @e[type=foo] Brigadier support on any 1.13+ server to get fancy tooltips and client-side argument validation So, what are you waiting for? Go ahead and create professional, user-friendly, rich commands! Lamp was designed purely to lighten your headache
Seems like a fun project, I'll definitely have to try it out. Nicely written thread - very inspirational
the advantages I see over acf are that you can create a custom annotation, you can create an argument which can be put anywhere in the command and that it supports brigadier 1.13+ rather than acf's 1.15+ ( which is experimental and limited to paper only because aikar "does not want to use hacks" ( not gonna go deep into the theme of aikar using or not using hacks ) ). is that all?
It does have more less obvious features such as providing your own sender implementation (instead of Bukkit's or the library's CommandActor), returning and handling the results of command methods (for example, methods can return Strings which are sent to the sender), and an API for generating help menus. As far as I know, ACF does not support quoted text or switches (come anywhere in the command to control a boolean), but yeah pretty much this. Yes. @Optional will make the argument null if it's not provided, and @Default allows a default value to be parsed in such cases. There is no need to have both Optional and Default together, one is enough.
This framework came in a clutch, I was searching for one to use instead of ACF. I love the objective syntax and the extendability of Lamp, will use right away on my plugins.
You can put all your registry logic in a CommandHandlerVisitor, then do commandHandler.accept(your visitor), if that's what you mean. You can extract your visitor to its own dependency/repository and use that anywhere
As far as messaging goes (one of my biggest pet peeves is actually making responses to players in commands...), is there an annotation or way to define a default prefix for a command group? Additionally, how can I specify a command group (using @command above the class header) but also have a default, no args command? For example, I am making a /titles command, which, for admins, will have sub commands, but for everyone who executes without arguments, it will run a default that will open a GUI.
The @Default annotation also works on methods. After defining your commands with @Subcommand, use @Default just like any other command method (it can take parameters, etc.) As for the 1st part, please elaborate as I'm not sure what this means, though if you want to make responses you can register a ResponseHandler in the CommandHandler, and return the response in your methods
If I had to take a guess it's about the formatting of responses, e.g. having the method return a String like "Successfully added Foo to Bar" and the player gets "[MyPlugin] Successfully added Foo to Bar" in return. Which should be easily doable with ResponseHandlers and potentially custom annotations. Or custom sender implementations, I guess. Late Edit: looking into it... how would you go about to implement a custom CommandActor for Lamp? oO
CommandActors are internally constructed and handled so normally they're not supposed to be implemented from outside, however similar and more flexible functionality can be achieved by implementing a SenderResolver, which allows you to use other types as senders (Lamp uses built-in implementations to provide support for Bukkit's Player and CommandSender, see this). Though I'm not sure how that would help, so I'd prefer the approach below If you'd like to use different prefixes for different commands, you can either test against custom annotations with ExecutableCommand#hasAnnotation/getAnnotation(), or use ExecutableCommand#getPath() if you want a different prefix for different commands. For example all commands that start with "foo" would get [Foo] but "bar" would get [Bar], you'd use command.getPath().getFirst().equals("foo"), etc., or create a foo CommandPath with CommandPath.get("foo"), then use isParentOf/isChildOf. Or, you could create a basic "Response" class, with static factory methods that each uses a custom prefix, then return Response.foo("hello"), Response.bar("hello"), and handle that in your response handler Multiple ways Though you should use the one that would fit your use cases. I'd likely go with custom annotations or Responses, as these are most explicit and would not cause surprises if you come to your code three months later. They also make it possible to change the behavior per-command which is probably better when you come across such a situation that requires so.
My thought regarding the CommandActors was overwriting the #reply() and #error() methods to implement custom formatting there, but I guess a ResponseHandler/Response Object is ultimately more versatile. Only downside is that you might need a custom exception handler as well.