diff --git a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java index a90337a..e8f2f91 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java @@ -48,10 +48,10 @@ public class ComponentCommand extends TBMCCommandBase { private Optional getComponentOrError(String arg, CommandSender sender) { val oc = Component.getComponents().values().stream().filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny(); - if (!oc.isPresent()) - sender.sendMessage("§cComponent not found!"); - return oc; - } + if (!oc.isPresent()) //TODO: There may be multiple components with the same name + sender.sendMessage("§cComponent not found!"); //^ Much simpler to solve in the new command system + return oc; //TODO: Offer overload options with clickable link (with all the plugins it found) + } //TODO: Tabcompletion for the new command system @Override public String[] GetHelpText(String alias) { diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/IHaveConfig.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/IHaveConfig.java index e2ddee2..2271d56 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/IHaveConfig.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/IHaveConfig.java @@ -7,7 +7,7 @@ import java.util.HashMap; import java.util.function.Function; /** - * Members of this interface should be protected (access level) + * A config system */ public final class IHaveConfig { private final HashMap> datamap = new HashMap<>(); @@ -15,7 +15,7 @@ public final class IHaveConfig { private ConfigurationSection config; /** - * May be used in testing + * May be used in testing. * * @param section May be null for testing */ diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java index af9052c..756dd25 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java @@ -1,12 +1,22 @@ package buttondevteam.lib.chat; +import lombok.RequiredArgsConstructor; +import lombok.val; import org.bukkit.command.CommandSender; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.function.Function; +/** + * The method name is the subcommand, use underlines (_) to add further subcommands. + * The args may be null if the conversion failed. + */ public abstract class Command2 { /** * Default handler for commands, can be used to copy the args too. @@ -14,7 +24,7 @@ public abstract class Command2 { * @param sender The sender which ran the command * @param command The (sub)command ran by the user * @param args All of the arguments passed as is - * @return + * @return The success of the command */ public boolean def(CommandSender sender, String command, @TextArg String args) { return false; @@ -28,4 +38,92 @@ public abstract class Command2 { @Retention(RetentionPolicy.RUNTIME) public @interface TextArg { } -} + + /** + * Methods annotated with this will be recognised as subcommands + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Subcommand { + } + + @RequiredArgsConstructor + private static class SubcommandData { + public final Method method; + public final Command2 command; + } + + private static HashMap subcommands = new HashMap<>(); + private static HashMap, Function> paramConverters = new HashMap<>(); + + public Command2() { + for (val method : getClass().getMethods()) + if (method.isAnnotationPresent(Subcommand.class)) + subcommands.put(method.getName().replace('_', ' '), new SubcommandData(method, this)); + path = getcmdpath(); + } + + /** + * Adds a param converter that obtains a specific object from a string parameter. + * The converter may return null. + * + * @param cl The class of the result object + * @param converter The converter to use + * @param The type of the result + */ + public static void addParamConverter(Class cl, Function converter) { + paramConverters.put(cl, converter); + } + + public static boolean handleCommand(CommandSender sender, String commandline) throws Exception { + for (int i = commandline.lastIndexOf(' '); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { + String subcommand = commandline.substring(0, i); + SubcommandData sd = subcommands.get(subcommand); //O(1) + if (sd == null) continue; //TODO: This will run each time someone runs any command + val params = new ArrayList(sd.method.getParameterCount()); + int j = 0, pj; + for (val cl : sd.method.getParameterTypes()) { + pj = j + 1; + j = commandline.indexOf(' ', j + 1); + String param = subcommand.substring(pj, j); + if (cl == String.class) { + params.add(param); + 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() + "'"); + params.add(conv.apply(param)); + } + sd.method.invoke(sd.command, params); + return true; //We found a method + } + return false; //Didn't handle + } //TODO: Use preprocess event and add to the help + + private final String path; + + /** + * The command's path, or name if top-level command.
+ * For example:
+ * "u admin updateplugin" or "u" for the top level one
+ * The path must be lowercase!
+ * Abstract classes with no {@link CommandClass} annotations will be ignored. + * + * @return The command path, which is the command class name by default (removing any "command" from it) - Change via the {@link CommandClass} annotation + */ + public final String GetCommandPath() { + return path; + } + + private String getcmdpath() { + if (!getClass().isAnnotationPresent(CommandClass.class)) + throw new RuntimeException( + "No @CommandClass annotation on command class " + getClass().getSimpleName() + "!"); + Function, String> getFromClass = cl -> cl.getSimpleName().toLowerCase().replace("commandbase", "") // <-- ... + .replace("command", ""); + String path = getClass().getAnnotation(CommandClass.class).path(); + path = path.length() == 0 ? getFromClass.apply(getClass()) : path; + return path; + } +} //TODO: Support Player instead of CommandSender diff --git a/ButtonCore/src/main/resources/plugin.yml b/ButtonCore/src/main/resources/plugin.yml index fb36458..f29bf69 100755 --- a/ButtonCore/src/main/resources/plugin.yml +++ b/ButtonCore/src/main/resources/plugin.yml @@ -1,4 +1,4 @@ -name: ButtonCore +name: ThorpeCore main: buttondevteam.core.MainPlugin version: 1.0 author: TBMCPlugins