Resource (ACF - BETA) Annotation Command Framework

Discussion in 'Spigot Plugin Development' started by Aikar, Apr 19, 2017.

  1. ACF (Annotation Command Framework) is currently in Beta Test!

    Those who watch my live streams have likely seen my command framework. It has a similar concept as WorldEdit's Intake command system, but VASTLY easier to use.

    I've finally gotten around to doing what I think the basic steps to refactor it into a library would be, so it should now be usable.

    Using ACF Wiki Page: https://github.com/aikar/commands/wiki/Using-ACF
    Repo: https://github.com/aikar/commands

    Examples of Command Definitions:

    Code (Java):

    @CommandAlias("res|residence|resadmin")
    public class ResidenceCommand extends co.aikar.commands.BaseCommand {

        @Subcommand("pset")
        @CommandCompletion("@allplayers:30 @flags @flagstates")
        public void onResFlagPSet(Player player, @Flags("admin") Residence res, EmpireUser[] users, String flag, @Values("@flagstates") String state) {
            res.getPermissions().setPlayerFlag(player, Stream.of(users).map(EmpireUser::getName).collect(Collectors.joining(",")), flag, state, resadmin, true);
        }


        @Subcommand("area replace")
        @CommandPermission("residence.admin")
        public void onResAreaReplace(Player player, CuboidSelection selection, @Flags("verbose") Residence res, @Default("main") @Single String area) {
            res.replaceArea(player,
                new CuboidArea(selection),
                area,
                resadmin);
        }

    }
    @CommandAlias("gr")
    public class GroupCommand extends co.aikar.commands.BaseCommand {
        public GroupCommand() {
            super("group");
        }
        @Subcommand("invitenear|invnear")
        @CommandAlias("invitenear|invnear|ginvnear")
        @Syntax("[radius=32] &e- Invite Nearby Players to the group.")
        public void onInviteNear(Player player, @Default("32") Integer radius) {
            int maxRadius = UserUtil.isModerator(player) ? 256 : 64;
            radius = !UserUtil.isSrStaff(player) ? Math.min(maxRadius, radius) : radius;
            List<String> names = player.getNearbyEntities(radius, Math.min(128, radius), radius)
                .stream().filter((e) -> e instanceof Player && !UserUtil.isVanished((Player) e))
                .map(CommandSender::getName)
                .collect(Collectors.toList());
            Groups.invitePlayers(player, names);
        }

        @Subcommand("invite|inv")
        @CommandAlias("invite|inv|ginv")
        @Syntax("<name> [name2] [name3] &e- Invite Players to the group.")
        public void onInvite(Player player, String[] names) {
            Groups.invitePlayers(player, names);
        }

        @Subcommand("kick|gkick")
        @CommandAlias("gkick")
        @Syntax("<player> &e- Kick Player from the group.")
        public void onKick(Player player, @Flags("leader") Group group, OnlinePlayer toKick) {
            group.kickPlayer(player, toKick.getPlayer());
        }

    }
    Why you should want to use this

    How many times have you wrote code that converts what the player typed into a number, complete with error feedback? How many places is that code duplicated?

    With ACF, you only write it once (and numbers are already done for you!), and then simply build your command format as a method signature.

    All of the users input is mapped into the method parameters.

    Flags let you easily define restrictions and other validations quick and easy.

    ACF is designed to make creating complex command architectures painlessly.

    Oh and did I mention you no longer need to use plugin.yml to define commands?

    All commands registered with ACF are 100% programmatic. No plugin.yml definitions needed.

    Annotation Driven
    Everything about ACF is Annotation driven.

    You can define a @CommandPermission at the root node, and require everything in that command class to need that permission, or you can break it down to the per subcommand level, and put them on that.

    Want multiple ways to invoke a command? The syntax for @Subcommand and @CommandAlias supports a pipe format to easily create tens to hundreds of combinations to create a command

    Code (Java):
    @CommandAlias("foobarcommand|foo|f bar|b baz")
    This will result in all of the following commands working:
    Code (Text):

    foobarcommand bar baz
    foobarcommand b baz
    foo bar baz
    foo b baz
    f bar baz
    f b baz
     
    Want a a subcommand of one of your primary commands to also have its own root command? That works too!

    Code (Java):

    @CommandAlias("group")
    public class GroupCommand extends BaseCommand {
        @Subcommand("invite")
        @CommandAlias("ginvite")
        public void onGroupInvite(Player player, OnlinePlayer playerToInvite) { }
    }
     
    In this example, both /group invite Aikar and /ginvite Aikar would do the same thing!
    Command Completion? Yep it's smart enough to work from both of those commands.

    Support / Disclaimer
    Documentation is very light right now. Please examine the source code and just poke around. I will try to document things as I can.

    Please ask for support in [#aikar on Spigot IRC], or [Code With Aikar Discord] . Do NOT ask in #spigot!

    Also note that this is still beta, at version 0.5.0.

    Semantic Versioning for Pre 1.0.0 releases are treated as minor (0.X) are API breakages, and patch updates would be safe.


    I welcome early testers!

    PSST, Also check out TaskChain - Another useful library for plugin developers
     
    #1 Aikar, Apr 19, 2017
    Last edited: Dec 15, 2017 at 4:09 AM
    • Winner x 16
    • Like x 12
    • Useful x 6
    • Agree x 1
    • Friendly x 1
  2. thanks, this has really sped up my development.
     
    #2 Synapz, Apr 19, 2017
    Last edited: Jul 27, 2017

  3. i've always highly respected u because of your use of the latest Java 8 techniques and functions, thanks for encouraging it in all your stuff, i myself use it as well, and would want the rest of the community to do so as well!
     
    • Agree Agree x 2
  4. WAS

    WAS

    Well this is sure interesting. Really like this concept.
     
  5. Love it. Integrating immediately. Can't wait to see where I can help with the development. Woooo! Pumped.
     
  6. @Aikar
    You need to like annotations ;)
    The flags seem like a nice concept though, and the resolvers look nice.

    I do have a question though:
    Are all messages defined in the source code?

    What I like about my own bad command thingy is the following:
    1. You select a language
    2. All command keywords and messages change to respect that.
      I can have "/friend" (english/fallback) and "/freunde" (german) refer to the exact same command, depending on the chosen language.

    Is that possible with yours?


    Thank you for open-sourcing your work though, I appreciate it!
     
  7. Let's ditch onCommand! :)
     
    • Funny Funny x 1
  8. Just add them all as aliases.
    Code (Java):

    @CommandAlias("friend|freunde")
    public void onFriend(){}
     
    What the code does and the command used is up to you, but there is a field exposed to access the raw command used

    Overall I18N work will be up to you though, but def possible.
     
  9. Though for the hard coded messages, I can see supporting Language Tables for that.
     
  10. Why you use "|" to separate aliases, but not comma?
     
  11. Pipe is a pretty standard separator that is unlikely to conflict with natural language.

    Sure for aliases a comma could of worked too, but consistency is more important.


    For example, for separating natural language you may do

    Hello, Bob|Hello, Sally
     
    • Like Like x 2
  12. @Aikar
    Of course I could just add them as aliases. But only in the source code, as the value of the annotations is not evaluated at runtime.

    If the user decides he now wants arabic, I would need to add an arabic alias. By resolving the aliases at runtime he can just change a language file.

    Do you see what I mean? Or have I misunderstood your response?
     
  13. As said above, I am for I18N work to provide language .yml files to it.

    We could do something like @CommandAlias("i18n.command.foo")

    That would replace from example YML:
    Code (Text):

    command:
      - foo: "bar"
     
    https://github.com/aikar/commands/issues/13
     
  14. I'll try to work on some i18n stuff over the next few days. I got some ideas already.
     
  15. Ok 2 things happened tonight.
    I changed the Maven Artifact ID and bumped to 0.2.0
    it is now
    Code (Text):
    GROUP: co.aikar.commands
    ARTIFACT: acf
    VERSION: 0.2.0-SNAPSHOT
    I then wrote an example plugin and found 0.1.0 wasn't working, and fixed the bug.

    So please bump up to 0.2.0 (the code package didn't change, so a simple pom/gradle file change)

    Also note, its very important you add Package Relocation Instructions to your plugin to avoid clashes with other plugins using ACF.

    Additionally, it is recommended to add -parameters compile flag

    Both of these are covered in the setup guides:

    Maven Setup: https://github.com/aikar/commands/wiki/Maven-Setup
    Gradle Setup: https://github.com/aikar/commands/wiki/Gradle-Setup

    Example Plugin POM with Relocation and parameters:
    https://github.com/aikar/commands/blob/master/example/pom.xml

    Example Plugin Source:
    https://github.com/aikar/commands/tree/master/example/src/main/java/co/aikar/acfexample

    Basic stuff is working on the Example Plugin now!
     
    #15 Aikar, Apr 21, 2017
    Last edited: Apr 24, 2017
    • Creative Creative x 1
  16. Some more bugs fixed! But heads up, im likely going to change the group ID again in 0.3.0, as i realized I did use co.aikar for TaskChain and i want to keep it consistent.

    and to future proof it, i think im going to do
    co.aikar:acf-core

    similar to taskchain. But i'll let everyone know when that happens, as I'll prob wait until the I18N stuff comes in for that.
     
    • Informative Informative x 1
  17. Ok group/artifact ID updated

    GROUP ID: co.aikar
    ARTIFACT: acf-core
    VERSION: 0.3.0-SNAPSHOT

    Just exposed ACFUtil which has a ton of misc util methods, not really related to the framework itself, but good stuff.
     
    • Like Like x 1
    • Useful Useful x 1
  18. [​IMG]
    image
    Posting in memory of the dinosaurs.

    Anyway, nice work, Aikar. This completely redefines the command system!
     
    #18 dinosaur, Apr 24, 2017
    Last edited: Apr 24, 2017
    • Winner Winner x 2
  19. MiniDigger

    Supporter

    dinosaurs have timemachines? :O
     
    • Funny Funny x 1
  20. Gasp! A dinosaur! Quick, give it some Java 8!
     
    • Funny Funny x 1

Share This Page