From a39fd083be78326a2e2d55b9b752395fc0664507 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 8 Jul 2019 01:56:25 +0200 Subject: [PATCH] Added support for cmd tabcomplete --- BuildConfigUpdater/BuildConfigUpdater.iml | 2 + ButtonCore/pom.xml | 4 +- .../java/buttondevteam/core/MainPlugin.java | 1 + .../buttondevteam/lib/TBMCExceptionEvent.java | 3 +- .../java/buttondevteam/lib/chat/Command2.java | 6 +- .../buttondevteam/lib/chat/Command2MC.java | 126 +++++++++++++++++- .../lib/player/TBMCPlayerGetInfoEvent.java | 1 + 7 files changed, 135 insertions(+), 8 deletions(-) diff --git a/BuildConfigUpdater/BuildConfigUpdater.iml b/BuildConfigUpdater/BuildConfigUpdater.iml index d0f3587..15937e7 100644 --- a/BuildConfigUpdater/BuildConfigUpdater.iml +++ b/BuildConfigUpdater/BuildConfigUpdater.iml @@ -20,6 +20,8 @@ + + diff --git a/ButtonCore/pom.xml b/ButtonCore/pom.xml index ed35499..db16155 100755 --- a/ButtonCore/pom.xml +++ b/ButtonCore/pom.xml @@ -111,7 +111,7 @@ --> ess-repo - http://repo.ess3.net/content/repositories/essrel/ + https://ci.ender.zone/plugin/repository/everything/ Votifier @@ -209,4 +209,4 @@ scm:git:https://github.com/TBMCPlugins/mvn-repo.git scm:git:https://github.com/TBMCPlugins/mvn-repo.git - \ No newline at end of file + diff --git a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java index 2f29b6e..5c4463f 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java +++ b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java @@ -97,6 +97,7 @@ public class MainPlugin extends ButtonPlugin { getCommand2MC().registerCommand(new ComponentCommand()); getCommand2MC().registerCommand(new ThorpeCommand()); TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this); + TBMCCoreAPI.RegisterEventsForExceptions(getCommand2MC(), this); ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender ? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof Player diff --git a/ButtonCore/src/main/java/buttondevteam/lib/TBMCExceptionEvent.java b/ButtonCore/src/main/java/buttondevteam/lib/TBMCExceptionEvent.java index 44a93b1..974a52f 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/TBMCExceptionEvent.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/TBMCExceptionEvent.java @@ -1,6 +1,7 @@ package buttondevteam.lib; import lombok.Getter; +import org.bukkit.Bukkit; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -22,7 +23,7 @@ public class TBMCExceptionEvent extends Event { @java.beans.ConstructorProperties({"sourceMessage", "exception"}) public TBMCExceptionEvent(String sourceMessage, Throwable exception) { - super(true); + super(!Bukkit.isPrimaryThread()); this.sourceMessage = sourceMessage; this.exception = exception; } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java index 020bc84..d2851fe 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java @@ -109,7 +109,7 @@ public abstract class Command2 public final String errormsg; } - private HashMap> subcommands = new HashMap<>(); + protected HashMap> subcommands = new HashMap<>(); private HashMap, ParamConverter> paramConverters = new HashMap<>(); private ArrayList commandHelp = new ArrayList<>(); //Mainly needed by Discord @@ -321,4 +321,8 @@ public abstract class Command2 if (scmd == null) return null; return scmd.helpText; } + + /*public Set getAllSubcommands() { + return Collections.unmodifiableSet(subcommands.keySet()); + }*/ } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java index 3a46491..aac9cc9 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -5,16 +5,22 @@ import lombok.experimental.var; import lombok.val; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.TabCompleteEvent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; import java.util.UUID; import java.util.function.Function; -public class Command2MC extends Command2 { +public class Command2MC extends Command2 implements Listener { @Override public void registerCommand(ICommand2MC command) { super.registerCommand(command, '/'); @@ -36,7 +42,11 @@ public class Command2MC extends Command2 { @Override public boolean hasPermission(Command2MCSender sender, ICommand2MC command, Method method) { - if (sender.getSender() instanceof ConsoleCommandSender) return true; //Always allow the console + return hasPermission(sender.getSender(), command, method); + } + + public boolean hasPermission(CommandSender sender, ICommand2MC command, Method method) { + if (sender instanceof ConsoleCommandSender) return true; //Always allow the console String pg; boolean p = true; String[] perms = { @@ -47,8 +57,8 @@ public class Command2MC extends Command2 { for (String perm : perms) { if (perm != null) { if (p) { //Use OfflinePlayer to avoid fetching player data - if (sender.getSender() instanceof OfflinePlayer) - p = MainPlugin.permission.playerHas(null, (OfflinePlayer) sender.getSender(), perm); + if (sender instanceof OfflinePlayer) + p = MainPlugin.permission.playerHas(null, (OfflinePlayer) sender, perm); else p = MainPlugin.permission.playerHas(null, Bukkit.getOfflinePlayer(new UUID(0, 0)), perm); } else break; //If any of the permissions aren't granted then don't allow @@ -109,4 +119,112 @@ public class Command2MC extends Command2 { public void addParamConverter(Class cl, Function converter, String errormsg) { super.addParamConverter(cl, converter, "§c" + errormsg); } + + @EventHandler + private void handleTabComplete(TabCompleteEvent event) { + String commandline = event.getBuffer(); + CommandSender sender = event.getSender(); + //System.out.println("tab"); + for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { + String subcommand = commandline.substring(0, i).toLowerCase(); + if (subcommand.charAt(0) != '/') subcommand = '/' + subcommand; //Console + //System.out.println("Subcommand: " + subcommand); + SubcommandData sd = subcommands.get(subcommand); //O(1) + if (sd == null) continue; + //System.out.println("ht: " + Arrays.toString(sd.helpText)); + Arrays.stream(sd.helpText).skip(1).map(ht -> new HashMap.SimpleEntry<>(ht, subcommands.get(ht))).filter(e -> e.getValue() != null) + .filter(kv -> kv.getKey().startsWith(commandline)) + .filter(kv -> hasPermission(sender, kv.getValue().command, kv.getValue().method)) + .forEach(kv -> event.getCompletions().add((kv.getKey()).substring(kv.getKey().lastIndexOf(' ', commandline.length()) + 1))); + if (sd.method == null || sd.command == null) + return; + /*if (!hasPermission(sender, sd.command, sd.method)) { - TODO: Arguments + 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(); + if (parameterTypes.length == 0) + throw new Exception("No sender parameter for method '" + sd.method + "'"); + val sendertype = parameterTypes[0]; + final ChromaGamerBase cg; + if (sendertype.isAssignableFrom(sender.getClass())) + params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type + else if (sender instanceof Command2MCSender + && sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass())) + params.add(((Command2MCSender) sender).getSender()); + else if (ChromaGamerBase.class.isAssignableFrom(sendertype) + && sender instanceof Command2MCSender + && (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null + && cg.getClass() == sendertype) //The command expects a user of our system + params.add(cg); + else { + 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 + if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) { + if (cl.isPrimitive()) + params.add(Defaults.defaultValue(cl)); + else if (Number.class.isAssignableFrom(cl) + || Number.class.isAssignableFrom(cl)) + params.add(Defaults.defaultValue(Primitives.unwrap(cl))); + else + params.add(null); + continue; //Fill the remaining params with nulls + } else { + sender.sendMessage(sd.helpText); //Required param missing + return true; + } + } + if (paramArr[i1].isVarArgs()) { + params.add(commandline.substring(j + 1).split(" +")); + continue; + } + j = commandline.indexOf(' ', j + 1); //End index + if (j == -1 || paramArr[i1].isAnnotationPresent(TextArg.class)) //Last parameter + j = commandline.length(); + String param = commandline.substring(pj, j); + if (cl == String.class) { + params.add(param); + continue; + } else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) { + try { + //noinspection unchecked + Number n = ThorpeUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class) cl); + params.add(n); + } catch (ParseException e) { + sender.sendMessage("§c'" + param + "' is not a number."); + return true; + } + continue; + } + 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() + "'"); + 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); + } + try { + val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time) + if (ret instanceof Boolean) { + if (!(boolean) ret) //Show usage + sender.sendMessage(sd.helpText); + } else if (ret != null) + throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret); + return true; //We found a method + } catch (InvocationTargetException e) { + TBMCCoreAPI.SendException("An error occurred in a command handler!", e.getCause()); + }*/ + } + } } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/player/TBMCPlayerGetInfoEvent.java b/ButtonCore/src/main/java/buttondevteam/lib/player/TBMCPlayerGetInfoEvent.java index 5e63713..471a5f6 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/player/TBMCPlayerGetInfoEvent.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/player/TBMCPlayerGetInfoEvent.java @@ -24,6 +24,7 @@ public class TBMCPlayerGetInfoEvent extends Event { private final InfoTarget target; TBMCPlayerGetInfoEvent(ChromaGamerBase player, InfoTarget target) { + super(true); this.player = player; infolines = new ArrayList<>(); this.target = target;