diff --git a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java index 78aa6ea..a0e18f7 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java @@ -2,6 +2,7 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2.Subcommand; import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.ICommand2MC; @@ -13,29 +14,27 @@ import org.bukkit.plugin.Plugin; import java.util.Optional; @CommandClass(modOnly = true, helpText = { - "§6---- Component command ----", + "Component command", "Can be used to enable/disable/list components" }) public class ComponentCommand extends ICommand2MC { public ComponentCommand() { - getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg)); + getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!"); } @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) { + public boolean list(CommandSender sender, @Command2.OptionalArg 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); diff --git a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java index b2200a4..6286e32 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java +++ b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java @@ -14,7 +14,6 @@ import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.chat.Color; -import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.TBMCPlayer; @@ -30,7 +29,6 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.RegisteredServiceProvider; -import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -42,7 +40,6 @@ import java.util.logging.Logger; public class MainPlugin extends ButtonPlugin { public static MainPlugin Instance; - @Nullable public static Permission permission; public static boolean Test; public static Essentials ess; @@ -59,7 +56,8 @@ public class MainPlugin extends ButtonPlugin { Instance = this; PluginDescriptionFile pdf = getDescription(); logger = getLogger(); - setupPermissions(); + if (!setupPermissions()) + throw new NullPointerException("No permission plugin found!"); Test = getConfig().getBoolean("test", true); saveConfig(); Component.registerComponent(this, new PluginUpdaterComponent()); @@ -69,7 +67,7 @@ public class MainPlugin extends ButtonPlugin { Component.registerComponent(this, new MemberComponent()); Component.registerComponent(this, new TownyComponent()); ComponentManager.enableComponents(); - Command2MC.registerCommand(new ComponentCommand()); + getCommand2MC().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 c5146cd..073cb87 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java +++ b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java @@ -3,7 +3,7 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCommandPreprocessEvent; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.chat.Command2MC; +import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.player.TBMCPlayerBase; import lombok.val; import org.bukkit.Bukkit; @@ -64,7 +64,7 @@ public class PlayerListener implements Listener { public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) { if (event.isCancelled()) return; try { - event.setCancelled(Command2MC.handleCommand(event.getSender(), event.getMessage())); + event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(event.getSender(), event.getMessage())); } catch (Exception e) { TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e); } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java index d395d99..1059b26 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java @@ -1,56 +1,47 @@ package buttondevteam.core.component.members; import buttondevteam.core.MainPlugin; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.chat.TBMCCommandBase; -import lombok.val; +import buttondevteam.lib.chat.ICommand2MC; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -@CommandClass(modOnly = true, path = "member") -public class MemberCommand extends TBMCCommandBase { - @Override - public boolean OnCommand(CommandSender sender, String alias, String[] args) { - if (args.length < 2) - return false; - final boolean add; - if (args[0].equalsIgnoreCase("add")) - add = true; - else if (args[0].equalsIgnoreCase("remove")) - add = false; - else - return false; +@CommandClass(modOnly = true, path = "member", helpText = { // + "Member command", // + "Add or remove server members.", // +}) +public class MemberCommand extends ICommand2MC { + private final MemberComponent component; + + public MemberCommand(MemberComponent component) { + getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!"); + this.component = component; + } + + @Command2.Subcommand + public boolean add(CommandSender sender, OfflinePlayer player) { + return addRemove(sender, player, true); + } + + @Command2.Subcommand + public boolean remove(CommandSender sender, OfflinePlayer player) { + return addRemove(sender, player, false); + } + + public boolean addRemove(CommandSender sender, OfflinePlayer op, boolean add) { Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> { - if (MainPlugin.permission == null) { - sender.sendMessage("§cError: No permission plugin found!"); - return; - } - val op = Bukkit.getOfflinePlayer(args[1]); if (!op.hasPlayedBefore()) { sender.sendMessage("§cCannot find player or haven't played before."); return; } - if (add) { - if (MainPlugin.permission.playerAddGroup(null, op, "member")) - sender.sendMessage("§b" + op.getName() + " added as a member!"); - else - sender.sendMessage("§cFailed to add " + op.getName() + " as a member!"); - } else { - if (MainPlugin.permission.playerRemoveGroup(null, op, "member")) - sender.sendMessage("§b" + op.getName() + " removed as a member!"); - else - sender.sendMessage("§bFailed to remove " + op.getName() + " as a member!"); - } + if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup().get()) + : MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup().get())) + sender.sendMessage("§b" + op.getName() + " " + (add ? "added" : "removed") + " as a member!"); + else + sender.sendMessage("§cFailed to " + (add ? "add" : "remove") + " " + op.getName() + " as a member!"); }); return true; } - - @Override - public String[] GetHelpText(String alias) { - return new String[]{ // - "06---- Member ----", // - "Add or remove server members.", // - "Usage: /member " // - }; - } } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java index 0caeca7..1fff614 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java @@ -15,14 +15,14 @@ import java.util.Date; import static buttondevteam.core.MainPlugin.permission; public class MemberComponent extends Component implements Listener { - private ConfigData memberGroup() { + ConfigData memberGroup() { return getConfig().getData("memberGroup", "member"); } @Override protected void enable() { registerListener(this); - registerCommand(new MemberCommand()); + registerCommand(new MemberCommand(this)); } @Override diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java index 7d8a2d4..6e0a9c3 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java @@ -3,7 +3,7 @@ package buttondevteam.lib.architecture; import buttondevteam.core.ComponentManager; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException; -import buttondevteam.lib.chat.ICommand2; +import buttondevteam.lib.chat.ICommand2MC; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCCommandBase; import lombok.Getter; @@ -197,7 +197,7 @@ public abstract class Component { * * @param commandBase Custom coded command class */ - protected final void registerCommand(ICommand2 commandBase) { + protected final void registerCommand(ICommand2MC commandBase) { ButtonPlugin.getCommand2MC().registerCommand(commandBase); } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java index 5e3cf01..899fdbb 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java @@ -22,11 +22,13 @@ import java.util.stream.Collectors; /** * The method name is the subcommand, use underlines (_) to add further subcommands. - * The args may be null if the conversion failed. + * The args may be null if the conversion failed and it's optional. */ -public abstract class Command2 { +public abstract class Command2 { + protected Command2() { + } + /** - * TODO: @CommandClass(helpText=...) * Parameters annotated with this receive all of the remaining arguments */ @Target(ElementType.PARAMETER) @@ -46,12 +48,24 @@ public abstract class Command2 { String[] helpText() default {}; } + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface OptionalArg { + } + @RequiredArgsConstructor protected static class SubcommandData { public final Method method; public final T command; public final String[] helpText; } + + @RequiredArgsConstructor + protected static class ParamConverter { + public final Function converter; + public final String errormsg; + } + /** * Adds a param converter that obtains a specific object from a string parameter. * The converter may return null. @@ -60,20 +74,29 @@ public abstract class Command2 { * @param converter The converter to use * @param The type of the result */ - public abstract void addParamConverter(Class cl, Function converter); + public abstract void addParamConverter(Class cl, Function converter, String errormsg); - protected void addParamConverter(Class cl, Function converter, HashMap, Function> map) { - map.put(cl, converter); + protected void addParamConverter(Class cl, Function converter, String errormsg, HashMap, ParamConverter> map) { + map.put(cl, new ParamConverter<>(converter, errormsg)); } public abstract boolean handleCommand(CommandSender sender, String commandLine) throws Exception; - protected boolean handleCommand(CommandSender sender, String commandline, - HashMap> subcommands, HashMap, Function> paramConverters) throws Exception { + protected boolean handleCommand(CommandSender sender, String commandline, + HashMap> subcommands, + HashMap, ParamConverter> paramConverters) throws Exception { for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { String subcommand = commandline.substring(0, i).toLowerCase(); - SubcommandData sd = subcommands.get(subcommand); //O(1) - if (sd == null) continue; //TODO: This will run each time someone runs any command + SubcommandData sd = subcommands.get(subcommand); //O(1) + if (sd == null) continue; + if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands + sender.sendMessage(sd.helpText); + return true; + } + if (!hasPermission(sender, sd.command)) { + sender.sendMessage("§cYou don't have permission to use this command"); + return true; + } val params = new ArrayList(sd.method.getParameterCount()); int j = subcommand.length(), pj; Class[] parameterTypes = sd.method.getParameterTypes(); @@ -91,12 +114,18 @@ public abstract class Command2 { sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command."); return true; } + val paramArr = sd.method.getParameters(); 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 + if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) { + params.add(null); + continue; //Fill the remaining params with nulls + } else { + sender.sendMessage(sd.helpText); //Required param missing + return true; + } } j = commandline.indexOf(' ', j + 1); //End index if (j == -1) //Last parameter @@ -109,7 +138,12 @@ public abstract class Command2 { 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)); + val cparam = conv.converter.apply(param); + if (cparam == null) { + sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found + return true; + } + params.add(cparam); } //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) @@ -123,21 +157,26 @@ public abstract class Command2 { return false; //Didn't handle } //TODO: Add to the help - public abstract void registerCommand(ICommand2 command); + public abstract void registerCommand(TC command); - protected void registerCommand(T command, HashMap> subcommands, char commandChar) { + protected void registerCommand(TC command, HashMap> subcommands, char commandChar) { val path = command.getCommandPath(); + int x = path.indexOf(' '); + val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x); + //var scmdmap = subcommandStrings.computeIfAbsent(mainPath, k -> new HashSet<>()); //Used to display subcommands + val scmdHelpList = new ArrayList(); + Method mainMethod = null; try { //Register the default handler first so it can be reliably overwritten - val method = command.getClass().getMethod("def", CommandSender.class, String.class); + mainMethod = command.getClass().getMethod("def", CommandSender.class, String.class); val cc = command.getClass().getAnnotation(CommandClass.class); var ht = cc == null ? new String[0] : cc.helpText(); - String[] both = Arrays.copyOf(ht, ht.length + 1); - both[ht.length] = "Usage: " + commandChar + path; //TODO: Print subcommands - ht = both; - subcommands.put(commandChar + path, new SubcommandData<>(method, command, ht)); //TODO: Disable components when the plugin is disabled + if (ht.length > 0) + ht[0] = "§6---- " + ht[0] + " ----"; + scmdHelpList.addAll(Arrays.asList(ht)); + scmdHelpList.add("§6Subcommands:"); } catch (Exception e) { TBMCCoreAPI.SendException("Could not register default handler for command /" + path, e); - } //Continue on + } for (val method : command.getClass().getMethods()) { val ann = method.getAnnotation(Subcommand.class); if (ann != null) { @@ -147,15 +186,22 @@ public abstract class Command2 { (method.getName().equals("def") ? "" : " " + method.getName().replace('_', ' ').toLowerCase()); //Add method name, unless it's 'def' ht = getHelpText(method, ht, subcommand); subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text + scmdHelpList.add(subcommand); } } + if (mainMethod != null && !subcommands.containsKey(commandChar + path)) //Command specified by the class + subcommands.put(commandChar + path, new SubcommandData<>(mainMethod, command, scmdHelpList.toArray(new String[0]))); + if (mainMethod != null && !subcommands.containsKey(mainPath)) //Main command, typically the same as the above + subcommands.put(mainPath, new SubcommandData<>(null, null, scmdHelpList.toArray(new String[0]))); } - private static String[] getHelpText(Method method, String[] ht, String subcommand) { //TODO: helpText[0]="§6---- "+helpText[0]+" ----"; + private String[] getHelpText(Method method, String[] ht, String subcommand) { val str = method.getDeclaringClass().getResourceAsStream("/commands.yml"); if (str == null) TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!")); else { + if (ht.length > 0) + ht[0] = "§6---- " + ht[0] + " ----"; YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName()); if (ccs != null) { @@ -166,7 +212,7 @@ public abstract class Command2 { val goodname = method.getName() + "(" + Arrays.stream(method.getParameterTypes()).map(cl -> cl.getCanonicalName()).collect(Collectors.joining(",")) + ")"; if (goodname.equals(mname) && params != null) { String[] both = Arrays.copyOf(ht, ht.length + 1); - both[ht.length] = "Usage: " + subcommand + " " + params; + both[ht.length] = "§6Usage:§r " + subcommand + " " + params; ht = both; } else TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method.toString() + "' != " + mname + " or params is " + params)); @@ -178,5 +224,5 @@ public abstract class Command2 { return ht; } - public abstract boolean hasPermission(CommandSender sender, ICommand2 command); + public abstract boolean hasPermission(CommandSender sender, TC command); } //TODO: Test support of Player instead of CommandSender diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java index e49ca0d..10d3035 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -1,24 +1,36 @@ package buttondevteam.lib.chat; +import buttondevteam.core.MainPlugin; import org.bukkit.command.CommandSender; import java.util.HashMap; import java.util.function.Function; -public class Command2MC extends Command2 { - - private HashMap> subcommands = new HashMap<>(); - private HashMap, Function> paramConverters = new HashMap<>(); +public class Command2MC extends Command2 { + private HashMap> subcommands = new HashMap<>(); + private HashMap, ParamConverter> paramConverters = new HashMap<>(); + @Override public boolean handleCommand(CommandSender sender, String commandLine) throws Exception { return handleCommand(sender, commandLine, subcommands, paramConverters); } - public void registerCommand(ICommand2 command) { + @Override + public void registerCommand(ICommand2MC command) { registerCommand(command, subcommands, '/'); } - public void addParamConverter(Class cl, Function converter) { - addParamConverter(cl, converter, paramConverters); + @Override + public boolean hasPermission(CommandSender sender, ICommand2MC command) { + return MainPlugin.permission.has(sender, "thorpe.command." + command.getCommandPath().replace(' ', '.')); + } + + /** + * Automatically colors the message red. + * {@see super#addParamConverter} + */ + @Override + public void addParamConverter(Class cl, Function converter, String errormsg) { + addParamConverter(cl, converter, "§c" + errormsg, paramConverters); } } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java index ddfff22..aa497fd 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java @@ -38,7 +38,8 @@ public @interface CommandClass { boolean excludeFromPath() default false; /** - * The help text to show for the players. A usage message will be also shown below it. + * The help text to show for the players. A usage message will be also shown below it.
+ * The fist line will be converted to a header. * * @return The help text */ diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java index 3aa0a96..b0da534 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java @@ -31,9 +31,9 @@ public abstract class ICommand2 { private final String path; @Getter - private final Command2 manager; + private final Command2 manager; //TIL that if I use a raw type on a variable then none of the type args will work (including what's defined on a method, not on the type) - public ICommand2(Command2 manager) { + public ICommand2(Command2 manager) { path = getcmdpath(); this.manager = manager; }