ACF - Annotation Command Framework

Hello,
Some of you who are also part of the Spigot community are well aware of this, but I just remembered that I never got around to creating a thread over here!

I’ve been working on a Command Framework over the past few years similar to the power of Intake, but much much more simple and easier to use.

Others asked me to make it public for the longest time, and a few months ago I finally finished doing that.

Since then, ACF has REALLY taken off in the Bukkit/Spigot community, and has greatly revolutionized command management.

I was able to abstract the core functionality to be completely platform agnostic, and now ACF can be used for Bukkit/Paper, Sponge and BungeeCord, enabling 1 clean API to manage commands across multiple platforms.

Using ACF Wiki Page: Using ACF · aikar/commands Wiki · GitHub
Repo: GitHub - aikar/commands: Java Command Dispatch Framework - (Bukkit, Spigot, Paper, Sponge, Bungee, JDA, Velocity supported, generically usable anywhere)

Examples of Command Definitions:

@CommandAlias("res|residence|resadmin")
public class ResidenceCommand extends 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 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());
    }

}
// Then in your initializer
private void initCommands() {
    SpongeCommandManager manager = new SpongeCommandManager(plugin);
    manager.registerCommand(new GroupCommand());
    manager.registerCommand(new ResidenceCommand());
}

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

@CommandAlias("foobarcommand|foo|f bar|b baz")

This will result in all of the following commands working:

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!

@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 my Discord

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.

6 Likes

I’m using this in one of my plugins, and it’s amazing. Really easy to use, and gets around the Optionals you have to #get() in CommandContext when you know a value is present. People who maintain a spigot version and a sponge version of some plugin should be especially excited for this, because you can now have commands in one core shared by both projects. Because ACF is platform agnostic, the only thing that changes between them is the CommandManager you use in the platform specific code.

Thanks for this, Aikar!

2 Likes

Figured this could use a good bump :slight_smile: ACF has came A LONG ways over the past few months, with MAJOR new features.

ACF plans to implement Brigadier for 1.13, so if you want a consistent framework that won’t require you to rewrite your commands for 1.13, then check out ACF :slight_smile:

Wiki has received major updates to documentation, check the wiki for all the goodies.

1 Like