diff --git a/src/main/java/net/nuggetmc/tplus/command/CommandHandler.java b/src/main/java/net/nuggetmc/tplus/command/CommandHandler.java index e1857c2..164a4e8 100644 --- a/src/main/java/net/nuggetmc/tplus/command/CommandHandler.java +++ b/src/main/java/net/nuggetmc/tplus/command/CommandHandler.java @@ -86,7 +86,7 @@ public class CommandHandler { } String methodName = cmd.name(); - CommandMethod commandMethod = new CommandMethod(methodName, Sets.newHashSet(cmd.aliases()), cmd.desc(), cmd.usage(), perm, command, method, autofiller); + CommandMethod commandMethod = new CommandMethod(methodName, Sets.newHashSet(cmd.aliases()), cmd.desc(), perm, command, method, autofiller); command.addMethod(methodName, commandMethod); } @@ -112,16 +112,6 @@ public class CommandHandler { help.put(commandInstance.getName(), getCommandInfo(commandInstance)); } - /* - * TODO - * Eventually, this will be a LOT better, basically not having to have this method - * at all (kind of like Drink), and even better, we won't even need a "usage" field - * in the @Command annotation, and can create the usage message from the method parameters. - */ - public void sendUsage(CommandSender sender, CommandInstance command, String usage) { - sender.sendMessage("Command Usage: " + ChatColor.YELLOW + "/" + command.getName() + " " + usage); - } - private List getCommandInfo(CommandInstance commandInstance) { List output = new ArrayList<>(); diff --git a/src/main/java/net/nuggetmc/tplus/command/CommandInstance.java b/src/main/java/net/nuggetmc/tplus/command/CommandInstance.java index 6246bfd..bbfd799 100644 --- a/src/main/java/net/nuggetmc/tplus/command/CommandInstance.java +++ b/src/main/java/net/nuggetmc/tplus/command/CommandInstance.java @@ -1,13 +1,23 @@ package net.nuggetmc.tplus.command; import net.md_5.bungee.api.ChatColor; +import net.nuggetmc.tplus.command.annotation.Arg; +import net.nuggetmc.tplus.command.annotation.OptArg; +import net.nuggetmc.tplus.command.annotation.TextArg; +import net.nuggetmc.tplus.command.exception.ArgCountException; +import net.nuggetmc.tplus.command.exception.ArgParseException; +import net.nuggetmc.tplus.command.exception.NonPlayerException; +import net.nuggetmc.tplus.utils.ChatUtils; +import org.apache.commons.lang.StringUtils; import org.bukkit.command.CommandSender; import org.bukkit.command.defaults.BukkitCommand; +import org.bukkit.entity.Player; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.*; import java.util.stream.Collectors; @@ -61,8 +71,136 @@ public abstract class CommandInstance extends BukkitCommand { arguments.remove(0); } + List parsedArguments = new ArrayList<>(); + + int index = 0; + try { - method.getMethod().invoke(method.getHandler(), sender, arguments); + for (Parameter parameter : method.getMethod().getParameters()) { + Class type = parameter.getType(); + + boolean required = !parameter.isAnnotationPresent(OptArg.class); + + if (type == CommandSender.class) { + parsedArguments.add(sender); + } else if (type == Player.class) { + if (!(sender instanceof Player)) { + throw new NonPlayerException(); + } + + parsedArguments.add(sender); + } else if (type == List.class) { + parsedArguments.add(arguments); + } + + else { + if (parameter.isAnnotationPresent(TextArg.class)) { + if (index >= arguments.size()) { + parsedArguments.add(""); + } else { + parsedArguments.add(StringUtils.join(arguments.subList(index, arguments.size()), " ")); + } + + continue; + } + + if (index >= arguments.size() && required) { + throw new ArgCountException(); + } + + String arg; + + if (index >= arguments.size()) { + arg = null; + } else { + arg = arguments.get(index); + } + + index++; + + if (type == String.class) { + parsedArguments.add(arg); + } else if (type == int.class) { + if (arg == null) { + parsedArguments.add(0); + continue; + } + + try { + parsedArguments.add(Integer.parseInt(arg)); + } catch (NumberFormatException e) { + throw new ArgParseException(parameter); + } + } else if (type == double.class) { + if (arg == null) { + parsedArguments.add(0); + continue; + } + + try { + parsedArguments.add(Double.parseDouble(arg)); + } catch (NumberFormatException e) { + throw new ArgParseException(parameter); + } + } else if (type == float.class) { + if (arg == null) { + parsedArguments.add(0); + continue; + } + + try { + parsedArguments.add(Float.parseFloat(arg)); + } catch (NumberFormatException e) { + throw new ArgParseException(parameter); + } + } else if (type == boolean.class) { + if (arg == null) { + parsedArguments.add(false); + continue; + } + + if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("false")) { + parsedArguments.add(Boolean.parseBoolean(arg)); + } else { + throw new ArgParseException(parameter); + } + } else { + parsedArguments.add(arg); + } + } + } + } + + catch (NonPlayerException e) { + sender.sendMessage("This is a player-only command."); + return true; + } + + catch (ArgParseException e) { + Parameter parameter = e.getParameter(); + String name = getArgumentName(parameter); + sender.sendMessage("The parameter " + ChatColor.YELLOW + name + ChatColor.RESET + " must be of type " + ChatColor.YELLOW + parameter.getType().toString() + ChatColor.RESET + "."); + return true; + } + + catch (ArgCountException e) { + List usageArgs = new ArrayList<>(); + + Arrays.stream(method.getMethod().getParameters()).forEach(parameter -> { + Class type = parameter.getType(); + + if (type != CommandSender.class && type != Player.class){ + usageArgs.add(getArgumentName(parameter)); + } + }); + + sender.sendMessage("Command Usage: " + org.bukkit.ChatColor.YELLOW + "/" + getName() + (method.getName().isEmpty() ? "" : " " + method.getName()) + + " " + StringUtils.join(usageArgs, " ")); + return true; + } + + try { + method.getMethod().invoke(method.getHandler(), parsedArguments.toArray()); } catch (InvocationTargetException | IllegalAccessException e) { sender.sendMessage(ChatColor.RED + "Failed to perform command."); e.printStackTrace(); @@ -71,6 +209,24 @@ public abstract class CommandInstance extends BukkitCommand { return true; } + public static String getArgumentName(Parameter parameter) { + if (parameter.isAnnotationPresent(OptArg.class)) { + OptArg arg = parameter.getAnnotation(OptArg.class); + + if (!arg.value().isEmpty()) { + return "[" + ChatUtils.camelToDashed(arg.value()) + "]"; + } + } else if (parameter.isAnnotationPresent(Arg.class)) { + Arg arg = parameter.getAnnotation(Arg.class); + + if (!arg.value().isEmpty()) { + return "<" + ChatUtils.camelToDashed(arg.value()) + ">"; + } + } + + return "<" + ChatUtils.camelToDashed(parameter.getName()) + ">"; + } + @Override @Nonnull @SuppressWarnings("unchecked") diff --git a/src/main/java/net/nuggetmc/tplus/command/CommandMethod.java b/src/main/java/net/nuggetmc/tplus/command/CommandMethod.java index 771f8c9..58b1988 100644 --- a/src/main/java/net/nuggetmc/tplus/command/CommandMethod.java +++ b/src/main/java/net/nuggetmc/tplus/command/CommandMethod.java @@ -8,7 +8,6 @@ public class CommandMethod { private final String name; private final Set aliases; private final String description; - private final String usage; private final String permission; private final CommandInstance handler; @@ -16,11 +15,10 @@ public class CommandMethod { private final Method method; private final Method autofiller; - public CommandMethod(String name, Set aliases, String description, String usage, String permission, CommandInstance handler, Method method, Method autofiller) { + public CommandMethod(String name, Set aliases, String description, String permission, CommandInstance handler, Method method, Method autofiller) { this.name = name; this.aliases = aliases; this.description = description; - this.usage = usage; this.permission = permission; this.handler = handler; this.method = method; @@ -39,10 +37,6 @@ public class CommandMethod { return description; } - public String getUsage() { - return usage; - } - public String getPermission() { return permission; } @@ -61,6 +55,6 @@ public class CommandMethod { @Override public String toString() { - return getClass().getSimpleName() + "{name=\"" + name + "\",aliases=" + aliases + ",description=\"" + description + "\",usage=\"" + usage + "\",permission=\"" + permission + "\",method=" + method + ",autofiller=" + autofiller + "}"; + return getClass().getSimpleName() + "{name=\"" + name + "\",aliases=" + aliases + ",description=\"" + description + "\",permission=\"" + permission + "\",method=" + method + ",autofiller=" + autofiller + "}"; } } diff --git a/src/main/java/net/nuggetmc/tplus/command/annotation/Arg.java b/src/main/java/net/nuggetmc/tplus/command/annotation/Arg.java new file mode 100644 index 0000000..93a0e3f --- /dev/null +++ b/src/main/java/net/nuggetmc/tplus/command/annotation/Arg.java @@ -0,0 +1,12 @@ +package net.nuggetmc.tplus.command.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Arg { + String value() default ""; +} diff --git a/src/main/java/net/nuggetmc/tplus/command/annotation/Command.java b/src/main/java/net/nuggetmc/tplus/command/annotation/Command.java index 10ac89e..0637c64 100644 --- a/src/main/java/net/nuggetmc/tplus/command/annotation/Command.java +++ b/src/main/java/net/nuggetmc/tplus/command/annotation/Command.java @@ -14,8 +14,6 @@ public @interface Command { String desc() default "Blank description."; - String usage() default ""; - String autofill() default ""; boolean visible() default true; diff --git a/src/main/java/net/nuggetmc/tplus/command/annotation/OptArg.java b/src/main/java/net/nuggetmc/tplus/command/annotation/OptArg.java new file mode 100644 index 0000000..6a16bae --- /dev/null +++ b/src/main/java/net/nuggetmc/tplus/command/annotation/OptArg.java @@ -0,0 +1,12 @@ +package net.nuggetmc.tplus.command.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OptArg { + String value() default ""; +} diff --git a/src/main/java/net/nuggetmc/tplus/command/annotation/TextArg.java b/src/main/java/net/nuggetmc/tplus/command/annotation/TextArg.java new file mode 100644 index 0000000..f836b59 --- /dev/null +++ b/src/main/java/net/nuggetmc/tplus/command/annotation/TextArg.java @@ -0,0 +1,12 @@ +package net.nuggetmc.tplus.command.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface TextArg { + String value() default ""; +} diff --git a/src/main/java/net/nuggetmc/tplus/command/commands/AICommand.java b/src/main/java/net/nuggetmc/tplus/command/commands/AICommand.java index 35eae25..adfa74f 100644 --- a/src/main/java/net/nuggetmc/tplus/command/commands/AICommand.java +++ b/src/main/java/net/nuggetmc/tplus/command/commands/AICommand.java @@ -7,8 +7,10 @@ import net.nuggetmc.tplus.bot.agent.legacyagent.ai.IntelligenceAgent; import net.nuggetmc.tplus.bot.agent.legacyagent.ai.NeuralNetwork; import net.nuggetmc.tplus.command.CommandHandler; import net.nuggetmc.tplus.command.CommandInstance; +import net.nuggetmc.tplus.command.annotation.Arg; import net.nuggetmc.tplus.command.annotation.Autofill; import net.nuggetmc.tplus.command.annotation.Command; +import net.nuggetmc.tplus.command.annotation.OptArg; import net.nuggetmc.tplus.utils.ChatUtils; import net.nuggetmc.tplus.utils.MathUtils; import org.bukkit.Bukkit; @@ -49,82 +51,26 @@ public class AICommand extends CommandInstance { @Command( name = "random", - desc = "Create bots with random neural networks, collecting feed data.", - usage = " [skin]" + desc = "Create bots with random neural networks, collecting feed data." ) - public void random(CommandSender sender, List args) { - if (!(sender instanceof Player)) { - return; - } - - if (args.size() < 2) { - commandHandler.sendUsage(sender, this, "random [skin]"); - return; - } - - String skin; - - if (args.size() < 3) { - skin = null; - } else { - skin = args.get(2); - } - - int n; - - try { - n = Integer.parseInt(args.get(0)); - } catch (NumberFormatException e) { - sender.sendMessage("The amount must be an integer!"); - return; - } - - manager.createBots((Player) sender, args.get(1), skin, n, NeuralNetwork.RANDOM); + public void random(Player sender, @Arg("amount") int amount, @Arg("name") String name, @OptArg("skin") String skin) { + manager.createBots(sender, name, skin, amount, NeuralNetwork.RANDOM); } @Command( name = "reinforcement", - desc = "Begin an AI training session.", - usage = " [skin]" + desc = "Begin an AI training session." ) - public void reinforcement(CommandSender sender, List args) { - if (!(sender instanceof Player)) { - return; - } - - Player player = (Player) sender; - - if (args.size() < 2) { - commandHandler.sendUsage(player, this, "reinforcement [skin]"); - return; - } - - String skin; - - if (args.size() < 3) { - skin = null; - } else { - skin = args.get(2); - } - - int populationSize; - - try { - populationSize = Integer.parseInt(args.get(0)); - } catch (NumberFormatException e) { - player.sendMessage("The population size must be an integer!"); - return; - } - + public void reinforcement(Player sender, @Arg("population-size") int populationSize, @Arg("name") String name, @OptArg("skin") String skin) { if (agent != null) { - player.sendMessage("A session is already active."); + sender.sendMessage("A session is already active."); return; } - player.sendMessage("Starting a new session..."); + sender.sendMessage("Starting a new session..."); - agent = new IntelligenceAgent(this, populationSize, args.get(1), skin); - agent.addUser(player); + agent = new IntelligenceAgent(this, populationSize, name, skin); + agent.addUser(sender); } public IntelligenceAgent getSession() { @@ -135,7 +81,7 @@ public class AICommand extends CommandInstance { name = "stop", desc = "End a currently running AI training session." ) - public void stop(CommandSender sender, List args) { + public void stop(CommandSender sender) { if (agent == null) { sender.sendMessage("No session is currently active."); return; @@ -162,17 +108,9 @@ public class AICommand extends CommandInstance { @Command( name = "info", desc = "Display neural network information about a bot.", - usage = "", autofill = "infoAutofill" ) - public void info(CommandSender sender, List args) { - if (args.isEmpty()) { - commandHandler.sendUsage(sender, this, "info "); - return; - } - - String name = args.get(0); - + public void info(CommandSender sender, @Arg("bot-name") String name) { sender.sendMessage("Processing request..."); scheduler.runTaskAsynchronously(plugin, () -> { diff --git a/src/main/java/net/nuggetmc/tplus/command/commands/BotCommand.java b/src/main/java/net/nuggetmc/tplus/command/commands/BotCommand.java index 8e59e09..b96a3aa 100644 --- a/src/main/java/net/nuggetmc/tplus/command/commands/BotCommand.java +++ b/src/main/java/net/nuggetmc/tplus/command/commands/BotCommand.java @@ -8,8 +8,10 @@ import net.nuggetmc.tplus.bot.agent.legacyagent.EnumTargetGoal; import net.nuggetmc.tplus.bot.agent.legacyagent.LegacyAgent; import net.nuggetmc.tplus.command.CommandHandler; import net.nuggetmc.tplus.command.CommandInstance; +import net.nuggetmc.tplus.command.annotation.Arg; import net.nuggetmc.tplus.command.annotation.Autofill; import net.nuggetmc.tplus.command.annotation.Command; +import net.nuggetmc.tplus.command.annotation.OptArg; import net.nuggetmc.tplus.utils.ChatUtils; import net.nuggetmc.tplus.utils.Debugger; import org.bukkit.Bukkit; @@ -51,83 +53,31 @@ public class BotCommand extends CommandInstance { } @Command - public void root(CommandSender sender, List args) { + public void root(CommandSender sender) { commandHandler.sendRootInfo(this, sender); } @Command( name = "create", - desc = "Create a bot.", - usage = " [skin]" + desc = "Create a bot." ) - public void create(CommandSender sender, List args) { - if (!(sender instanceof Player)) { - return; - } - - if (args.isEmpty()) { - commandHandler.sendUsage(sender, this, "create [skin]"); - return; - } - - String skin; - - if (args.size() < 2) { - skin = null; - } else { - skin = args.get(1); - } - - manager.createBots((Player) sender, args.get(0), skin, 1); + public void create(Player sender, @Arg("name") String name, @OptArg("skin") String skin) { + manager.createBots(sender, name, skin, 1); } @Command( name = "multi", - desc = "Create multiple bots at once.", - usage = " [skin]" + desc = "Create multiple bots at once." ) - public void multi(CommandSender sender, List args) { - if (!(sender instanceof Player)) { - return; - } - - if (args.size() < 2) { - commandHandler.sendUsage(sender, this, "multi [skin]"); - return; - } - - String skin; - - if (args.size() < 3) { - skin = null; - } else { - skin = args.get(2); - } - - int n; - - try { - n = Integer.parseInt(args.get(0)); - } catch (NumberFormatException e) { - sender.sendMessage("The amount must be an integer!"); - return; - } - - manager.createBots((Player) sender, args.get(1), skin, n); + public void multi(Player sender, @Arg("amount") int amount, @Arg("name") String name, @OptArg("skin") String skin) { + manager.createBots(sender, name, skin, amount); } @Command( name = "give", - desc = "Gives a specified item to all bots.", - usage = "" + desc = "Gives a specified item to all bots." ) - public void give(CommandSender sender, List args) { - if (args.isEmpty()) { - commandHandler.sendUsage(sender, this, "give "); - return; - } - - String itemName = args.get(0); + public void give(CommandSender sender, @Arg("item-name") String itemName) { Material type = Material.matchMaterial(itemName); if (type == null) { @@ -191,17 +141,11 @@ public class BotCommand extends CommandInstance { @Command( name = "armor", desc = "Gives all bots an armor set.", - usage = "", autofill = "armorAutofill" ) @SuppressWarnings("deprecation") - public void armor(CommandSender sender, List args) { - if (args.isEmpty()) { - commandHandler.sendUsage(sender, this, "armor "); - return; - } - - String tier = args.get(0).toLowerCase(); + public void armor(CommandSender sender, @Arg("armor-tier") String armorTier) { + String tier = armorTier.toLowerCase(); if (!armorTiers.containsKey(tier)) { sender.sendMessage(ChatColor.YELLOW + tier + ChatColor.RESET + " is not a valid tier!"); @@ -233,17 +177,9 @@ public class BotCommand extends CommandInstance { @Command( name = "info", desc = "Information about loaded bots.", - usage = "[name]", autofill = "infoAutofill" ) - public void info(CommandSender sender, List args) { - if (args.isEmpty()) { - commandHandler.sendUsage(sender, this, "info "); - return; - } - - String name = args.get(0); - + public void info(CommandSender sender, @Arg("bot-name") String name) { if (name == null) { sender.sendMessage(ChatColor.YELLOW + "Bot GUI coming soon!"); return; @@ -301,7 +237,7 @@ public class BotCommand extends CommandInstance { name = "reset", desc = "Remove all loaded bots." ) - public void reset(CommandSender sender, List args) { + public void reset(CommandSender sender) { sender.sendMessage("Removing every bot..."); int size = manager.fetch().size(); manager.reset(); @@ -316,6 +252,10 @@ public class BotCommand extends CommandInstance { } } + /* + * EVENTUALLY, we should make a command parent hierarchy system soon too! (so we don't have to do this crap) + * basically, in the @Command annotation, you can include a "parent" for the command, so it will be a subcommand under the specified parent + */ @Command( name = "settings", desc = "Make changes to the global configuration file and bot-specific settings.", @@ -381,15 +321,9 @@ public class BotCommand extends CommandInstance { @Command( name = "debug", desc = "Debug plugin code.", - usage = "", visible = false ) - public void debug(CommandSender sender, List args) { - if (args.isEmpty()) { - commandHandler.sendUsage(sender, this, "debug "); - return; - } - - new Debugger(sender).execute(args.get(0)); + public void debug(CommandSender sender, @Arg("expression") String expression) { + new Debugger(sender).execute(expression); } } diff --git a/src/main/java/net/nuggetmc/tplus/command/commands/MainCommand.java b/src/main/java/net/nuggetmc/tplus/command/commands/MainCommand.java index 96aff21..59ad796 100644 --- a/src/main/java/net/nuggetmc/tplus/command/commands/MainCommand.java +++ b/src/main/java/net/nuggetmc/tplus/command/commands/MainCommand.java @@ -13,8 +13,6 @@ import net.nuggetmc.tplus.utils.ChatUtils; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; -import java.util.List; - public class MainCommand extends CommandInstance { private BaseComponent[] rootInfo; @@ -24,7 +22,7 @@ public class MainCommand extends CommandInstance { } @Command - public void root(CommandSender sender, List args) { + public void root(CommandSender sender) { if (rootInfo == null) { rootInfoSetup(); } diff --git a/src/main/java/net/nuggetmc/tplus/command/exception/ArgCountException.java b/src/main/java/net/nuggetmc/tplus/command/exception/ArgCountException.java new file mode 100644 index 0000000..d9a663e --- /dev/null +++ b/src/main/java/net/nuggetmc/tplus/command/exception/ArgCountException.java @@ -0,0 +1,4 @@ +package net.nuggetmc.tplus.command.exception; + +public class ArgCountException extends Exception { +} diff --git a/src/main/java/net/nuggetmc/tplus/command/exception/ArgParseException.java b/src/main/java/net/nuggetmc/tplus/command/exception/ArgParseException.java new file mode 100644 index 0000000..4085462 --- /dev/null +++ b/src/main/java/net/nuggetmc/tplus/command/exception/ArgParseException.java @@ -0,0 +1,16 @@ +package net.nuggetmc.tplus.command.exception; + +import java.lang.reflect.Parameter; + +public class ArgParseException extends Exception { + + private final Parameter parameter; + + public ArgParseException(Parameter parameter) { + this.parameter = parameter; + } + + public Parameter getParameter() { + return parameter; + } +} diff --git a/src/main/java/net/nuggetmc/tplus/command/exception/NonPlayerException.java b/src/main/java/net/nuggetmc/tplus/command/exception/NonPlayerException.java new file mode 100644 index 0000000..8d1322f --- /dev/null +++ b/src/main/java/net/nuggetmc/tplus/command/exception/NonPlayerException.java @@ -0,0 +1,4 @@ +package net.nuggetmc.tplus.command.exception; + +public class NonPlayerException extends Exception { +} diff --git a/src/main/java/net/nuggetmc/tplus/utils/ChatUtils.java b/src/main/java/net/nuggetmc/tplus/utils/ChatUtils.java index 75169fc..73623cb 100644 --- a/src/main/java/net/nuggetmc/tplus/utils/ChatUtils.java +++ b/src/main/java/net/nuggetmc/tplus/utils/ChatUtils.java @@ -19,4 +19,18 @@ public class ChatUtils { public static String trim16(String str) { return str.length() > 16 ? str.substring(0, 16) : str; } + + public static String camelToDashed(String input) { + StringBuilder result = new StringBuilder(); + + for (char ch : input.toCharArray()) { + if (Character.isUpperCase(ch)) { + result.append("-").append(Character.toLowerCase(ch)); + } else { + result.append(ch); + } + } + + return result.toString(); + } }