From 5e1f378ec74fe953d9d2f9b7b6a5452f0c5df8b2 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 21 Apr 2023 04:05:04 +0200 Subject: [PATCH] It compiles! Finished MC tab completion - Custom tab complete methods are now case-sensitive - Custom tab complete methods are also not supported for now --- Chroma-Core/pom.xml | 2 +- .../buttondevteam/lib/TBMCChatEventBase.kt | 1 + .../java/buttondevteam/lib/chat/Command2.kt | 29 +-- .../java/buttondevteam/lib/chat/Command2MC.kt | 223 ++++++------------ .../lib/chat/CoreArgumentBuilder.kt | 9 +- .../lib/chat/CoreArgumentCommandNode.kt | 49 ++-- .../lib/chat/CoreCommandBuilder.kt | 15 +- .../lib/chat/commands/CommandArgument.kt | 4 +- .../lib/chat/commands/CommandUtils.kt | 19 +- 9 files changed, 137 insertions(+), 214 deletions(-) diff --git a/Chroma-Core/pom.xml b/Chroma-Core/pom.xml index 104f341..3769c08 100755 --- a/Chroma-Core/pom.xml +++ b/Chroma-Core/pom.xml @@ -62,7 +62,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.4.1 package diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatEventBase.kt b/Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatEventBase.kt index 09fffd3..aeaabc9 100755 --- a/Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatEventBase.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatEventBase.kt @@ -17,6 +17,7 @@ abstract class TBMCChatEventBase( */ val groupID: String, ) : Event(true), Cancellable { + @JvmField var isCancelled: Boolean = false /** 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 a2f6823..12d6e8d 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt @@ -139,18 +139,9 @@ abstract class Command2, TP : Command2Sender>( 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) { + 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) } } @@ -202,7 +193,7 @@ 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, CoreCommandNode?, String> { + private fun registerNodeFromPath(path: String): Triple, CoreCommandNode, String> { val split = path.split(" ") var parent: CommandNode = dispatcher.root var mainCommand: CoreCommandNode? = null @@ -214,7 +205,7 @@ abstract class Command2, TP : Command2Sender>( if (i == 0) mainCommand = parent as CoreCommandNode // Has to be our own literal node, if not, well, error } - return Triple(parent, mainCommand, split.last()) + return Triple(parent, mainCommand!!, split.last()) } private fun getSubcommandList(): (Any) -> Array { @@ -425,17 +416,15 @@ abstract class Command2, TP : Command2Sender>( fun getSubcommands( mainCommand: LiteralCommandNode, deep: Boolean = true - ): List>> { - return getSubcommands(mainCommand, deep, mainCommand.core()) + ): List> { + return getSubcommands(deep, mainCommand.core()) } private fun getSubcommands( - mainCommand: LiteralCommandNode, deep: Boolean = true, - root: CoreCommandNode - ): List>> { - + root: CoreNoOpNode + ): List> { return root.children.mapNotNull { it.coreExecutable() } + - if (deep) root.children.flatMap { getSubcommands(mainCommand, deep, it.core()) } else emptyList() + if (deep) root.children.flatMap { getSubcommands(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 e77312a..499d96a 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt @@ -5,19 +5,15 @@ 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.coreArgument import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable import buttondevteam.lib.chat.commands.MCCommandSettings import buttondevteam.lib.chat.commands.SubcommandData import buttondevteam.lib.player.ChromaGamerBase import com.mojang.brigadier.arguments.StringArgumentType -import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.builder.LiteralArgumentBuilder.literal import com.mojang.brigadier.builder.RequiredArgumentBuilder -import com.mojang.brigadier.context.CommandContext -import com.mojang.brigadier.suggestion.Suggestion -import com.mojang.brigadier.suggestion.SuggestionProvider -import com.mojang.brigadier.suggestion.Suggestions -import com.mojang.brigadier.suggestion.SuggestionsBuilder -import com.mojang.brigadier.tree.ArgumentCommandNode +import com.mojang.brigadier.builder.RequiredArgumentBuilder.argument import com.mojang.brigadier.tree.CommandNode import com.mojang.brigadier.tree.LiteralCommandNode import me.lucko.commodore.Commodore @@ -31,9 +27,7 @@ import org.bukkit.entity.Player import org.bukkit.event.Listener import org.bukkit.permissions.Permission import org.bukkit.permissions.PermissionDefault -import java.lang.reflect.Parameter import java.util.* -import java.util.function.BiConsumer import java.util.function.Function import java.util.function.Supplier @@ -179,7 +173,7 @@ class Command2MC : Command2('/', true), Listener bukkitCommand = oldcmd if (bukkitCommand is PluginCommand) bukkitCommand.setExecutor(this::executeCommand) } - if (CommodoreProvider.isSupported()) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand) + TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand) bukkitCommand } catch (e: Exception) { if (command.component == null) @@ -220,12 +214,7 @@ class Command2MC : Command2('/', true), Listener } @Throws(IllegalArgumentException::class) - override fun tabComplete( - sender: CommandSender, - alias: String, - args: Array?, - location: Location? - ): MutableList { + override fun tabComplete(sender: CommandSender, alias: String, args: Array?, location: Location?): MutableList { return mutableListOf() } } @@ -234,151 +223,75 @@ class Command2MC : Command2('/', true), Listener 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() + literal("un") // TODO: This is a test + .redirect(argument("unsomething", StringArgumentType.word()) + .suggests { _, builder -> builder.suggest("untest").buildFuture() }.build() ) ) commodore } - 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].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()) + fun registerTabcomplete(command2MC: ICommand2MC, commandNode: CoreCommandNode, bukkitCommand: Command) { + if (!CommodoreProvider.isSupported()) { + throw UnsupportedOperationException("Commodore is not supported! Please use 1.14 or higher. Server version: ${Bukkit.getVersion()}") + } + // TODO: Allow extending annotation processing for methods and parameters + val customTabCompleteMethods = command2MC.javaClass.declaredMethods + .flatMap { method -> + method.getAnnotation(CustomTabCompleteMethod::class.java)?.let { ctcmAnn -> + (ctcmAnn.subcommand.takeIf { it.isNotEmpty() } + ?: arrayOf(CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' })) + .map { name -> Triple(name, ctcmAnn, method) } + } ?: emptyList() + } + val mcNode = CommandUtils.mapSubcommands(commandNode) { node -> + val builder = node.createBuilder() + val argNode = node.coreArgument() ?: return@mapSubcommands builder + val subpath = "" // TODO: This needs the same processing as the command path to have the same flexibility + val argData = argNode.commandData.arguments[argNode.name] ?: return@mapSubcommands builder + val customTCTexts = argData.annotations.filterIsInstance().flatMap { it.value.asList() } + val customTCmethod = customTabCompleteMethods.firstOrNull { (name, ann, _) -> + name == subpath && argData.name.replace("[\\[\\]<>]".toRegex(), "") == ann.param + } + (builder as RequiredArgumentBuilder).suggests { context, b -> + val sbuilder = if (argData.greedy) { //Do it before the builder is used + val nextTokenStart = context.input.lastIndexOf(' ') + 1 + b.createOffset(nextTokenStart) + } else b + // Suggest custom tab complete texts + for (ctc in customTCTexts) { + sbuilder.suggest(ctc) + } + val ignoreCustomParamType = false // TODO: This should be set by the @CustomTabCompleteMethod annotation + // TODO: Custom tab complete method handling + if (!ignoreCustomParamType) { + val converter = getParamConverter(argData.type, command2MC) + if (converter != null) { + val suggestions = converter.allSupplier.get() + for (suggestion in suggestions) sbuilder.suggest(suggestion) + } + } + if (argData.type === Boolean::class.javaPrimitiveType || argData.type === Boolean::class.java) + sbuilder.suggest("true").suggest("false") + val loweredInput = sbuilder.remaining.lowercase(Locale.getDefault()) + // The list is automatically ordered, so we need to put the at the end after that + // We're also removing all suggestions that don't start with the input + sbuilder.suggest(argData.name).buildFuture().whenComplete { ss, _ -> + ss.list.add(ss.list.removeAt(0)) + }.whenComplete { ss, _ -> + ss.list.removeIf { s -> + s.text.lowercase().let { !it.startsWith("<") && !it.startsWith("[") && !it.startsWith(loweredInput) } + } } } - val parameters: Array = subcmd.method.getParameters() - for (i in 1 until parameters.size) { //Skip sender - val parameter = parameters[i] - val customParamType: Boolean - // TODO: Arg type - val param: String = subcmd.parameters.get(i - 1) - val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete::class.java)) - .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 - val nextTokenStart = context.getInput().lastIndexOf(' ') + 1 - builder = builder.createOffset(nextTokenStart) - } - if (customTC.isPresent) for (ctc in customTC.get()) builder.suggest(ctc) - var ignoreCustomParamType = false - if (customTCmethod.isPresent) { - val tr = customTCmethod.get() - if (tr.second.ignoreTypeCompletion) ignoreCustomParamType = true - val method = tr.third - val params = method.parameters - val args = arrayOfNulls(params.size) - var j = 0 - var k = 0 - 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()) - j++ - continue - } - val paramValueString = context.getArgument(subcmd.parameters.get(k), String::class.java) - if (paramObj.type == String::class.java) { - args[j] = paramValueString - j++ - continue - } - //Break if converter is not found or for example, the player provided an invalid plugin name - val converter = getParamConverter(params[j].type, command2MC) ?: break - val paramValue = converter.converter.apply(paramValueString) ?: break - args[j] = paramValue - k++ //Only increment if not CommandSender - j++ - } - if (args.isEmpty() || args[args.size - 1] != null) { //Arguments filled entirely - try { - 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) - } - } - } - if (!ignoreCustomParamType && customParamType) { - val converter = getParamConverter(ptype, command2MC) - if (converter != null) { - val suggestions = converter.allSupplier.get() - for (suggestion in suggestions) builder.suggest(suggestion) - } - } - if (ptype === Boolean::class.javaPrimitiveType || ptype === Boolean::class.java) builder.suggest("true").suggest("false") - val loweredInput = builder.remaining.lowercase(Locale.getDefault()) - builder.suggest(param).buildFuture().whenComplete(BiConsumer { s: Suggestions, e: Throwable? -> //The list is automatically ordered - s.list.add(s.list.removeAt(0)) - }) //So we need to put the at the end after that - .whenComplete(BiConsumer { ss: Suggestions, e: Throwable? -> - ss.list.removeIf { s: Suggestion -> - val text = s.text - !text.startsWith("<") && !text.startsWith("[") && !text.lowercase(Locale.getDefault()).startsWith(loweredInput) - } - }) - }) - val arg: ArgumentCommandNode = argb.build() - scmd.addChild(arg) - scmd = arg - } + builder } - if (shouldRegister.get()) { - commodore.register(maincmd) - //MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft("")) - val pluginName = command2MC.plugin.name.lowercase(Locale.getDefault()) - val prefixedcmd = LiteralArgumentBuilder.literal(pluginName + ":" + path.get(0)) - .redirect(maincmd).build() - 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(mcNode as LiteralCommandNode<*>) + commodore.register(literal("${command2MC.plugin.name.lowercase()}:${mcNode.name}").redirect(mcNode)) + for (alias in bukkitCommand.aliases) { + commodore.register(literal(alias).redirect(mcNode)) + commodore.register(literal("${command2MC.plugin.name.lowercase()}:${alias}").redirect(mcNode)) } } } @@ -395,4 +308,6 @@ class Command2MC : Command2('/', true), Listener return converter } } -} \ No newline at end of file +} + +private typealias CNode = CommandNode diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreArgumentBuilder.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreArgumentBuilder.kt index a95289d..e3c3f1e 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreArgumentBuilder.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreArgumentBuilder.kt @@ -1,15 +1,17 @@ package buttondevteam.lib.chat +import buttondevteam.lib.chat.commands.SubcommandData import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.builder.ArgumentBuilder import com.mojang.brigadier.suggestion.SuggestionProvider -class CoreArgumentBuilder( +class CoreArgumentBuilder( private val name: String, private val type: ArgumentType, private val optional: Boolean ) : ArgumentBuilder>() { private var suggestionsProvider: SuggestionProvider? = null + internal lateinit var data: SubcommandData<*, S> fun suggests(provider: SuggestionProvider): CoreArgumentBuilder { suggestionsProvider = provider return this @@ -29,12 +31,13 @@ class CoreArgumentBuilder( redirectModifier, isFork, suggestionsProvider, - optional + optional, + data ) } companion object { - fun argument(name: String, type: ArgumentType, optional: Boolean): CoreArgumentBuilder { + fun argument(name: String, type: ArgumentType, optional: Boolean): CoreArgumentBuilder { return CoreArgumentBuilder(name, type, optional) } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreArgumentCommandNode.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreArgumentCommandNode.kt index 4f014e8..1c4f207 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreArgumentCommandNode.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreArgumentCommandNode.kt @@ -1,30 +1,25 @@ -package buttondevteam.lib.chat; +package buttondevteam.lib.chat -import com.mojang.brigadier.Command; -import com.mojang.brigadier.RedirectModifier; -import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.tree.ArgumentCommandNode; -import com.mojang.brigadier.tree.CommandNode; +import buttondevteam.lib.chat.commands.SubcommandData +import com.mojang.brigadier.Command +import com.mojang.brigadier.RedirectModifier +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.builder.RequiredArgumentBuilder +import com.mojang.brigadier.suggestion.SuggestionProvider +import com.mojang.brigadier.tree.ArgumentCommandNode +import com.mojang.brigadier.tree.CommandNode +import java.util.function.Predicate -import java.util.function.Predicate; +class CoreArgumentCommandNode( + name: String?, type: ArgumentType?, command: Command?, requirement: Predicate?, redirect: CommandNode?, modifier: RedirectModifier?, forks: Boolean, customSuggestions: SuggestionProvider?, + val optional: Boolean, val commandData: SubcommandData<*, S> +) : + ArgumentCommandNode(name, type, command, requirement, redirect, modifier, forks, customSuggestions) { + override fun getUsageText(): String { + return if (optional) "[$name]" else "<$name>" + } -public class CoreArgumentCommandNode extends ArgumentCommandNode { - private final boolean optional; - - public CoreArgumentCommandNode(String name, ArgumentType type, Command command, Predicate requirement, CommandNode redirect, RedirectModifier modifier, boolean forks, SuggestionProvider customSuggestions, boolean optional) { - super(name, type, command, requirement, redirect, modifier, forks, customSuggestions); - this.optional = optional; - } - - @Override - public String getUsageText() { - return optional ? "[" + getName() + "]" : "<" + getName() + ">"; - } - - @Override - public RequiredArgumentBuilder createBuilder() { - return super.createBuilder(); - } -} + override fun createBuilder(): RequiredArgumentBuilder { + return super.createBuilder() + } +} \ No newline at end of file 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 60849ee..fd9cf9c 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt @@ -3,9 +3,10 @@ package buttondevteam.lib.chat import buttondevteam.lib.chat.commands.CommandArgument import buttondevteam.lib.chat.commands.NoOpSubcommandData import buttondevteam.lib.chat.commands.SubcommandData +import com.mojang.brigadier.builder.ArgumentBuilder import com.mojang.brigadier.builder.LiteralArgumentBuilder -class CoreCommandBuilder, TSD : NoOpSubcommandData> private constructor( +class CoreCommandBuilder, TSD : NoOpSubcommandData> private constructor( literal: String, val data: TSD ) : LiteralArgumentBuilder(literal) { @@ -30,6 +31,14 @@ class CoreCommandBuilder, TSD : NoOpSubcom return result } + override fun then(argument: ArgumentBuilder): LiteralArgumentBuilder { + if (argument is CoreArgumentBuilder<*, *> && data is SubcommandData<*, *>) { + @Suppress("UNCHECKED_CAST") + (argument as CoreArgumentBuilder).data = data as SubcommandData<*, S> + } + return super.then(argument) + } + companion object { /** * Start building an executable command node. @@ -44,7 +53,7 @@ class CoreCommandBuilder, TSD : NoOpSubcom * @param annotations All annotations implemented by the method that executes the command * @param fullPath The full command path of this subcommand. */ - fun > literal( + fun > literal( name: String, senderType: Class<*>, arguments: Map, @@ -67,7 +76,7 @@ class CoreCommandBuilder, TSD : NoOpSubcom * @param name The subcommand name as written by the user * @param helpTextGetter Custom help text that can depend on the context. The function receives the sender as the command itself receives it. */ - fun > literalNoOp( + fun > literalNoOp( name: String, helpTextGetter: (Any) -> Array, ): CoreCommandBuilder { diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.kt index bd8b7fa..b34d948 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.kt @@ -4,11 +4,11 @@ package buttondevteam.lib.chat.commands * A command argument's information to be used to construct the command. */ class CommandArgument( - val name: String, + val name: String, // TODO: Remove <> from name and add it where appropriate val type: Class<*>, val greedy: Boolean, val limits: Pair, val optional: Boolean, val description: String, - val annotations: Array // TODO: Annotations for parameters as well + val annotations: Array ) 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 97ef84b..21ba132 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 @@ -1,9 +1,7 @@ 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 buttondevteam.lib.chat.* +import com.mojang.brigadier.builder.ArgumentBuilder import com.mojang.brigadier.tree.CommandNode import java.util.* @@ -20,6 +18,15 @@ object CommandUtils { .lowercase(Locale.getDefault()) } + /** + * Performs the given action on the given node and all of its nodes recursively and creates new nodes. + */ + fun mapSubcommands(node: CommandNode, action: (CommandNode) -> ArgumentBuilder): CommandNode { + val newNode = action(node) + node.children.map { mapSubcommands(it, action) }.forEach(newNode::then) + return newNode.build() + } + /** * Casts the node to whatever you say. Use responsibly. */ @@ -35,4 +42,8 @@ object CommandUtils { val ret = core() return if (ret.data is SubcommandData<*, *>) ret.core() else null } + + fun CommandNode.coreArgument(): CoreArgumentCommandNode? { + return if (this is CoreArgumentCommandNode<*, *>) this as CoreArgumentCommandNode else null + } } \ No newline at end of file