From 8c49ecabcff63a4dd7ba283f30998db3bce2958f Mon Sep 17 00:00:00 2001 From: Aikar Date: Wed, 11 Oct 2017 20:47:10 -0400 Subject: [PATCH] Add many comments to the example to help explain things --- .../java/co/aikar/acfexample/ACFExample.java | 27 ++++++++++++++++-- .../java/co/aikar/acfexample/SomeCommand.java | 28 ++++++++++++++++++- .../java/co/aikar/acfexample/SomeObject.java | 4 +++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/example/src/main/java/co/aikar/acfexample/ACFExample.java b/example/src/main/java/co/aikar/acfexample/ACFExample.java index 2f27346d..0409a99b 100644 --- a/example/src/main/java/co/aikar/acfexample/ACFExample.java +++ b/example/src/main/java/co/aikar/acfexample/ACFExample.java @@ -40,30 +40,53 @@ public final class ACFExample extends JavaPlugin { } private void registerCommands() { + // 1: Create Command Manager for your respective platform commandManager = new BukkitCommandManager(this); - commandManager.getCommandReplacements().addReplacements("test", "foobar", "%foo", "barbaz"); + + // 2: Setup some replacement values that may be used inside of the annotations dynamically. + commandManager.getCommandReplacements().addReplacements( + // key - value + "test", "foobar", + // key - demonstrate that % is ignored - value + "%foo", "barbaz"); + // Another replacement for piped values commandManager.getCommandReplacements().addReplacement("testcmd", "test4|foobar|barbaz"); - commandManager.getCommandContexts().registerContext(SomeObject.class, SomeObject.getContextResolver()); + + // 3: Register Custom Command Contexts + commandManager.getCommandContexts().registerContext( + /* The class of the object to resolve*/ + SomeObject.class, + /* A resolver method - Placed the resolver in its own class for organizational purposes */ + SomeObject.getContextResolver()); + + // 4: Register Command Completions - this will be accessible with @CommandCompletion("@test") commandManager.getCommandCompletions().registerCompletion("test", c -> ( Lists.newArrayList("foo", "bar", "baz") )); + // 5: Register your commands - This first command demonstrates adding an exception handler to that command commandManager.registerCommand(new SomeCommand().setExceptionHandler((command, registeredCommand, sender, args, t) -> { sender.sendMessage(MessageType.ERROR, MessageKeys.ERROR_GENERIC_LOGGED); return true; // mark as handeled, default message will not be send to sender })); + // 5: Register an additional command. This one happens to share the same CommandAlias as the previous command + // This means it simply registers additional sub commands under the same command, but organized into separate + // Classes (Maybe different permission sets) commandManager.registerCommand(new SomeCommand_ExtraSubs()); + // 6: Register default exception handler for any command that doesn't supply its own commandManager.setDefaultExceptionHandler((command, registeredCommand, sender, args, t) -> { getLogger().warning("Error occured while executing command " + command.getName()); return false; // mark as unhandeled, sender will see default message }); } + // Typical Bukkit Plugin Scaffolding public static ACFExample getPlugin() { return plugin; } + // A way to access your command manager from other files if you do not use a Dependency Injection approach public static BukkitCommandManager getCommandManager() { return commandManager; } diff --git a/example/src/main/java/co/aikar/acfexample/SomeCommand.java b/example/src/main/java/co/aikar/acfexample/SomeCommand.java index 6fb7ec58..de43ab8d 100644 --- a/example/src/main/java/co/aikar/acfexample/SomeCommand.java +++ b/example/src/main/java/co/aikar/acfexample/SomeCommand.java @@ -39,11 +39,17 @@ import org.bukkit.entity.Player; @CommandAlias("acf|somecommand|sc|somcom") public class SomeCommand extends BaseCommand { + // %testcmd was defined in ACFExample plugin and defined as "test4|foobar|barbaz" + // This means, /test4, /foobar and /barbaz all are aliased here. + // functionally equivalent to @CommandAlias("test4|foobar|barbaz") but could be dynamic (Read from config) + // Any @CommandAlias implies an automatic @Subcommand too, so this is also accessible from /acf test4 @CommandAlias("%testcmd") public void onCommand(CommandSender sender, SomeObject someObject) { sender.sendMessage("You got an object of type: " + someObject.getClass().getName() + " with a value of: " + someObject.getValue()); } + // /acf admin - requires permission some.perm + // May also be accessed with /acfa or /acfadmin @Subcommand("admin") @CommandPermission("some.perm") @CommandAlias("acfadmin|acfa") @@ -51,6 +57,7 @@ public class SomeCommand extends BaseCommand { player.sendMessage("You got permission!"); } + // Has an optional parameter opt, /acfo and /acfo both work @Subcommand("optional") @CommandAlias("acfoptional|acfo") public void onOptional(CommandSender sender, @Optional String opt) { @@ -60,12 +67,18 @@ public class SomeCommand extends BaseCommand { sender.sendMessage("You supplied: " + opt); } } + + // Like optional above, but name will always have a value, Unknown User if /acfd is executed @Subcommand("default") @CommandAlias("acfdefault|acfd") public void onTestDefault(CommandSender sender, @Default("Unknown User") String name) { sender.sendMessage("Hello, " + name); } + // Pressing tab after typing /acfc A might pop up command completions for Aikar if Aikar was online, + // /acf Aikar wo with a world named "world" would provide world as a completion. + // @test was custom defined in ACFExample as foobar, so only that value would show up as a completion + // then finally /acfc Aikar world foobar would show foo1, foo2, foo3 statically @Subcommand("completions") @CommandAlias("acfcompletions|acfc") @CommandCompletion("@players @worlds @test foo1|foo2|foo3") @@ -74,27 +87,39 @@ public class SomeCommand extends BaseCommand { } + // This sub command requires that `/acf testsub test1` be typed to be executed, or /Foo @Subcommand("testsub test1") @CommandCompletion("Foo") public void onTestSub1(CommandSender sender, String hi) { sender.sendMessage(hi); } + // Nested inner classes are automatically loaded when the parent is registered. + // When @Subcommand is defined in an inner class, it is assumed that the values defined here are automatically prepended + // to all of the children methods @Subcommand + // A CommandPermission defined at this spot would also require that permission on every sub command without + // Every sub command having to define that annotation. This is good for grouping like-permissioned commands. + // Since %test was defined as a command replacement to "foobar", every inner method will require that permission. @Subcommand("test|txt|tfoo") + @CommandPermission("%test") public class Test extends BaseCommand { + // Will require /acf test test1 to access (or any of the alternate formats such as /acf txt td1) @Subcommand("test1|td1") @CommandCompletion("%foo") public void onTest1(Player player, String testX) { player.sendMessage("You got test inner test1: " + testX); } + + // same again, but /acf test foobar (command replacement) will work here @Subcommand("test2|td2|%test") @CommandCompletion("%test") - @CommandPermission("%test") public void onTest2(Player player, @Values("%test") String testY) { player.sendMessage("You got test inner test2: " + testY); } + // Nesting inner classes works infinitely recursive + // All of these inner methods require /acf test next to acces @Subcommand("next") public class TestInner extends BaseCommand { @@ -103,6 +128,7 @@ public class SomeCommand extends BaseCommand { public void onTest1(Player player, String testX) { player.sendMessage("You got test inner inner test3: " + testX); } + // Requires /acf test next test4 or simply /deepinner command alias @CommandAlias("deepinner") @Subcommand("test4|td4") @CommandCompletion("BAR") diff --git a/example/src/main/java/co/aikar/acfexample/SomeObject.java b/example/src/main/java/co/aikar/acfexample/SomeObject.java index b4cb1f53..c4247387 100644 --- a/example/src/main/java/co/aikar/acfexample/SomeObject.java +++ b/example/src/main/java/co/aikar/acfexample/SomeObject.java @@ -39,6 +39,9 @@ public abstract class SomeObject { return this.thisValue; } + // Dynamically returns up to 3 different objects based on the users input. + // A command signature using SomeObject doesn't care about all this logic, its defined once + // And it knows it will receive SomeObject public static ContextResolver getContextResolver() { return (c) -> { String first = c.popFirstArg(); @@ -50,6 +53,7 @@ public abstract class SomeObject { try { return new TestOther(Integer.parseInt(first)); } catch (NumberFormatException ignored) { + // User didn't type a number, show error! throw new InvalidCommandArgument(MessageKeys.MUST_BE_A_NUMBER); } }