diff --git a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java index e8f2f91..4b090fc 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java @@ -2,59 +2,66 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.chat.TBMCCommandBase; import lombok.val; +import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; import java.util.Optional; @CommandClass(modOnly = true) -public class ComponentCommand extends TBMCCommandBase { - @Override - public boolean OnCommand(CommandSender sender, String alias, String[] args) { - if (args.length < 1) - return false; - boolean enable = true; +public class ComponentCommand extends Command2 { + public ComponentCommand() { + addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg)); + + } + + @Subcommand + public boolean enable(CommandSender sender, Plugin plugin, String component) { + if (plugin == null) return respond(sender, "§cPlugin not found!"); + plugin.reloadConfig(); //Reload config so the new config values are read - All changes are saved to disk on disable + return enable_disable(sender, plugin, component, true); + } + + @Subcommand + public boolean disable(CommandSender sender, Plugin plugin, String component) { + if (plugin == null) return respond(sender, "§cPlugin not found!"); + return enable_disable(sender, plugin, component, false); + } + + @Subcommand + public boolean list(CommandSender sender, String plugin) { + sender.sendMessage("§6List of components:"); + Component.getComponents().values().stream().filter(c -> plugin == null || c.getPlugin().getName().equalsIgnoreCase(plugin)) //If plugin is null, don't check + .map(c -> c.getPlugin().getName() + " - " + c.getClass().getSimpleName() + " - " + (c.isEnabled() ? "en" : "dis") + "abled").forEach(sender::sendMessage); + return true; + } + + private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable) { try { - switch (args[0]) { - case "enable": - enable = true; - break; - case "disable": - enable = false; - break; - case "list": - sender.sendMessage("§6List of components:"); - Component.getComponents().values().stream().map(c -> c.getPlugin().getName() + " - " + c.getClass().getSimpleName() + " - " + (c.isEnabled() ? "en" : "dis") + "abled").forEach(sender::sendMessage); - return true; - default: - return false; - } - if (args.length < 2) - return false; - val oc = getComponentOrError(args[1], sender); + val oc = getComponentOrError(plugin, component, sender); if (!oc.isPresent()) return true; - if (enable) //Reload config so the new config values are read - getPlugin().reloadConfig(); //All changes are saved to disk on disable Component.setComponentEnabled(oc.get(), enable); sender.sendMessage(oc.get().getClass().getSimpleName() + " " + (enable ? "en" : "dis") + "abled."); } catch (Exception e) { - TBMCCoreAPI.SendException("Couldn't " + (enable ? "en" : "dis") + "able component " + args[0] + "!", e); + TBMCCoreAPI.SendException("Couldn't " + (enable ? "en" : "dis") + "able component " + component + "!", e); } return true; } - private Optional getComponentOrError(String arg, CommandSender sender) { - val oc = Component.getComponents().values().stream().filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny(); - if (!oc.isPresent()) //TODO: There may be multiple components with the same name + private Optional getComponentOrError(Plugin plugin, String arg, CommandSender sender) { + val oc = Component.getComponents().values().stream() + .filter(c -> plugin.getName().equals(c.getPlugin().getName())) + .filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny(); + if (!oc.isPresent()) 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) { + public String[] GetHelpText(String alias) { //TODO return new String[]{ "§6---- Component command ----", "Enable or disable or list components" diff --git a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java index f18cedd..c92a3c9 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java +++ b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java @@ -14,6 +14,7 @@ import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.chat.Color; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.TBMCPlayer; @@ -68,7 +69,7 @@ public class MainPlugin extends ButtonPlugin { Component.registerComponent(this, new MemberComponent()); Component.registerComponent(this, new TownyComponent()); ComponentManager.enableComponents(); - TBMCChatAPI.AddCommand(this, ComponentCommand.class); + Command2.registerCommand(new ComponentCommand()); TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this); ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender ? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks diff --git a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java index 3c3907f..91448ba 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java +++ b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java @@ -1,13 +1,21 @@ package buttondevteam.core; +import buttondevteam.lib.TBMCCommandPreprocessEvent; +import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCSystemChatEvent; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.player.TBMCPlayerBase; +import lombok.val; import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.server.ServerCommandEvent; import java.util.Arrays; @@ -32,4 +40,33 @@ public class PlayerListener implements Listener { Bukkit.getOnlinePlayers().stream().filter(event::shouldSendTo) .forEach(p -> p.sendMessage(event.getChannel().DisplayName().get().substring(0, 2) + event.getMessage())); } + + @EventHandler + public void onPlayerChatPreprocess(PlayerCommandPreprocessEvent event) { + handlePreprocess(event.getPlayer(), event.getMessage(), event); + } + + @EventHandler + public void onSystemChatPreprocess(ServerCommandEvent event) { + handlePreprocess(event.getSender(), "/" + event.getCommand(), event); + if (event.isCancelled()) event.setCommand("dontrunthiscmd"); //Bugfix + } + + private void handlePreprocess(CommandSender sender, String message, Cancellable event) { + if (event.isCancelled()) return; + val ev = new TBMCCommandPreprocessEvent(sender, message); + Bukkit.getPluginManager().callEvent(ev); + if (ev.isCancelled()) + event.setCancelled(true); //Cancel the original event + } + + @EventHandler + public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) { + if (event.isCancelled()) return; + try { + event.setCancelled(Command2.handleCommand(event.getSender(), event.getMessage())); + } catch (Exception e) { + TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e); + } + } } \ No newline at end of file diff --git a/ButtonCore/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java b/ButtonCore/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java new file mode 100755 index 0000000..a72c0b7 --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java @@ -0,0 +1,39 @@ +package buttondevteam.lib; + +import lombok.Getter; +import lombok.Setter; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Can be used to change or handle commands before they're sent. + * Called on using player, console and Discord commands. + * + * @author NorbiPeti + */ +@Getter +public class TBMCCommandPreprocessEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final CommandSender sender; + @Setter + private String message; + @Setter + private boolean cancelled; + + public TBMCCommandPreprocessEvent(CommandSender sender, String message) { + this.sender = sender; + this.message = message; //TODO: Actually call from Discord as well + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java index 02b58e7..634f23a 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java @@ -3,6 +3,7 @@ package buttondevteam.lib.architecture; import buttondevteam.core.ComponentManager; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCCommandBase; import lombok.Getter; @@ -183,6 +184,16 @@ public abstract class Component { */ protected abstract void disable(); + /** + * Registers a TBMCCommand to the component. Make sure to use {@link buttondevteam.lib.chat.CommandClass} and {@link buttondevteam.lib.chat.Command2.Subcommand}. + * You don't need to register the command in plugin.yml. + * + * @param commandBase Custom coded command class + */ + protected final void registerCommand(Command2 commandBase) { + Command2.registerCommand(commandBase); + } + /** * Registers a TBMCCommand to the component. Make sure to add it to plugin.yml and use {@link buttondevteam.lib.chat.CommandClass}. * diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java index 756dd25..014b512 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java @@ -1,5 +1,6 @@ package buttondevteam.lib.chat; +import buttondevteam.lib.player.ChromaGamerBase; import lombok.RequiredArgsConstructor; import lombok.val; import org.bukkit.command.CommandSender; @@ -22,14 +23,26 @@ public abstract class Command2 { * Default handler for commands, can be used to copy the args too. * * @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 The success of the command */ - public boolean def(CommandSender sender, String command, @TextArg String args) { + @Subcommand + public boolean def(CommandSender sender, @TextArg String args) { return false; } + /** + * Convenience method. Return with this. + * + * @param sender The sender of the command + * @param message The message to send to the sender + * @return Always true so that the usage isn't shown + */ + protected boolean respond(CommandSender sender, String message) { + sender.sendMessage(message); + return true; + } + /** * TODO: @CommandClass(helpText=...) * Parameters annotated with this receive all of the remaining arguments @@ -57,9 +70,6 @@ public abstract class Command2 { 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(); } @@ -76,16 +86,41 @@ public abstract class Command2 { } 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); + for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { + String subcommand = commandline.substring(0, i).toLowerCase(); + //System.out.println("Subcommand: "+subcommand); + //System.out.println("Subcmds: "+subcommands.toString()); SubcommandData sd = subcommands.get(subcommand); //O(1) if (sd == null) continue; //TODO: This will run each time someone runs any command + //System.out.println("sd.method: "+sd.method); //TODO: Rename in Maven 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); + 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 (ChromaGamerBase.class.isAssignableFrom(sendertype) + && (cg = ChromaGamerBase.getFromSender(sender)) != 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; + } + 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 + params.add(null); + continue; //Fill the remaining params with nulls + } + j = commandline.indexOf(' ', j + 1); //End index + if (j == -1) //Last parameter + j = commandline.length(); + String param = commandline.substring(pj, j); if (cl == String.class) { params.add(param); continue; @@ -95,11 +130,25 @@ public abstract class Command2 { 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); + //System.out.println("Our params: "+params); + 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) + sender.sendMessage("Wrong usage."); //TODO: Show help text + } 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 } return false; //Didn't handle - } //TODO: Use preprocess event and add to the help + } //TODO: Add to the help + + public static void registerCommand(Command2 command) { + for (val method : command.getClass().getMethods()) + if (method.isAnnotationPresent(Subcommand.class)) + subcommands.put("/" + command.path + //Add command path (class name by default) + (method.getName().equals("def") ? "" : " " + method.getName().replace('_', ' ').toLowerCase()), //Add method name, unless it's 'def' + new SubcommandData(method, command)); //Result of the above (def) is that it will show the help text + } private final String path; @@ -126,4 +175,4 @@ public abstract class Command2 { path = path.length() == 0 ? getFromClass.apply(getClass()) : path; return path; } -} //TODO: Support Player instead of CommandSender +} //TODO: Test support of Player instead of CommandSender diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java index a2da67e..66eac93 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java @@ -160,7 +160,7 @@ public class TBMCChatAPI { } catch (Exception e) { TBMCCoreAPI.SendException("An error occured while registering command " + thecmdclass.getSimpleName(), e); } - } //TODO: onCommand(CommandSender sender, String alias, int arg1, String arg2) (planned for a while) + } /** *

diff --git a/ButtonCore/src/main/resources/plugin.yml b/ButtonCore/src/main/resources/plugin.yml index f29bf69..43c609f 100755 --- a/ButtonCore/src/main/resources/plugin.yml +++ b/ButtonCore/src/main/resources/plugin.yml @@ -15,3 +15,4 @@ commands: description: Add or remove a member component: description: Enable or disable or list components + dontrunthiscmd: \ No newline at end of file