From b59a090e1383f80d9a5630f5eadf8d0b2f98ca42 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Wed, 3 Aug 2022 22:11:40 +0200 Subject: [PATCH] Implement argument handling and add a bunch of TODOs --- .../java/buttondevteam/lib/chat/Command2.java | 137 ++++++++---------- .../buttondevteam/lib/chat/Command2MC.java | 6 +- .../lib/chat/commands/CommandArgument.java | 10 ++ .../lib/chat/commands/ParameterData.java | 9 ++ .../lib/chat/commands/SubcommandData.java | 30 ++++ .../lib/player/ChromaGamerBase.java | 1 + 6 files changed, 115 insertions(+), 78 deletions(-) create mode 100644 Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.java create mode 100644 Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/ParameterData.java create mode 100644 Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.java diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java index e1693a5..cccc4fb 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java @@ -1,17 +1,16 @@ package buttondevteam.lib.chat; import buttondevteam.core.MainPlugin; -import buttondevteam.lib.ChromaUtils; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.commands.CommandArgument; +import buttondevteam.lib.chat.commands.ParameterData; +import buttondevteam.lib.chat.commands.SubcommandData; import buttondevteam.lib.player.ChromaGamerBase; -import com.google.common.base.Defaults; -import com.google.common.primitives.Primitives; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.ParseResults; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.tree.LiteralCommandNode; -import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.val; import org.bukkit.Bukkit; @@ -26,8 +25,6 @@ import java.lang.annotation.Target; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; -import java.text.NumberFormat; -import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -41,13 +38,10 @@ import static buttondevteam.lib.chat.CoreCommandBuilder.literal; * The method name is the subcommand, use underlines (_) to add further subcommands. * The args may be null if the conversion failed and it's optional. */ +@RequiredArgsConstructor public abstract class Command2, TP extends Command2Sender> { - protected Command2() { - commandHelp.add("§6---- Commands ----"); - } - /** - * Parameters annotated with this receive all of the remaining arguments + * Parameters annotated with this receive all the remaining arguments */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @@ -84,13 +78,6 @@ public abstract class Command2, TP extends Command2Send public @interface OptionalArg { } - @AllArgsConstructor - protected static class SubcommandData> { - public final Method method; - public final T command; - public String[] helpText; - } - /*protected static class SubcommandHelpData extends SubcommandData { private final TreeSet ht = new TreeSet<>(); private BukkitTask task; @@ -127,7 +114,7 @@ public abstract class Command2, TP extends Command2Send private final ArrayList commandHelp = new ArrayList<>(); //Mainly needed by Discord private final CommandDispatcher dispatcher = new CommandDispatcher<>(); - private char commandChar; + private final char commandChar; /** * Adds a param converter that obtains a specific object from a string parameter. @@ -165,9 +152,8 @@ public abstract class Command2, TP extends Command2Send * @param commandNode The processed command the sender sent * @param sd The subcommand data * @param sync Whether the command was originally sync - * @throws Exception If something's not right */ - private void handleCommandAsync(TP sender, ParseResults parsed, SubcommandData sd, boolean sync) throws Exception { + private void handleCommandAsync(TP sender, ParseResults parsed, SubcommandData sd, boolean sync) { if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands sender.sendMessage(sd.helpText); return; @@ -176,17 +162,12 @@ public abstract class Command2, TP extends Command2Send sender.sendMessage("§cYou don't have permission to use this command"); return; } - val params = new ArrayList(sd.method.getParameterCount()); - Class[] parameterTypes = sd.method.getParameterTypes(); - if (parameterTypes.length == 0) - throw new Exception("No sender parameter for method '" + sd.method + "'"); + // TODO: WIP if (processSenderType(sender, sd, params, parameterTypes)) return; // Checks if the sender is the wrong type - val paramArr = sd.method.getParameters(); val args = parsed.getContext().getArguments(); - 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 + for (var arg : sd.arguments.entrySet()) { + // TODO: Invoke using custom method + /*if (pj == commandline.length() + 1) { //No param given if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) { if (cl.isPrimitive()) params.add(Defaults.defaultValue(cl)); @@ -200,50 +181,13 @@ public abstract class Command2, TP extends Command2Send sender.sendMessage(sd.helpText); //Required param missing return; } - } - if (paramArr[i1].isVarArgs()) { + }*/ + /*if (paramArr[i1].isVarArgs()) { - TODO: Varargs support? (colors?) 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 { - if (cl == boolean.class) { - params.add(Boolean.parseBoolean(param)); - continue; - } - if (cl == char.class) { - if (param.length() != 1) { - sender.sendMessage("§c'" + param + "' is not a character."); - return; - } - params.add(param.charAt(0)); - continue; - } - //noinspection unchecked - Number n = ChromaUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class) cl); - params.add(n); - } catch (ParseException e) { - sender.sendMessage("§c'" + param + "' is not a number."); - return; - } - 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 + "'"); - val cparam = conv.converter.apply(param); - if (cparam == null) { - sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found - return; - } - params.add(cparam); + }*/ + // TODO: Character handling (strlen) + // TODO: Param converter } Runnable invokeCommand = () -> { try { @@ -266,12 +210,12 @@ public abstract class Command2, TP extends Command2Send invokeCommand.run(); } //TODO: Add to the help - private boolean processSenderType(TP sender, SubcommandData sd, ArrayList params, Class[] parameterTypes) { - val sendertype = parameterTypes[0]; + private boolean processSenderType(TP sender, SubcommandData sd, ArrayList params) { + val sendertype = sd.senderType; 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 + else if (sender instanceof Command2MCSender // TODO: This is Minecraft only && sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass())) params.add(((Command2MCSender) sender).getSender()); else if (ChromaGamerBase.class.isAssignableFrom(sendertype) @@ -281,15 +225,54 @@ public abstract class Command2, TP extends Command2Send params.add(cg); else { sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command."); - sender.sendMessage(sd.helpText); //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only + sender.sendMessage(sd.getHelpText(sender)); //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only return true; } return false; } + protected LiteralCommandNode processSubcommand(TC command, Method method) throws Exception { + val params = new ArrayList(method.getParameterCount()); + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length == 0) + throw new Exception("No sender parameter for method '" + method + "'"); + val paramArr = method.getParameters(); + val arguments = new HashMap(parameterTypes.length - 1); + for (int i1 = 1; i1 < parameterTypes.length; i1++) { + Class cl = parameterTypes[i1]; + var pdata = getParameterData(method, i1); + arguments.put(pdata.name, new CommandArgument(pdata.name, cl, pdata.description)); + } + var sd = new SubcommandData(parameterTypes[0], arguments, command, command.getHelpText(method, method.getAnnotation(Subcommand.class)), null); // TODO: Help text + return getSubcommandNode(method, sd); // TODO: Integrate with getCommandNode and store SubcommandData instead of help text + } + + /** + * Get parameter data for the given subcommand. Attempts to read it from the commands file, if it fails, it will return generic info. + * + * @param method The method the subcommand is created from + * @param i The index to use if no name was found + * @return Parameter data object + */ + private ParameterData getParameterData(Method method, int i) { + return null; // TODO: Parameter data (from help text method) + } + + /** + * Register a command in the command system. The way this command gets registered may change depending on the implementation. + * Always invoke {@link #registerCommandSuper(ICommand2)} when implementing this method. + * + * @param command The command to register + */ public abstract void registerCommand(TC command); - protected LiteralCommandNode registerCommand(TC command, char commandChar) { + /** + * Registers a command in the Command2 system, so it can be looked up and executed. + * + * @param command The command to register + * @return The Brigadier command node if you need it for something (like tab completion) + */ + protected LiteralCommandNode registerCommandSuper(TC command) { return dispatcher.register(getCommandNode(command)); } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java index f53f6dd..e970e9b 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -38,6 +38,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; public class Command2MC extends Command2 implements Listener { + public Command2MC() { + super('/'); + } + /** * Don't use directly, use the method in Component and ButtonPlugin to automatically unregister the command when needed. * @@ -52,7 +56,7 @@ public class Command2MC extends Command2 implemen int i = cpath.indexOf(' '); mainpath = cpath.substring(0, i == -1 ? cpath.length() : i); }*/ - var subcmds = super.registerCommand(command, '/'); + var subcmds = super.registerCommandSuper(command); var bcmd = registerOfficially(command, subcmds); if (bcmd != null) for (String alias : bcmd.getAliases()) diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.java new file mode 100644 index 0000000..0aeeec5 --- /dev/null +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.java @@ -0,0 +1,10 @@ +package buttondevteam.lib.chat.commands; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class CommandArgument { + public final String name; + public final Class type; + public final String description; +} diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/ParameterData.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/ParameterData.java new file mode 100644 index 0000000..b0bcf7d --- /dev/null +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/ParameterData.java @@ -0,0 +1,9 @@ +package buttondevteam.lib.chat.commands; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class ParameterData { + public final String name; + public final String description; +} diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.java new file mode 100644 index 0000000..4715c75 --- /dev/null +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.java @@ -0,0 +1,30 @@ +package buttondevteam.lib.chat.commands; + +import buttondevteam.lib.chat.ICommand2; +import lombok.RequiredArgsConstructor; + +import java.util.Map; +import java.util.function.Function; + +@RequiredArgsConstructor +public final class SubcommandData> { + // The actual sender type may not be represented by Command2Sender (TP) + public final Class senderType; + public final Map arguments; + public final TC command; + + /** + * Static help text added through annotations. May be overwritten with the getter. + */ + private final String[] staticHelpText; + /** + * Custom help text that depends on the context. Overwrites the static one. + * The function receives the sender but its type is not guaranteed to match the one at the subcommand. + * It will either match or be a Command2Sender, however. + */ + private final Function helpTextGetter; + + public String[] getHelpText(Object sender) { + return staticHelpText == null ? helpTextGetter.apply(sender) : staticHelpText; + } +} diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java b/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java index f931a15..a03aa4f 100755 --- a/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java @@ -41,6 +41,7 @@ public abstract class ChromaGamerBase { /** * Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)}) and to init the configs. + * Also, to construct an instance if an abstract class is provided. */ public static void RegisterPluginUserClass(Class userclass, Supplier constructor) { Class cl;