From 8616ecbefc9f546d6024afc5a52b5573132a3e66 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 18 Apr 2023 01:52:40 +0200 Subject: [PATCH] Fixed getSubcommands() and other command fixes - Command2MC tab completion still awaits - but then that might be all that's left before testing --- .../buttondevteam/core/ChromaCommand.java | 2 +- .../java/buttondevteam/core/MainPlugin.kt | 10 +- .../component/restart/PrimeRestartCommand.kt | 3 - .../lib/architecture/Component.kt | 5 +- .../java/buttondevteam/lib/chat/Command2.kt | 54 +++-- .../java/buttondevteam/lib/chat/Command2MC.kt | 211 +++++++++--------- .../lib/chat/CoreCommandBuilder.kt | 4 +- .../buttondevteam/lib/chat/CoreCommandNode.kt | 8 +- .../java/buttondevteam/lib/chat/ICommand2.kt | 34 ++- .../buttondevteam/lib/chat/ICommand2MC.kt | 8 +- .../lib/chat/commands/CommandUtils.kt | 15 +- .../lib/chat/commands/SubcommandData.kt | 2 +- 12 files changed, 189 insertions(+), 167 deletions(-) diff --git a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java index 3e03a3f..bc5bb02 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java @@ -14,7 +14,7 @@ import java.util.Optional; @CommandClass public class ChromaCommand extends ICommand2MC { public ChromaCommand() { - manager.addParamConverter(ButtonPlugin.class, name -> + getManager().addParamConverter(ButtonPlugin.class, name -> (ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name)) .filter(plugin -> plugin instanceof ButtonPlugin).orElse(null), "No Chroma plugin found by that name.", () -> Arrays.stream(Bukkit.getPluginManager().getPlugins()) diff --git a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt index f10603d..93525e2 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt @@ -57,7 +57,7 @@ class MainPlugin : ButtonPlugin() { public override fun pluginEnable() { instance = this val pdf = description - if (!setupPermissions()) throw NullPointerException("No permission plugin found!") + setupPermissions() if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials logger.warning("No economy plugin found! Components using economy will not be registered.") saveConfig() @@ -134,9 +134,8 @@ class MainPlugin : ButtonPlugin() { logger.info("Player data saved.") } - private fun setupPermissions(): Boolean { - permission = setupProvider(Permission::class.java) - return permission != null + private fun setupPermissions() { + permission = setupProvider(Permission::class.java) ?: throw NullPointerException("No permission plugin found!") } private fun setupEconomy(): Boolean { @@ -159,8 +158,7 @@ class MainPlugin : ButtonPlugin() { companion object { lateinit var instance: MainPlugin - @JvmField - var permission: Permission? = null + lateinit var permission: Permission @JvmField var ess: Essentials? = null diff --git a/Chroma-Core/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.kt b/Chroma-Core/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.kt index c71a086..741f933 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.kt @@ -42,7 +42,4 @@ class PrimeRestartCommand : ICommand2MC() { Bukkit.spigot().restart() } } - - companion object { - } } \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt index d5b0db8..1c2727e 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt @@ -77,7 +77,7 @@ abstract class Component { * @param command Custom coded command class */ fun registerCommand(command: ICommand2MC) { - if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin) + if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin) // TODO: Require ButtonPlugin command.registerToComponent(this) ButtonPlugin.command2MC.registerCommand(command) } @@ -102,8 +102,7 @@ abstract class Component { */ fun getConfigMap(key: String, defaultProvider: Map>): Map { val c: ConfigurationSection = config.config - var cs = c.getConfigurationSection(key) - if (cs == null) cs = c.createSection(key) + val cs = c.getConfigurationSection(key) ?: c.createSection(key) val res = cs.getValues(false).entries.stream() .filter { (_, value) -> value is ConfigurationSection } .collect(Collectors.toMap( diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt index 75de6a1..52ee3df 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt @@ -133,13 +133,22 @@ abstract class Command2, TP : Command2Sender>( * @param command The command to register * @return The Brigadier command node if you need it for something (like tab completion) */ - protected fun registerCommandSuper(command: TC): LiteralCommandNode { - var mainCommandNode: LiteralCommandNode? = null + protected fun registerCommandSuper(command: TC): CoreCommandNode { + var mainCommandNode: CoreCommandNode? = null for (meth in command.javaClass.methods) { val ann = meth.getAnnotation(Subcommand::class.java) ?: continue val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ') val (lastNode, mainNode, remainingPath) = registerNodeFromPath(fullPath) - lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command), fullPath)) + lastNode.addChild( + getExecutableNode( + meth, + command, + ann, + remainingPath, + CommandArgumentHelpManager(command), + fullPath + ) + ) if (mainCommandNode == null) mainCommandNode = mainNode else if (mainNode!!.name != mainCommandNode.name) { MainPlugin.instance.logger.warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.javaClass.simpleName) @@ -193,16 +202,17 @@ abstract class Command2, TP : Command2Sender>( * @return The last no-op node that can be used to register the executable node, * the main command node and the last part of the command path (that isn't registered yet) */ - private fun registerNodeFromPath(path: String): Triple, LiteralCommandNode?, String> { + private fun registerNodeFromPath(path: String): Triple, CoreCommandNode?, String> { val split = path.split(" ") var parent: CommandNode = dispatcher.root - var mainCommand: LiteralCommandNode? = null + var mainCommand: CoreCommandNode? = null split.forEachIndexed { i, part -> val child = parent.getChild(part) if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp(part, getSubcommandList()) .executes(::executeHelpText).build().also { parent = it }) else parent = child - if (i == 0) mainCommand = parent as LiteralCommandNode // Has to be a literal, if not, well, error + if (i == 0) mainCommand = + parent as CoreCommandNode // Has to be our own literal node, if not, well, error } return Triple(parent, mainCommand, split.last()) } @@ -362,9 +372,9 @@ abstract class Command2, TP : Command2Sender>( * * @return A set of command node objects containing the commands */ - val commandNodes: Set> + val commandNodes: Set> get() = dispatcher.root.children.stream() - .map { node: CommandNode -> node.core() } + .map { node: CommandNode -> node.core() } .collect(Collectors.toUnmodifiableSet()) /** @@ -373,7 +383,7 @@ abstract class Command2, TP : Command2Sender>( * @param command The exact name of the command * @return A command node */ - fun getCommandNode(command: String): CoreCommandNode { // TODO: What should this return? No-op? Executable? What's the use case? + fun getCommandNode(command: String): CoreCommandNode { // TODO: What should this return? No-op? Executable? What's the use case? return dispatcher.root.getChild(command).core() } @@ -391,14 +401,14 @@ abstract class Command2, TP : Command2Sender>( * * @param condition The condition for removing a given command */ - fun unregisterCommandIf(condition: Predicate>>, nested: Boolean) { + fun unregisterCommandIf(condition: Predicate>>, nested: Boolean) { dispatcher.root.children.removeIf { node -> node.coreExecutable()?.let { condition.test(it) } ?: false } if (nested) for (child in dispatcher.root.children) unregisterCommandIf(condition, child.core()) } private fun unregisterCommandIf( - condition: Predicate>>, - root: CoreCommandNode + condition: Predicate>>, + root: CoreCommandNode ) { // TODO: Remvoe no-op nodes without children // Can't use getCoreChildren() here because the collection needs to be modifiable @@ -408,8 +418,24 @@ abstract class Command2, TP : Command2Sender>( /** * Get all subcommands of the specified command. Only returns executable nodes. + * + * @param mainCommand The command to get the subcommands of + * @param deep Whether to get all subcommands recursively or only the direct children */ - fun getSubcommands(mainCommand: LiteralCommandNode): List>> { - return dispatcher.root.children.mapNotNull { it.coreExecutable() } // TODO: Needs more depth + fun getSubcommands( + mainCommand: LiteralCommandNode, + deep: Boolean = true + ): List>> { + return getSubcommands(mainCommand, deep, mainCommand.core()) + } + + private fun getSubcommands( + mainCommand: LiteralCommandNode, + deep: Boolean = true, + root: CoreCommandNode + ): List>> { + + return root.children.mapNotNull { it.coreExecutable() } + + if (deep) root.children.flatMap { getSubcommands(mainCommand, deep, it.core()) } else emptyList() } } \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt index 98a3508..b18016d 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt @@ -5,6 +5,7 @@ import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.architecture.ButtonPlugin import buttondevteam.lib.architecture.Component import buttondevteam.lib.chat.commands.CommandUtils +import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable import buttondevteam.lib.chat.commands.MCCommandSettings import buttondevteam.lib.chat.commands.SubcommandData import buttondevteam.lib.player.ChromaGamerBase @@ -29,7 +30,6 @@ import org.bukkit.entity.Player import org.bukkit.event.Listener import org.bukkit.permissions.Permission import org.bukkit.permissions.PermissionDefault -import java.lang.reflect.Method import java.lang.reflect.Parameter import java.util.* import java.util.function.BiConsumer @@ -43,42 +43,24 @@ class Command2MC : Command2('/', true), Listener * @param command The command to register */ override fun registerCommand(command: ICommand2MC) { - /*String mainpath; - var plugin = command.getPlugin(); - { - String cpath = command.getCommandPath(); - int i = cpath.indexOf(' '); - mainpath = cpath.substring(0, i == -1 ? cpath.length() : i); - }*/ val commandNode = super.registerCommandSuper(command) val bcmd = registerOfficially(command, commandNode) if (bcmd != null) // TODO: Support aliases super.registerCommandSuper(command) - val perm = "chroma.command." + command.commandPath.replace(' ', '.') - if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset - Bukkit.getPluginManager().addPermission(Permission(perm, - PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only - for (node in getSubcommands(commandNode)) { - if (path.length > 0) { - val subperm = perm + path - if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset - Bukkit.getPluginManager().addPermission( - Permission( - subperm, - PermissionDefault.TRUE - ) - ) //Allow commands by default, it will check mod-only - } + val permPrefix = "chroma.command." + //Allow commands by default, it will check mod-only + val nodes = commandNode.coreExecutable() + ?.let { getSubcommands(commandNode) + it } ?: getSubcommands(commandNode) + for (node in nodes) { + val subperm = permPrefix + node.data.fullPath.replace(' ', '.') + if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset + Bukkit.getPluginManager().addPermission(Permission(subperm, PermissionDefault.TRUE)) val pg = permGroup(node.data) if (pg.isEmpty()) continue val permGroup = "chroma.$pg" + //Do not allow any commands that belong to a group by default if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times - Bukkit.getPluginManager().addPermission( - Permission( - permGroup, - PermissionDefault.OP - ) - ) //Do not allow any commands that belong to a group + Bukkit.getPluginManager().addPermission(Permission(permGroup, PermissionDefault.OP)) } } @@ -111,7 +93,7 @@ class Command2MC : Command2('/', true), Listener /** * Returns the first group found in the hierarchy starting from the command method **or** the mod group if *any* of the superclasses are mod only. * - * @param method The subcommand to check + * @param data The data of the subcommand to check * @return The permission group for the subcommand or empty string */ private fun permGroup(data: SubcommandData): String { @@ -170,7 +152,8 @@ class Command2MC : Command2('/', true), Listener val i = commandline.indexOf(' ') val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash //Our commands aren't PluginCommands, unless it's specified in the plugin.yml - return if ((!checkPlugin || (MainPlugin.instance.prioritizeCustomCommands.get() == true)) + // So we need to handle the command if it's not a plugin command or if it's a plugin command, but for a ButtonPlugin + return if (!checkPlugin || MainPlugin.instance.prioritizeCustomCommands.get() || Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != false ) super.handleCommand(sender, commandline) else false @@ -178,28 +161,30 @@ class Command2MC : Command2('/', true), Listener private var shouldRegisterOfficially = true private fun registerOfficially(command: ICommand2MC, node: LiteralCommandNode): Command? { - return if (!shouldRegisterOfficially || command.plugin == null) null else try { - val cmdmap = Bukkit.getServer().javaClass.getMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap + return if (!shouldRegisterOfficially) null else try { + val cmdmap = + Bukkit.getServer().javaClass.getMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap val path = command.commandPath val x = path.indexOf(' ') val mainPath = path.substring(0, if (x == -1) path.length else x) - var bukkitCommand: Command - run { - //Commands conflicting with Essentials have to be registered in plugin.yml - val oldcmd = cmdmap.getCommand(command.plugin.name + ":" + mainPath) //The label with the fallback prefix is always registered - if (oldcmd == null) { - bukkitCommand = BukkitCommand(mainPath) - cmdmap.register(command.plugin.name, bukkitCommand) - } else { - bukkitCommand = oldcmd - if (bukkitCommand is PluginCommand) (bukkitCommand as PluginCommand).executor = CommandExecutor { sender: CommandSender, command: Command, label: String, args: Array -> this.executeCommand(sender, command, label, args) } - } - bukkitCommand = oldcmd ?: BukkitCommand(mainPath) + val bukkitCommand: Command + //Commands conflicting with Essentials have to be registered in plugin.yml + val oldcmd = + cmdmap.getCommand(command.plugin.name + ":" + mainPath) //The label with the fallback prefix is always registered + if (oldcmd == null) { + bukkitCommand = BukkitCommand(mainPath) + cmdmap.register(command.plugin.name, bukkitCommand) + } else { + bukkitCommand = oldcmd + if (bukkitCommand is PluginCommand) bukkitCommand.setExecutor(this::executeCommand) } if (CommodoreProvider.isSupported()) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand) bukkitCommand } catch (e: Exception) { - if (command.component == null) TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.plugin) else TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.component) + if (command.component == null) + TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.plugin) + else + TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.component) shouldRegisterOfficially = false null } @@ -216,12 +201,14 @@ class Command2MC : Command2('/', true), Listener sender.sendMessage("§cAn internal error occurred.") return true } + ///trim(): remove space if there are no args handleCommand(Command2MCSender(sender, user.channel.get(), sender), - ("/" + command.name + " " + java.lang.String.join(" ", *args)).trim { it <= ' ' }, false) ///trim(): remove space if there are no args + ("/${command.name} ${args.joinToString(" ")}").trim { it <= ' ' }, false + ) return true } - private class BukkitCommand(name: String?) : Command(name) { + private class BukkitCommand(name: String) : Command(name) { override fun execute(sender: CommandSender, commandLabel: String, args: Array): Boolean { return ButtonPlugin.command2MC.executeCommand(sender, this, commandLabel, args) } @@ -232,20 +219,42 @@ class Command2MC : Command2('/', true), Listener } @Throws(IllegalArgumentException::class) - override fun tabComplete(sender: CommandSender, alias: String, args: Array, location: Location): List { - return emptyList() + override fun tabComplete( + sender: CommandSender, + alias: String, + args: Array?, + location: Location? + ): MutableList { + return mutableListOf() } } private object TabcompleteHelper { - private var commodore: Commodore? = null - private fun appendSubcommand(path: String, parent: CommandNode, - subcommand: SubcommandData?): LiteralCommandNode { + private val commodore: Commodore by lazy { + val commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier + commodore.register( + LiteralArgumentBuilder.literal("un") // TODO: This is a test + .redirect( + RequiredArgumentBuilder.argument( + "unsomething", + StringArgumentType.word() + ).suggests { _, builder -> + builder.suggest("untest").buildFuture() + }.build() + ) + ) + commodore + } + + private fun appendSubcommand( + path: String, parent: CommandNode, + subcommand: SubcommandData? + ): LiteralCommandNode { var scmd: LiteralCommandNode if (parent.getChild(path) as LiteralCommandNode?. also { scmd = it } != null) return scmd val scmdBuilder = LiteralArgumentBuilder.literal(path) if (subcommand != null) scmdBuilder.requires { o: Any? -> - val sender = commodore!!.getBukkitSender(o) + val sender = commodore.getBukkitSender(o) subcommand.hasPermission(sender) } scmd = scmdBuilder.build() @@ -253,40 +262,33 @@ class Command2MC : Command2('/', true), Listener return scmd } - private fun registerTabcomplete(command2MC: ICommand2MC, commandNode: LiteralCommandNode, bukkitCommand: Command) { - if (commodore == null) { - commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier - commodore.register(LiteralArgumentBuilder.literal("un") - .redirect(RequiredArgumentBuilder.argument( - "unsomething", - StringArgumentType.word() - ).suggests { context: CommandContext?, builder: SuggestionsBuilder -> - builder.suggest("untest").buildFuture() - }.build() - ) - ) - } - commodore!!.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary - val customTCmethods = Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments - .flatMap { method: Method -> - Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream() - .flatMap { ctcmAnn: CustomTabCompleteMethod -> - val paths = Optional.of>(ctcmAnn.subcommand()).filter { s: Array -> s.size > 0 } - .orElseGet { - arrayOf( - CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' } - ) - } - Arrays.stream(paths).map { name: String? -> Triplet(name, ctcmAnn, method) } - } - }.toList() + fun registerTabcomplete( + command2MC: ICommand2MC, + commandNode: LiteralCommandNode, + bukkitCommand: Command + ) { + commodore.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary + val customTCmethods = + Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments + .flatMap { method -> + Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream() + .flatMap { ctcmAnn -> + val paths = Optional.of(ctcmAnn.subcommand).filter { s -> s.isNotEmpty() } + .orElseGet { + arrayOf( + CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' }) + } + Arrays.stream(paths).map { name: String? -> Triple(name, ctcmAnn, method) } + } + }.toList() for (subcmd in subcmds) { val subpathAsOne = CommandUtils.getCommandPath(subcmd.method.getName(), ' ').trim { it <= ' ' } val subpath = subpathAsOne.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() var scmd: CommandNode = cmd - if (subpath[0].length > 0) { //If the method is def, it will contain one empty string + if (subpath[0].isNotEmpty()) { //If the method is def, it will contain one empty string for (s in subpath) { - scmd = appendSubcommand(s, scmd, subcmd) //Add method name part of the path (could_be_multiple()) + scmd = + appendSubcommand(s, scmd, subcmd) //Add method name part of the path (could_be_multiple()) } } val parameters: Array = subcmd.method.getParameters() @@ -294,12 +296,13 @@ class Command2MC : Command2('/', true), Listener val parameter = parameters[i] val customParamType: Boolean // TODO: Arg type - val param: Any = subcmd.parameters.get(i - 1) + val param: String = subcmd.parameters.get(i - 1) val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete::class.java)) - .map(Function> { obj: CustomTabComplete -> obj.value() }) - val customTCmethod = customTCmethods.stream().filter { t: Triplet -> subpathAsOne.equals(t.value0, ignoreCase = true) } - .filter { t: Triplet -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.value1.param()) } - .findAny() + .map { obj -> obj.value } + val customTCmethod = + customTCmethods.stream().filter { t -> subpathAsOne.equals(t.first, ignoreCase = true) } + .filter { t -> param.replace("[\\[\\]<>]", "").equals(t.second.param, ignoreCase = true) } + .findAny() val argb: RequiredArgumentBuilder = RequiredArgumentBuilder.argument(param, type) .suggests(SuggestionProvider { context: CommandContext, builder: SuggestionsBuilder -> if (parameter.isVarArgs) { //Do it before the builder is used @@ -310,8 +313,8 @@ class Command2MC : Command2('/', true), Listener var ignoreCustomParamType = false if (customTCmethod.isPresent) { val tr = customTCmethod.get() - if (tr.value1.ignoreTypeCompletion()) ignoreCustomParamType = true - val method = tr.value2 + if (tr.second.ignoreTypeCompletion) ignoreCustomParamType = true + val method = tr.third val params = method.parameters val args = arrayOfNulls(params.size) var j = 0 @@ -319,7 +322,7 @@ class Command2MC : Command2('/', true), Listener while (j < args.size && k < subcmd.parameters.length) { val paramObj = params[j] if (CommandSender::class.java.isAssignableFrom(paramObj.type)) { - args[j] = commodore!!.getBukkitSender(context.getSource()) + args[j] = commodore.getBukkitSender(context.getSource()) j++ continue } @@ -336,12 +339,18 @@ class Command2MC : Command2('/', true), Listener k++ //Only increment if not CommandSender j++ } - if (args.size == 0 || args[args.size - 1] != null) { //Arguments filled entirely + if (args.isEmpty() || args[args.size - 1] != null) { //Arguments filled entirely try { - val suggestions = method.invoke(command2MC, *args) - if (suggestions is Iterable<*>) { - for (suggestion in suggestions) if (suggestion is String) builder.suggest(suggestion as String?) else throw ClassCastException("Bad return type! It should return an Iterable or a String[].") - } else if (suggestions is Array) for (suggestion in suggestions as Array) builder.suggest(suggestion) else throw ClassCastException("Bad return type! It should return a String[] or an Iterable.") + when (val suggestions = method.invoke(command2MC, *args)) { + is Iterable<*> -> { + for (suggestion in suggestions) if (suggestion is String) builder.suggest( + suggestion as String? + ) else throw ClassCastException("Bad return type! It should return an Iterable or a String[].") + } + + is Array<*> -> for (suggestion in suggestions) builder.suggest(suggestion) + else -> throw ClassCastException("Bad return type! It should return a String[] or an Iterable.") + } } catch (e: Exception) { val msg = "Failed to run tabcomplete method " + method.name + " for command " + command2MC.javaClass.simpleName if (command2MC.component == null) TBMCCoreAPI.SendException(msg, e, command2MC.plugin) else TBMCCoreAPI.SendException(msg, e, command2MC.component) @@ -378,10 +387,12 @@ class Command2MC : Command2('/', true), Listener val pluginName = command2MC.plugin.name.lowercase(Locale.getDefault()) val prefixedcmd = LiteralArgumentBuilder.literal(pluginName + ":" + path.get(0)) .redirect(maincmd).build() - commodore!!.register(prefixedcmd) + commodore.register(prefixedcmd) for (alias in bukkitCommand.aliases) { - commodore!!.register(LiteralArgumentBuilder.literal(alias).redirect(maincmd).build()) - commodore!!.register(LiteralArgumentBuilder.literal("$pluginName:$alias").redirect(maincmd).build()) + commodore.register(LiteralArgumentBuilder.literal(alias).redirect(maincmd).build()) + commodore.register( + LiteralArgumentBuilder.literal("$pluginName:$alias").redirect(maincmd).build() + ) } } } @@ -389,7 +400,7 @@ class Command2MC : Command2('/', true), Listener companion object { private fun getParamConverter(cl: Class<*>, command2MC: ICommand2MC): ParamConverter<*>? { - val converter = ButtonPlugin.getCommand2MC().paramConverters[cl] + val converter = ButtonPlugin.command2MC.paramConverters[cl] if (converter == null) { val msg = "Could not find a suitable converter for type " + cl.simpleName val exception: Exception = NullPointerException("converter is null") diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt index a82fcf1..60849ee 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt @@ -14,8 +14,8 @@ class CoreCommandBuilder, TSD : NoOpSubcom return this } - override fun build(): CoreCommandNode { - val result = CoreCommandNode<_, TC, _>( + override fun build(): CoreCommandNode { + val result = CoreCommandNode<_, _>( literal, command, requirement, diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandNode.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandNode.kt index c8ebfbc..a945a1d 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandNode.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandNode.kt @@ -1,13 +1,14 @@ package buttondevteam.lib.chat import buttondevteam.lib.chat.commands.NoOpSubcommandData +import buttondevteam.lib.chat.commands.SubcommandData import com.mojang.brigadier.Command import com.mojang.brigadier.RedirectModifier import com.mojang.brigadier.tree.CommandNode import com.mojang.brigadier.tree.LiteralCommandNode import java.util.function.Predicate -class CoreCommandNode, TSD : NoOpSubcommandData>( +class CoreCommandNode( literal: String, command: Command, requirement: Predicate, @@ -15,4 +16,7 @@ class CoreCommandNode, TSD : NoOpSubcomman modifier: RedirectModifier, forks: Boolean, val data: TSD -) : LiteralCommandNode(literal, command, requirement, redirect, modifier, forks) \ No newline at end of file +) : LiteralCommandNode(literal, command, requirement, redirect, modifier, forks) + +typealias CoreExecutableNode = CoreCommandNode> +typealias CoreNoOpNode = CoreCommandNode diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.kt index f397292..e33b938 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.kt @@ -13,7 +13,7 @@ import java.util.function.Function * * @param TP The sender's type */ -abstract class ICommand2(manager: Command2<*, TP>) { +abstract class ICommand2(val manager: Command2<*, TP>) { /** * Default handler for commands, can be used to copy the args too. * @@ -21,7 +21,7 @@ abstract class ICommand2(manager: Command2<*, TP>) { * @return The success of the command */ @Suppress("UNUSED_PARAMETER") - fun def(sender: TP): Boolean { + open fun def(sender: TP): Boolean { return false } @@ -32,6 +32,7 @@ abstract class ICommand2(manager: Command2<*, TP>) { * @param message The message to send to the sender * @return Always true so that the usage isn't shown */ + @Suppress("unused") protected fun respond(sender: TP, message: String): Boolean { sender.sendMessage(message) return true @@ -49,23 +50,15 @@ abstract class ICommand2(manager: Command2<*, TP>) { return if (ann.helpText.isNotEmpty() || cc == null) ann.helpText else cc.helpText //If cc is null then it's empty array } - private val path: String - val manager: Command2<*, TP> - open val commandPath: String - /** - * The command's path, or name if top-level command.

- * For example:

- * "u admin updateplugin" or "u" for the top level one

- * The path must be lowercase!

- * - * @return The command path, *which is the command class name by default* (removing any "command" from it) - Change via the [CommandClass] annotation - */ - get() = path - - init { - path = getcmdpath() - this.manager = manager - } + /** + * The command's path, or name if top-level command.

+ * For example:

+ * "u admin updateplugin" or "u" for the top level one

+ * The path must be lowercase!

+ * + * @return The command path, *which is the command class name by default* (removing any "command" from it) - Change via the [CommandClass] annotation + */ + open val commandPath: String = getcmdpath() open val commandPaths: Array /** @@ -74,8 +67,7 @@ abstract class ICommand2(manager: Command2<*, TP>) { * * @return The full command paths that this command should be registered under in addition to the default one. */ - get() =// TODO: Deal with this (used for channel IDs) - EMPTY_PATHS + get() = EMPTY_PATHS // TODO: Deal with this (used for channel IDs) private fun getcmdpath(): String { if (!javaClass.isAnnotationPresent(CommandClass::class.java)) throw RuntimeException( diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2MC.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2MC.kt index fe29029..61b3494 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2MC.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2MC.kt @@ -7,14 +7,14 @@ import buttondevteam.lib.architecture.Component abstract class ICommand2MC : ICommand2(command2MC) { private var _plugin: ButtonPlugin? = null var plugin: ButtonPlugin - get() = _plugin ?: throw IllegalStateException("The command is not registered to a plugin!") + get() = _plugin ?: throw IllegalStateException("The command is not registered to a Button plugin!") private set(value) { - if (_plugin != null) throw IllegalStateException("The command is already assigned to a plugin!") + if (_plugin != null) throw IllegalStateException("The command is already assigned to a Button plugin!") _plugin = value } private var _component: Component<*>? = null - var component: Component<*> - get() = _component ?: throw IllegalStateException("The command is not registered to a component!") + var component: Component<*>? + get() = _component private set(value) { if (_component != null) throw IllegalStateException("The command is already assigned to a component!") _component = value diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandUtils.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandUtils.kt index 1db8c68..97ef84b 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandUtils.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandUtils.kt @@ -2,8 +2,8 @@ package buttondevteam.lib.chat.commands import buttondevteam.lib.chat.Command2Sender import buttondevteam.lib.chat.CoreCommandNode +import buttondevteam.lib.chat.CoreExecutableNode import buttondevteam.lib.chat.ICommand2 -import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.tree.CommandNode import java.util.* @@ -24,20 +24,15 @@ object CommandUtils { * Casts the node to whatever you say. Use responsibly. */ @Suppress("UNCHECKED_CAST") - fun , TSD : NoOpSubcommandData> CommandNode.core(): CoreCommandNode { - return this as CoreCommandNode + fun CommandNode.core(): CoreCommandNode { + return this as CoreCommandNode } /** * Returns the node as an executable core command node or returns null if it's a no-op node. */ - fun > CommandNode.coreExecutable(): CoreCommandNode>? { - val ret = core() + fun > CommandNode.coreExecutable(): CoreExecutableNode? { + val ret = core() return if (ret.data is SubcommandData<*, *>) ret.core() else null } - - val CommandContext.subcommandPath - get(): String { - TODO("Return command path") - } } \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt index 9294225..7f220d8 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt @@ -47,7 +47,7 @@ class SubcommandData, TP : Command2Sender>( */ val annotations: Array, /** - * The full command path of this subcommand. + * The space-separated full command path of this subcommand. */ val fullPath: String ) : NoOpSubcommandData(helpTextGetter) {