From e32805c1fc38ab994cdd7ebad1ee5b8e45048d0a Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sun, 3 Feb 2019 00:33:38 +0100 Subject: [PATCH] Listing subcommands, other fixes Member command converted Now requiring the permissions plugin Added support for optional arguments Automatically sending an error if parameter conversion fails Automatically sending the help text if there aren't enough args Automatically converting the first line of the help text to a header --- .../buttondevteam/core/ComponentCommand.java | 9 +- .../java/buttondevteam/core/MainPlugin.java | 8 +- .../buttondevteam/core/PlayerListener.java | 4 +- .../core/component/members/MemberCommand.java | 71 ++++++-------- .../component/members/MemberComponent.java | 4 +- .../lib/architecture/Component.java | 4 +- .../java/buttondevteam/lib/chat/Command2.java | 94 ++++++++++++++----- .../buttondevteam/lib/chat/Command2MC.java | 26 +++-- .../buttondevteam/lib/chat/CommandClass.java | 3 +- .../buttondevteam/lib/chat/ICommand2.java | 4 +- 10 files changed, 137 insertions(+), 90 deletions(-) diff --git a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java index 78aa6ea..a0e18f7 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java @@ -2,6 +2,7 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2.Subcommand; import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.ICommand2MC; @@ -13,29 +14,27 @@ import org.bukkit.plugin.Plugin; import java.util.Optional; @CommandClass(modOnly = true, helpText = { - "§6---- Component command ----", + "Component command", "Can be used to enable/disable/list components" }) public class ComponentCommand extends ICommand2MC { public ComponentCommand() { - getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg)); + getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!"); } @Subcommand public boolean enable(CommandSender sender, Plugin plugin, String component) { - if (plugin == null) return respond(sender, "§cPlugin not found!"); plugin.reloadConfig(); //Reload config so the new config values are read - All changes are saved to disk on disable return enable_disable(sender, plugin, component, true); } @Subcommand public boolean disable(CommandSender sender, Plugin plugin, String component) { - if (plugin == null) return respond(sender, "§cPlugin not found!"); return enable_disable(sender, plugin, component, false); } @Subcommand - public boolean list(CommandSender sender, String plugin) { + public boolean list(CommandSender sender, @Command2.OptionalArg String plugin) { sender.sendMessage("§6List of components:"); Component.getComponents().values().stream().filter(c -> plugin == null || c.getPlugin().getName().equalsIgnoreCase(plugin)) //If plugin is null, don't check .map(c -> c.getPlugin().getName() + " - " + c.getClass().getSimpleName() + " - " + (c.isEnabled() ? "en" : "dis") + "abled").forEach(sender::sendMessage); diff --git a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java index b2200a4..6286e32 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java +++ b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java @@ -14,7 +14,6 @@ import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.chat.Color; -import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.TBMCPlayer; @@ -30,7 +29,6 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.RegisteredServiceProvider; -import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -42,7 +40,6 @@ import java.util.logging.Logger; public class MainPlugin extends ButtonPlugin { public static MainPlugin Instance; - @Nullable public static Permission permission; public static boolean Test; public static Essentials ess; @@ -59,7 +56,8 @@ public class MainPlugin extends ButtonPlugin { Instance = this; PluginDescriptionFile pdf = getDescription(); logger = getLogger(); - setupPermissions(); + if (!setupPermissions()) + throw new NullPointerException("No permission plugin found!"); Test = getConfig().getBoolean("test", true); saveConfig(); Component.registerComponent(this, new PluginUpdaterComponent()); @@ -69,7 +67,7 @@ public class MainPlugin extends ButtonPlugin { Component.registerComponent(this, new MemberComponent()); Component.registerComponent(this, new TownyComponent()); ComponentManager.enableComponents(); - Command2MC.registerCommand(new ComponentCommand()); + getCommand2MC().registerCommand(new ComponentCommand()); TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this); ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender ? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks diff --git a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java index c5146cd..073cb87 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java +++ b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java @@ -3,7 +3,7 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCommandPreprocessEvent; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.chat.Command2MC; +import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.player.TBMCPlayerBase; import lombok.val; import org.bukkit.Bukkit; @@ -64,7 +64,7 @@ public class PlayerListener implements Listener { public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) { if (event.isCancelled()) return; try { - event.setCancelled(Command2MC.handleCommand(event.getSender(), event.getMessage())); + event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(event.getSender(), event.getMessage())); } catch (Exception e) { TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e); } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java index d395d99..1059b26 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java @@ -1,56 +1,47 @@ package buttondevteam.core.component.members; import buttondevteam.core.MainPlugin; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.chat.TBMCCommandBase; -import lombok.val; +import buttondevteam.lib.chat.ICommand2MC; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -@CommandClass(modOnly = true, path = "member") -public class MemberCommand extends TBMCCommandBase { - @Override - public boolean OnCommand(CommandSender sender, String alias, String[] args) { - if (args.length < 2) - return false; - final boolean add; - if (args[0].equalsIgnoreCase("add")) - add = true; - else if (args[0].equalsIgnoreCase("remove")) - add = false; - else - return false; +@CommandClass(modOnly = true, path = "member", helpText = { // + "Member command", // + "Add or remove server members.", // +}) +public class MemberCommand extends ICommand2MC { + private final MemberComponent component; + + public MemberCommand(MemberComponent component) { + getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!"); + this.component = component; + } + + @Command2.Subcommand + public boolean add(CommandSender sender, OfflinePlayer player) { + return addRemove(sender, player, true); + } + + @Command2.Subcommand + public boolean remove(CommandSender sender, OfflinePlayer player) { + return addRemove(sender, player, false); + } + + public boolean addRemove(CommandSender sender, OfflinePlayer op, boolean add) { Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> { - if (MainPlugin.permission == null) { - sender.sendMessage("§cError: No permission plugin found!"); - return; - } - val op = Bukkit.getOfflinePlayer(args[1]); if (!op.hasPlayedBefore()) { sender.sendMessage("§cCannot find player or haven't played before."); return; } - if (add) { - if (MainPlugin.permission.playerAddGroup(null, op, "member")) - sender.sendMessage("§b" + op.getName() + " added as a member!"); - else - sender.sendMessage("§cFailed to add " + op.getName() + " as a member!"); - } else { - if (MainPlugin.permission.playerRemoveGroup(null, op, "member")) - sender.sendMessage("§b" + op.getName() + " removed as a member!"); - else - sender.sendMessage("§bFailed to remove " + op.getName() + " as a member!"); - } + if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup().get()) + : MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup().get())) + sender.sendMessage("§b" + op.getName() + " " + (add ? "added" : "removed") + " as a member!"); + else + sender.sendMessage("§cFailed to " + (add ? "add" : "remove") + " " + op.getName() + " as a member!"); }); return true; } - - @Override - public String[] GetHelpText(String alias) { - return new String[]{ // - "06---- Member ----", // - "Add or remove server members.", // - "Usage: /member " // - }; - } } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java index 0caeca7..1fff614 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java @@ -15,14 +15,14 @@ import java.util.Date; import static buttondevteam.core.MainPlugin.permission; public class MemberComponent extends Component implements Listener { - private ConfigData memberGroup() { + ConfigData memberGroup() { return getConfig().getData("memberGroup", "member"); } @Override protected void enable() { registerListener(this); - registerCommand(new MemberCommand()); + registerCommand(new MemberCommand(this)); } @Override diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java index 7d8a2d4..6e0a9c3 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java @@ -3,7 +3,7 @@ package buttondevteam.lib.architecture; import buttondevteam.core.ComponentManager; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException; -import buttondevteam.lib.chat.ICommand2; +import buttondevteam.lib.chat.ICommand2MC; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCCommandBase; import lombok.Getter; @@ -197,7 +197,7 @@ public abstract class Component { * * @param commandBase Custom coded command class */ - protected final void registerCommand(ICommand2 commandBase) { + protected final void registerCommand(ICommand2MC commandBase) { ButtonPlugin.getCommand2MC().registerCommand(commandBase); } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java index 5e3cf01..899fdbb 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java @@ -22,11 +22,13 @@ import java.util.stream.Collectors; /** * The method name is the subcommand, use underlines (_) to add further subcommands. - * The args may be null if the conversion failed. + * The args may be null if the conversion failed and it's optional. */ -public abstract class Command2 { +public abstract class Command2 { + protected Command2() { + } + /** - * TODO: @CommandClass(helpText=...) * Parameters annotated with this receive all of the remaining arguments */ @Target(ElementType.PARAMETER) @@ -46,12 +48,24 @@ public abstract class Command2 { String[] helpText() default {}; } + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface OptionalArg { + } + @RequiredArgsConstructor protected static class SubcommandData { public final Method method; public final T command; public final String[] helpText; } + + @RequiredArgsConstructor + protected static class ParamConverter { + public final Function converter; + public final String errormsg; + } + /** * Adds a param converter that obtains a specific object from a string parameter. * The converter may return null. @@ -60,20 +74,29 @@ public abstract class Command2 { * @param converter The converter to use * @param The type of the result */ - public abstract void addParamConverter(Class cl, Function converter); + public abstract void addParamConverter(Class cl, Function converter, String errormsg); - protected void addParamConverter(Class cl, Function converter, HashMap, Function> map) { - map.put(cl, converter); + protected void addParamConverter(Class cl, Function converter, String errormsg, HashMap, ParamConverter> map) { + map.put(cl, new ParamConverter<>(converter, errormsg)); } public abstract boolean handleCommand(CommandSender sender, String commandLine) throws Exception; - protected boolean handleCommand(CommandSender sender, String commandline, - HashMap> subcommands, HashMap, Function> paramConverters) throws Exception { + protected boolean handleCommand(CommandSender sender, String commandline, + HashMap> subcommands, + HashMap, ParamConverter> paramConverters) throws Exception { for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { String subcommand = commandline.substring(0, i).toLowerCase(); - SubcommandData sd = subcommands.get(subcommand); //O(1) - if (sd == null) continue; //TODO: This will run each time someone runs any command + SubcommandData sd = subcommands.get(subcommand); //O(1) + if (sd == null) continue; + if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands + sender.sendMessage(sd.helpText); + return true; + } + if (!hasPermission(sender, sd.command)) { + sender.sendMessage("§cYou don't have permission to use this command"); + return true; + } val params = new ArrayList(sd.method.getParameterCount()); int j = subcommand.length(), pj; Class[] parameterTypes = sd.method.getParameterTypes(); @@ -91,12 +114,18 @@ public abstract class Command2 { sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command."); return true; } + val paramArr = sd.method.getParameters(); for (int i1 = 1; i1 < parameterTypes.length; i1++) { Class cl = parameterTypes[i1]; pj = j + 1; //Start index if (pj == commandline.length() + 1) { //No param given - params.add(null); - continue; //Fill the remaining params with nulls + if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) { + params.add(null); + continue; //Fill the remaining params with nulls + } else { + sender.sendMessage(sd.helpText); //Required param missing + return true; + } } j = commandline.indexOf(' ', j + 1); //End index if (j == -1) //Last parameter @@ -109,7 +138,12 @@ public abstract class Command2 { val conv = paramConverters.get(cl); if (conv == null) throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'"); - params.add(conv.apply(param)); + val cparam = conv.converter.apply(param); + if (cparam == null) { + sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found + return true; + } + params.add(cparam); } //System.out.println("Our params: "+params); val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time) @@ -123,21 +157,26 @@ public abstract class Command2 { return false; //Didn't handle } //TODO: Add to the help - public abstract void registerCommand(ICommand2 command); + public abstract void registerCommand(TC command); - protected void registerCommand(T command, HashMap> subcommands, char commandChar) { + protected void registerCommand(TC command, HashMap> subcommands, char commandChar) { val path = command.getCommandPath(); + int x = path.indexOf(' '); + val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x); + //var scmdmap = subcommandStrings.computeIfAbsent(mainPath, k -> new HashSet<>()); //Used to display subcommands + val scmdHelpList = new ArrayList(); + Method mainMethod = null; try { //Register the default handler first so it can be reliably overwritten - val method = command.getClass().getMethod("def", CommandSender.class, String.class); + mainMethod = command.getClass().getMethod("def", CommandSender.class, String.class); val cc = command.getClass().getAnnotation(CommandClass.class); var ht = cc == null ? new String[0] : cc.helpText(); - String[] both = Arrays.copyOf(ht, ht.length + 1); - both[ht.length] = "Usage: " + commandChar + path; //TODO: Print subcommands - ht = both; - subcommands.put(commandChar + path, new SubcommandData<>(method, command, ht)); //TODO: Disable components when the plugin is disabled + if (ht.length > 0) + ht[0] = "§6---- " + ht[0] + " ----"; + scmdHelpList.addAll(Arrays.asList(ht)); + scmdHelpList.add("§6Subcommands:"); } catch (Exception e) { TBMCCoreAPI.SendException("Could not register default handler for command /" + path, e); - } //Continue on + } for (val method : command.getClass().getMethods()) { val ann = method.getAnnotation(Subcommand.class); if (ann != null) { @@ -147,15 +186,22 @@ public abstract class Command2 { (method.getName().equals("def") ? "" : " " + method.getName().replace('_', ' ').toLowerCase()); //Add method name, unless it's 'def' ht = getHelpText(method, ht, subcommand); subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text + scmdHelpList.add(subcommand); } } + if (mainMethod != null && !subcommands.containsKey(commandChar + path)) //Command specified by the class + subcommands.put(commandChar + path, new SubcommandData<>(mainMethod, command, scmdHelpList.toArray(new String[0]))); + if (mainMethod != null && !subcommands.containsKey(mainPath)) //Main command, typically the same as the above + subcommands.put(mainPath, new SubcommandData<>(null, null, scmdHelpList.toArray(new String[0]))); } - private static String[] getHelpText(Method method, String[] ht, String subcommand) { //TODO: helpText[0]="§6---- "+helpText[0]+" ----"; + private String[] getHelpText(Method method, String[] ht, String subcommand) { val str = method.getDeclaringClass().getResourceAsStream("/commands.yml"); if (str == null) TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!")); else { + if (ht.length > 0) + ht[0] = "§6---- " + ht[0] + " ----"; YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName()); if (ccs != null) { @@ -166,7 +212,7 @@ public abstract class Command2 { val goodname = method.getName() + "(" + Arrays.stream(method.getParameterTypes()).map(cl -> cl.getCanonicalName()).collect(Collectors.joining(",")) + ")"; if (goodname.equals(mname) && params != null) { String[] both = Arrays.copyOf(ht, ht.length + 1); - both[ht.length] = "Usage: " + subcommand + " " + params; + both[ht.length] = "§6Usage:§r " + subcommand + " " + params; ht = both; } else TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method.toString() + "' != " + mname + " or params is " + params)); @@ -178,5 +224,5 @@ public abstract class Command2 { return ht; } - public abstract boolean hasPermission(CommandSender sender, ICommand2 command); + public abstract boolean hasPermission(CommandSender sender, TC command); } //TODO: Test support of Player instead of CommandSender diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java index e49ca0d..10d3035 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -1,24 +1,36 @@ package buttondevteam.lib.chat; +import buttondevteam.core.MainPlugin; import org.bukkit.command.CommandSender; import java.util.HashMap; import java.util.function.Function; -public class Command2MC extends Command2 { - - private HashMap> subcommands = new HashMap<>(); - private HashMap, Function> paramConverters = new HashMap<>(); +public class Command2MC extends Command2 { + private HashMap> subcommands = new HashMap<>(); + private HashMap, ParamConverter> paramConverters = new HashMap<>(); + @Override public boolean handleCommand(CommandSender sender, String commandLine) throws Exception { return handleCommand(sender, commandLine, subcommands, paramConverters); } - public void registerCommand(ICommand2 command) { + @Override + public void registerCommand(ICommand2MC command) { registerCommand(command, subcommands, '/'); } - public void addParamConverter(Class cl, Function converter) { - addParamConverter(cl, converter, paramConverters); + @Override + public boolean hasPermission(CommandSender sender, ICommand2MC command) { + return MainPlugin.permission.has(sender, "thorpe.command." + command.getCommandPath().replace(' ', '.')); + } + + /** + * Automatically colors the message red. + * {@see super#addParamConverter} + */ + @Override + public void addParamConverter(Class cl, Function converter, String errormsg) { + addParamConverter(cl, converter, "§c" + errormsg, paramConverters); } } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java index ddfff22..aa497fd 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java @@ -38,7 +38,8 @@ public @interface CommandClass { boolean excludeFromPath() default false; /** - * The help text to show for the players. A usage message will be also shown below it. + * The help text to show for the players. A usage message will be also shown below it.
+ * The fist line will be converted to a header. * * @return The help text */ diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java index 3aa0a96..b0da534 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java @@ -31,9 +31,9 @@ public abstract class ICommand2 { private final String path; @Getter - private final Command2 manager; + private final Command2 manager; //TIL that if I use a raw type on a variable then none of the type args will work (including what's defined on a method, not on the type) - public ICommand2(Command2 manager) { + public ICommand2(Command2 manager) { path = getcmdpath(); this.manager = manager; }