diff --git a/res/messages.txt b/res/messages.txt index 7be9d8d..f541b95 100644 --- a/res/messages.txt +++ b/res/messages.txt @@ -5,4 +5,6 @@ firstLocationSet: &aFirst location set! secondLocationSet: &aSecond location set! cancelPromptMessage: &cType '%canceltext%' to cancel. cancelText: cancel -mustHoldItem: &cYou must be holding an item to do this! \ No newline at end of file +mustHoldItem: &cYou must be holding an item to do this! +playerOnly: &cThis command can only be executed as a player! +consoleOnly: &cThis command can only be executed as console! \ No newline at end of file diff --git a/src/redempt/redlib/commandmanager/ArgType.java b/src/redempt/redlib/commandmanager/ArgType.java new file mode 100644 index 0000000..c92a6d5 --- /dev/null +++ b/src/redempt/redlib/commandmanager/ArgType.java @@ -0,0 +1,34 @@ +package redempt.redlib.commandmanager; + +import org.bukkit.command.CommandSender; +import redempt.redlib.commandmanager.Command.CommandArgumentType; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * The name for the CommandArgumentType class is rather long. This class exists only + * as an alias for CommandArgumentType to save you a little bit of time. + * @param The type this ArgType represents + */ +public class ArgType extends CommandArgumentType { + + /** + * Create a CommandArgumentType from a name and converter + * @param name The name of this command argument type, to be used in the command file + * @param convert The {@link Function} to convert from a String to whatever type this converts to + */ + public ArgType(String name, Function convert) { + super(name, convert); + } + + /** + * Create a CommandArgumentType from a name and converter + * @param name The name of this command argument type, to be used in the command file + * @param convert The {@link BiFunction} to convert from a String to whatever type this converts to + */ + public ArgType(String name, BiFunction convert) { + super(name, convert); + } + +} diff --git a/src/redempt/redlib/commandmanager/Command.java b/src/redempt/redlib/commandmanager/Command.java index 0bd7f5a..7d0367e 100644 --- a/src/redempt/redlib/commandmanager/Command.java +++ b/src/redempt/redlib/commandmanager/Command.java @@ -53,21 +53,25 @@ public class Command { private CommandArgument[] args; private ContextProvider[] contextProviders; - private String[] names; + private ContextProvider[] asserters; + protected String[] names; private String permission; private SenderType type; protected String hook; private Method methodHook; - private String help; + protected String help; private Object listener; protected boolean topLevel = false; protected Command parent = null; private boolean hideSub = false; - protected Command(String[] names, CommandArgument[] args, ContextProvider[] providers, String help, String permission, SenderType type, String hook, List children, boolean hideSub) { + protected Command() {} + + protected Command(String[] names, CommandArgument[] args, ContextProvider[] providers, ContextProvider[] asserters, String help, String permission, SenderType type, String hook, List children, boolean hideSub) { this.names = names; this.args = args; this.contextProviders = providers; + this.asserters = asserters; this.permission = permission; this.type = type; this.hook = hook; @@ -89,7 +93,7 @@ public void showHelp(CommandSender sender) { sender.sendMessage(getHelpRecursive(sender, 0).trim()); } - private String getHelpRecursive(CommandSender sender, int level) { + protected String getHelpRecursive(CommandSender sender, int level) { if (permission != null && !sender.hasPermission(permission)) { return ""; } @@ -253,6 +257,10 @@ private Object[] processArgs(String[] sargs, CommandSender sender) { } private Object[] getContext(CommandSender sender) { + if (!(sender instanceof Player)) { + sender.sendMessage(Messages.msg("playerOnly")); + return null; + } Object[] output = new Object[contextProviders.length]; for (int i = 0; i < output.length; i++) { Object obj = null; @@ -273,6 +281,26 @@ private Object[] getContext(CommandSender sender) { return output; } + private boolean assertAll(CommandSender sender) { + if (!(sender instanceof Player)) { + sender.sendMessage(Messages.msg("playerOnly")); + return false; + } + for (ContextProvider provider : asserters) { + Object o = provider.provide((Player) sender); + if (o == null) { + String error = provider.getErrorMessage(); + if (error != null) { + sender.sendMessage(error); + } else { + showHelp(sender); + } + return false; + } + } + return true; + } + /** * Registers this command and its children * @param prefix The fallback prefix @@ -329,7 +357,7 @@ public List tabComplete(CommandSender sender, String alias, String[] arg } } - private void registerHook(Object... listeners) { + protected void registerHook(Object... listeners) { loop: for (Object listener : listeners) { for (Method method : listener.getClass().getDeclaredMethods()) { @@ -360,7 +388,7 @@ private void registerHook(Object... listeners) { } } - private List tab(CommandSender sender, String[] args) { + protected List tab(CommandSender sender, String[] args) { List completions = new ArrayList<>(); if (args.length > 0) { for (Command child : children) { @@ -401,7 +429,7 @@ private List tab(CommandSender sender, String[] args) { return completions; } - private boolean execute(CommandSender sender, String[] args) { + protected boolean execute(CommandSender sender, String[] args) { if (permission != null && !sender.hasPermission(permission)) { sender.sendMessage(msg("noPermission").replace("%permission%", permission)); return true; @@ -417,19 +445,22 @@ private boolean execute(CommandSender sender, String[] args) { break; case CONSOLE: if (sender instanceof Player) { - sender.sendMessage(ChatColor.RED + "This command can only be executed from console!"); + sender.sendMessage(Messages.msg("consoleOnly")); return true; } break; case PLAYER: if (!(sender instanceof Player)) { - sender.sendMessage(ChatColor.RED + "This command can only be executed as a player!"); + sender.sendMessage(Messages.msg("playerOnly")); return true; } break; } Object[] objArgs = processArgs(parseArgs(String.join(" ", args)), sender); if (objArgs != null) { + if (asserters.length > 0 && !assertAll(sender)) { + return true; + } int size = objArgs.length + contextProviders.length; Object[] arr = new Object[size]; System.arraycopy(objArgs, 0, arr, 0, objArgs.length); @@ -596,7 +627,7 @@ public static CommandArgumentType of(String name, String... values) { private Function> tab = null; /** - * Had to make this into a single constructor that takes an Object for Maven reasons + * Create a CommandArgumentType from a name and converter * @param name The name of this command argument type, to be used in the command file * @param convert The {@link Function} to convert from a String to whatever type this converts to */ @@ -609,7 +640,7 @@ public CommandArgumentType(String name, Function convert) { } /** - * Had to make this into a single constructor that takes an Object for Maven reasons + * Create a CommandArgumentType from a name and converter * @param name The name of this command argument type, to be used in the command file * @param convert The {@link BiFunction} to convert from a String to whatever type this converts to */ diff --git a/src/redempt/redlib/commandmanager/CommandCollection.java b/src/redempt/redlib/commandmanager/CommandCollection.java index 25bb828..69316fb 100644 --- a/src/redempt/redlib/commandmanager/CommandCollection.java +++ b/src/redempt/redlib/commandmanager/CommandCollection.java @@ -1,6 +1,10 @@ package redempt.redlib.commandmanager; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.bukkit.command.CommandSender; @@ -23,9 +27,26 @@ public CommandCollection(List commands) { * @param listeners The list of listener objects which contain hooks for the commands in this collection */ public void register(String prefix, Object... listeners) { + mergeBaseCommands(); commands.stream().forEach(c -> c.register(prefix, listeners)); } + private void mergeBaseCommands() { + Map> names = new HashMap<>(); + for (Command command : commands) { + String name = String.join(", ", command.getAliases()); + List cmds = names.getOrDefault(name, new ArrayList<>()); + cmds.add(command); + names.put(name, cmds); + } + names.forEach((k, v) -> { + if (v.size() > 1) { + commands.removeAll(v); + commands.add(new MergedBaseCommand(v)); + } + }); + } + /** * * @return The commands in this CommandCollection @@ -77,4 +98,49 @@ private Command getByHookName(String hookName, Command base) { return null; } + private static class MergedBaseCommand extends Command { + + + public MergedBaseCommand(List commands) { + this.children = commands; + this.help = commands.get(0).help; + this.names = commands.get(0).names; + for (Command command : children) { + command.topLevel = false; + command.parent = this; + } + } + + @Override + public boolean execute(CommandSender sender, String[] args) { + if (children.stream().anyMatch(c -> c.execute(sender, args))) { + return true; + } + sender.sendMessage(Messages.msg("helpTitle").replace("%cmdname%", children.get(0).getName())); + sender.sendMessage(getHelpRecursive(sender, 0)); + return true; + } + + @Override + public String getHelpRecursive(CommandSender sender, int level) { + return children.stream().map(c -> c.getHelpRecursive(sender, 1)).collect(Collectors.joining("\n")); + } + + @Override + public List tab(CommandSender sender, String[] args) { + List completions = new ArrayList<>(); + children.forEach(c -> completions.addAll(c.tab(sender, args))); + return completions; + } + + @Override + public void register(String prefix, Object... listeners) { + super.register(prefix, listeners); + for (Command command : children) { + command.registerHook(listeners); + } + } + + } + } diff --git a/src/redempt/redlib/commandmanager/CommandParser.java b/src/redempt/redlib/commandmanager/CommandParser.java index a4b6700..50b076e 100644 --- a/src/redempt/redlib/commandmanager/CommandParser.java +++ b/src/redempt/redlib/commandmanager/CommandParser.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.List; import java.util.function.Function; +import java.util.regex.Pattern; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -88,6 +89,7 @@ private CommandCollection fromLines(List lines, int lineNumber) { String[] names = null; List args = new ArrayList<>(); List> contextProviders = new ArrayList<>(); + List> asserters = new ArrayList<>(); String permission = null; String hook = null; SenderType type = SenderType.EVERYONE; @@ -209,15 +211,26 @@ private CommandCollection fromLines(List lines, int lineNumber) { } if (line.startsWith("context ")) { contextProviders.clear(); - String rest = line.replaceFirst("^context ", ""); + String rest = line.substring(8); String[] split = rest.split(" "); + int fpos = pos; for (String name : split) { - int fpos = pos; ContextProvider provider = Arrays.stream(this.contextProviders).filter(c -> c.getName().equals(name)).findFirst() .orElseThrow(() -> new CommandParseException("Missing context provider " + name + ", line " + fpos)); contextProviders.add(provider); } } + if (line.startsWith("assert ")) { + asserters.clear(); + String rest = line.substring(7); + String[] split = rest.split(" "); + int fpos = pos; + for (String name : split) { + ContextProvider provider = Arrays.stream(this.contextProviders).filter(c -> c.getName().equals(name)).findFirst() + .orElseThrow(() -> new CommandParseException("Missing context provider " + name + ", line " + fpos)); + asserters.add(provider); + } + } if (line.equalsIgnoreCase("hidesub")) { hideSub = true; } @@ -230,11 +243,13 @@ private CommandCollection fromLines(List lines, int lineNumber) { if (depth == 0) { commands.add(new Command(names, args.toArray(new CommandArgument[args.size()]), contextProviders.toArray(new ContextProvider[contextProviders.size()]), + asserters.toArray(new ContextProvider[asserters.size()]), help, permission, type, hook, children, hideSub)); children = new ArrayList<>(); names = null; args = new ArrayList<>(); contextProviders = new ArrayList<>(); + asserters = new ArrayList<>(); help = null; permission = null; type = null;