Resource (ACF - BETA) Annotation Command Framework

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

  1. WAS

    WAS

    A comma represents a list, or array; in comprehension (like tags for a post). Pipes usually indicate a programmable set.
     
  2. bump for awesome sauces.

    Ghost Pepper anyone?
     
  3. Updated to v0.4.0 to improve command completion resolvers
    THIS IS AN API BREAK IF YOU REGISTERED YOUR OWN COMMAND COMPLETION HANDLERS (If you did not register your own handlers, then it is safe to upgrade with no changes)

    TO FIX: Simply add , c to your completion handlers parameters like so:

    Code (Text):
    commandManager.getCommandCompletions().registerCompletion("test", (sender, config, input, c) -> {
    You may use the c to do c.getContextValue(SomeClassThatHasAContextResolver.class); for any parameter BEFORE the one being command completed on.

    For example, I am working on a Guild like system, where your in multiple guilds (Empires)

    /empire kick <TAG> <Member>

    The member is dynamic based on what is passed to TAG. It will pull the member list from the parameter before it.
     
    #23 Aikar, Apr 29, 2017
    Last edited: Apr 29, 2017
    • Useful Useful x 1
  4. Example of the discussed logic above:

    The Context/Completion handlers (This would only be done once per plugin to define your custom types)

    Code (Java):

    CommandContexts commandContexts = EmpirePlugin.commandManager.getCommandContexts();
    CommandCompletions commandCompletions = EmpirePlugin.commandManager.getCommandCompletions();

    commandContexts.registerContext(Empire.class, c -> {
        String tag = c.popFirstArg();
        CommandSender sender = c.getSender();

        Empire empire = Empires.getByNameOrTag(tag);
        if (empire == null) {
            throw new InvalidCommandArgument("Could not find an empire with that name or tag.");
        }
        if (sender instanceof Player) {
            Player player = (Player) sender;
            EmpireUser user = player.getUser();
            if (!c.hasFlag("any") && !empire.hasMember(user)) {
                throw new InvalidCommandArgument("You are not a member of that Empire");
            }
            String perm = c.getFlagValue("perm", (String) null);
            if (perm != null) {
                EmpireRolePerm rolePerm = Util.getEnumFromName(EmpireRolePerm.values(), perm);
                if (rolePerm == null) {
                    Log.exception("Command requires an invalid Empire permission name: " + perm);
                    throw new InvalidCommandArgument("Alert Dev Team that this command requires an invalid permission.");
                }
                if (!empire.doesPlayerHavePerm(player, rolePerm)) {
                    throw new InvalidCommandArgument("You do not have permission to execute this command for this Empire. You need the " + perm + " permission.");
                }
            }
        }
        return empire;
    });

    commandCompletions.registerCompletion("empiremembers", (sender, config, input, c) -> {
        if (sender instanceof Player) {
            Empire contextValue = c.getContextValue(Empire.class);
            if (contextValue != null) {
                return contextValue.getMembers().values().stream().map(EmpireMember::getName).collect(Collectors.toList());
            }
        }
        return null;
    });
     
    Then a command handler using them:
    Code (Java):

    @Subcommand("kick|remove")
    @CommandCompletion("@empires @empiremembers")
    public void onKick(Player player, @Flags("perm=KICK") Empire empire, EmpireMember member, String reason) {
        Confirmation.confirm(player, "Kick " + member.getName() + " from Empire", () -> {
            empire.kickMember(player, member, reason);
        });
    }
     
    You can see that @empiremembers command completions definition looks up an Empire context.

    That handler gets the value of the Empire typed before EmpireMember, and only provides a list of players that are actually in the Empire.
     
    #24 Aikar, Apr 29, 2017
    Last edited: Apr 29, 2017
    • Agree Agree x 1
  5. WAS

    WAS

    Such compact. Such new age Java. Love it, really. Imagine this in your plain onCommand methods of old.
     
  6. I just keep getting it smarter :)
    Code (Java):


    @Subcommand("roles|role list|l")
    @CommandCompletion("@empires")
    public void onRolesList(Player player, @Flags("perm=ADMIN") Empire empire) {
        onRoles(player, empire);
    }

    @Subcommand("roles|role")
    @CommandCompletion("@empires")
    public void onRoles(Player player, @Flags("perm=ADMIN") Empire empire) {
    Both these do the same thing, but before the 2nd one would end up overriding the command completion and the other subcommands under roles were not being listed.

    Now, it's even smarter and it combines the possible list of sub commands AND the command completions for the command for if it doesn't find a deeper subcommand match.
     
  7. WAS

    WAS

    I really feel like this command structure should be implemented into Bukkit as the "AikarExecutor" haha
     
  8. electronicboy

    IRC Staff

    Just wondering if anybody has been crazy enough to try this on Kotlin yet?
     
    • Funny Funny x 1
  9. Of course @Redrield is interested in that!
     
    • Like Like x 1
  10. I've now broken out the Timings Impl I used in ACF to be a separate library:
    https://github.com/aikar/minecraft-timings

    So that if a plugin doesn't use ACF, it can at least add timing support in a way that supports CraftBukkit (no timings), Spigot (v1) and Paper (v2).

    So this lets plugin developers add timings to their plugin and if the plugins used on non timings supported software, it won't blow up.

    I'll make a new thread for it later once documentation and such is ready for it, but wanted to note the change.

    This should fix Paper 1.8.8 support.
     
    • Like Like x 2
  11. bump for command awesomeness :)
     
    • Agree Agree x 1
  12. Useful update just deployed.

    Say you have some staff commands that exist under same root command.

    Before, you would have to tag every one of them with @CommandPermission.

    Now, you can do stuff like (in context of my server):

    ResidenceCommand: No permission requirement
    ResidenceAdminCommands: Sets permission requirement
    EmpireCommand: Basic Empire commands
    EmpireOfficerCommands: Basic Officer commands (Sets a flag requiring officer)
    EmpireAdminCommands: Admins of the Empire commands (Role Management) (Sets a flag requiring Admin)
    EmpireStaffCommands: Commands for Empire staff to act on an Empire (Requires permission node)

    Key note being that only 1 of the classes are able to use @Default... otherwise the last one registered is going to assume default.

    Further expansions to come with breaking commands up into multiple files/classes.
    ======================
    This is still in 0.4.0, as no API break occurred. Just get your compiler to pull the freshest snapshot.
     
  13. More big updates!

    Inner classes now supported if extends BaseSubCommand, and is auto instantiated on register.

    Subcommand can now be placed on the command class itself, to act as a prefix to all the sub commands.
    This is all recursive too!

    See:https://github.com/aikar/commands/b...ava/co/aikar/acfexample/SomeCommand.java#L108

    The test4 command is /acf test next test4

    ------------------
    Another major new feature is that you can now register Command Text Replacements, that apply to every annotations value

    Example:
    https://github.com/aikar/commands/b.../java/co/aikar/acfexample/ACFExample.java#L43
    https://github.com/aikar/commands/b...java/co/aikar/acfexample/SomeCommand.java#L93

    this command can be accessed as /acf test foobar, requires the foobar permission node, provides foobar as a command completion, and only allows foobar as a value due to @Values. Also works on Flags, Syntax, and Default annotations.

    This is primarily so you can do .addReplacement("staff", "myplugin.ranks.staff") for your permission nodes, and then only need to do @CommandPermission("%staff")

    Also allows server state contextual @Values, for example, a server switcher, don't provide the current server
    You can define a replacement for all OTHER servers to be used in @CommandCompletion AND @Values


    -------------
    Finally,
    We've had a case where some evil plugin hijacked the Bukkit Commandmap. They replaced it with their own system that isn't an extension of SimpleCommandMap.

    This blew ACF up. I've put in code to work around that, so it should hopefully resolve the situation but because I don't know what plugin caused it (have not been any reports since I added the code to identify who hijacked it), it's not tested, but hopefully will work.

    Happy Command Writing!
     
  14. Anyone using the newest features? Show off your commands!
     
  15. Bump for commands!
     
  16. Hey, I am probably just overlooking something easily, but if I want to just have a simple command with no sub-commands or anything, where do I put the logic for that command using your framework? also, what should I call the method, or can I name it anything and I just link with your framework?

    For example, say I wanted to make a simple command "/Test". No sub-commands, or anything. If all I wanted this command to do was display a message in chat, where would I put the code for that using your framework?

    This may be very simple and I am just overthinking it, but I can't see how to do this on your wiki page or in any of the examples you have posted on here and on GitHub.
     
  17. It would look like this:

    Code (Java):

    // Do not add a CommandAlias or constructor here! Leave it undefined
    public class MyCommand extends BaseCommand {
        @CommandAlias("mycommand|myc")
        public void onCommand(CommandSender sender, String foo, Integer bar, @Optional baz) {
           // Command executed with desired parameters.
        }
    }
     
    You can define more than 1 command in this file too just give each their own command alias
     
  18. bump for commands!
     

Share This Page