Resource Lamp - A highly flexible, extremely powerful and customizable commands framework

Discussion in 'Spigot Plugin Development' started by Reflxction, Jan 25, 2022.

?

Are you going to use Lamp some time in your projects?

  1. Yes

    75.9%
  2. No

    24.1%
  1. 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

    [​IMG]
    [​IMG]


    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 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().
    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

        [​IMG]

        [​IMG]

        [​IMG]
    So, what are you waiting for? Go ahead and create professional, user-friendly, rich commands! Lamp was designed purely to lighten your headache :)
     
    #1 Reflxction, Jan 25, 2022
    Last edited: Feb 6, 2022
    • Winner Winner x 9
    • Like Like x 7
    • Useful Useful x 6
    • Creative Creative x 1
  2. Seems like a fun project, I'll definitely have to try it out.

    Nicely written thread - very inspirational
     
    • Friendly Friendly x 1
  3. Have been using Lamp for a while now, and it served our projects well!
     
    • Friendly Friendly x 1
  4. Happy to hear this! :D
     
  5. [​IMG]
     
    • Funny Funny x 7
  6. This looks awesome. Can't wait to give this a try!
     
    • Friendly Friendly x 1
  7. 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?
     
  8. Are optional parameters supported?
     
  9. 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.
     
  10. 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.
     
    • Friendly Friendly x 1
  11. Happy to hear this! :)
     
  12. This seems like almost exactly the command framework I always wanted, I'll definitely give it a try. :)
     
    • Agree Agree x 1
    • Friendly Friendly x 1
  13. Good to hear! :)
     
  14. How would you suggest sharing resolvers across plugins?
     
  15. 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
     
  16. 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.
     
    #16 SizzleMcGrizzle, Feb 3, 2022
    Last edited: Feb 3, 2022
  17. 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
     
    • Like Like x 1
  18. 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
     
    #18 SydMontague, Feb 4, 2022
    Last edited: Feb 4, 2022
  19. 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.
     
    #19 Reflxction, Feb 4, 2022
    Last edited: Feb 4, 2022
  20. 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.