It compiles! Finished MC tab completion

- Custom tab complete methods are now case-sensitive
- Custom tab complete methods are also not supported for now
This commit is contained in:
Norbi Peti 2023-04-21 04:05:04 +02:00
parent ee7e531dc0
commit 5e1f378ec7
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
9 changed files with 137 additions and 214 deletions

View file

@ -62,7 +62,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version> <version>3.4.1</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>

View file

@ -17,6 +17,7 @@ abstract class TBMCChatEventBase(
*/ */
val groupID: String, val groupID: String,
) : Event(true), Cancellable { ) : Event(true), Cancellable {
@JvmField
var isCancelled: Boolean = false var isCancelled: Boolean = false
/** /**

View file

@ -139,18 +139,9 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
val ann = meth.getAnnotation(Subcommand::class.java) ?: continue val ann = meth.getAnnotation(Subcommand::class.java) ?: continue
val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ') val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ')
val (lastNode, mainNode, remainingPath) = registerNodeFromPath(fullPath) val (lastNode, mainNode, remainingPath) = registerNodeFromPath(fullPath)
lastNode.addChild( lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command), fullPath))
getExecutableNode(
meth,
command,
ann,
remainingPath,
CommandArgumentHelpManager(command),
fullPath
)
)
if (mainCommandNode == null) mainCommandNode = mainNode 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) 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<TC : ICommand2<TP>, TP : Command2Sender>(
* @return The last no-op node that can be used to register the executable node, * @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) * the main command node and the last part of the command path (that isn't registered yet)
*/ */
private fun registerNodeFromPath(path: String): Triple<CommandNode<TP>, CoreCommandNode<TP, *>?, String> { private fun registerNodeFromPath(path: String): Triple<CommandNode<TP>, CoreCommandNode<TP, *>, String> {
val split = path.split(" ") val split = path.split(" ")
var parent: CommandNode<TP> = dispatcher.root var parent: CommandNode<TP> = dispatcher.root
var mainCommand: CoreCommandNode<TP, *>? = null var mainCommand: CoreCommandNode<TP, *>? = null
@ -214,7 +205,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
if (i == 0) mainCommand = if (i == 0) mainCommand =
parent as CoreCommandNode<TP, *> // Has to be our own literal node, if not, well, error parent as CoreCommandNode<TP, *> // 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<String> { private fun getSubcommandList(): (Any) -> Array<String> {
@ -425,17 +416,15 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
fun getSubcommands( fun getSubcommands(
mainCommand: LiteralCommandNode<TP>, mainCommand: LiteralCommandNode<TP>,
deep: Boolean = true deep: Boolean = true
): List<CoreCommandNode<TP, SubcommandData<TC, TP>>> { ): List<CoreExecutableNode<TP, TC>> {
return getSubcommands(mainCommand, deep, mainCommand.core()) return getSubcommands(deep, mainCommand.core())
} }
private fun getSubcommands( private fun getSubcommands(
mainCommand: LiteralCommandNode<TP>,
deep: Boolean = true, deep: Boolean = true,
root: CoreCommandNode<TP, NoOpSubcommandData> root: CoreNoOpNode<TP>
): List<CoreCommandNode<TP, SubcommandData<TC, TP>>> { ): List<CoreExecutableNode<TP, TC>> {
return root.children.mapNotNull { it.coreExecutable<TP, TC>() } + return root.children.mapNotNull { it.coreExecutable<TP, TC>() } +
if (deep) root.children.flatMap { getSubcommands(mainCommand, deep, it.core()) } else emptyList() if (deep) root.children.flatMap { getSubcommands(deep, it.core()) } else emptyList()
} }
} }

View file

@ -5,19 +5,15 @@ import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ButtonPlugin import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.architecture.Component import buttondevteam.lib.architecture.Component
import buttondevteam.lib.chat.commands.CommandUtils 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.CommandUtils.coreExecutable
import buttondevteam.lib.chat.commands.MCCommandSettings import buttondevteam.lib.chat.commands.MCCommandSettings
import buttondevteam.lib.chat.commands.SubcommandData import buttondevteam.lib.chat.commands.SubcommandData
import buttondevteam.lib.player.ChromaGamerBase import buttondevteam.lib.player.ChromaGamerBase
import com.mojang.brigadier.arguments.StringArgumentType 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.builder.RequiredArgumentBuilder
import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.builder.RequiredArgumentBuilder.argument
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.tree.CommandNode import com.mojang.brigadier.tree.CommandNode
import com.mojang.brigadier.tree.LiteralCommandNode import com.mojang.brigadier.tree.LiteralCommandNode
import me.lucko.commodore.Commodore import me.lucko.commodore.Commodore
@ -31,9 +27,7 @@ import org.bukkit.entity.Player
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.permissions.Permission import org.bukkit.permissions.Permission
import org.bukkit.permissions.PermissionDefault import org.bukkit.permissions.PermissionDefault
import java.lang.reflect.Parameter
import java.util.* import java.util.*
import java.util.function.BiConsumer
import java.util.function.Function import java.util.function.Function
import java.util.function.Supplier import java.util.function.Supplier
@ -179,7 +173,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
bukkitCommand = oldcmd bukkitCommand = oldcmd
if (bukkitCommand is PluginCommand) bukkitCommand.setExecutor(this::executeCommand) if (bukkitCommand is PluginCommand) bukkitCommand.setExecutor(this::executeCommand)
} }
if (CommodoreProvider.isSupported()) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand)
bukkitCommand bukkitCommand
} catch (e: Exception) { } catch (e: Exception) {
if (command.component == null) if (command.component == null)
@ -220,12 +214,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
} }
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
override fun tabComplete( override fun tabComplete(sender: CommandSender, alias: String, args: Array<out String>?, location: Location?): MutableList<String> {
sender: CommandSender,
alias: String,
args: Array<out String>?,
location: Location?
): MutableList<String> {
return mutableListOf() return mutableListOf()
} }
} }
@ -234,151 +223,75 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
private val commodore: Commodore by lazy { private val commodore: Commodore by lazy {
val commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier val commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier
commodore.register( commodore.register(
LiteralArgumentBuilder.literal<Any?>("un") // TODO: This is a test literal<Any?>("un") // TODO: This is a test
.redirect( .redirect(argument<Any?, String>("unsomething", StringArgumentType.word())
RequiredArgumentBuilder.argument<Any?, String>( .suggests { _, builder -> builder.suggest("untest").buildFuture() }.build()
"unsomething",
StringArgumentType.word()
).suggests { _, builder ->
builder.suggest("untest").buildFuture()
}.build()
) )
) )
commodore commodore
} }
fun registerTabcomplete( fun registerTabcomplete(command2MC: ICommand2MC, commandNode: CoreCommandNode<Command2MCSender, *>, bukkitCommand: Command) {
command2MC: ICommand2MC, if (!CommodoreProvider.isSupported()) {
commandNode: LiteralCommandNode<Command2MCSender>, throw UnsupportedOperationException("Commodore is not supported! Please use 1.14 or higher. Server version: ${Bukkit.getVersion()}")
bukkitCommand: Command }
) { // TODO: Allow extending annotation processing for methods and parameters
commodore.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary val customTabCompleteMethods = command2MC.javaClass.declaredMethods
val customTCmethods = .flatMap { method ->
Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments method.getAnnotation(CustomTabCompleteMethod::class.java)?.let { ctcmAnn ->
.flatMap { method -> (ctcmAnn.subcommand.takeIf { it.isNotEmpty() }
Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream() ?: arrayOf(CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' }))
.flatMap { ctcmAnn -> .map { name -> Triple(name, ctcmAnn, method) }
val paths = Optional.of(ctcmAnn.subcommand).filter { s -> s.isNotEmpty() } } ?: emptyList()
.orElseGet { }
arrayOf( val mcNode = CommandUtils.mapSubcommands(commandNode) { node ->
CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' }) val builder = node.createBuilder()
} val argNode = node.coreArgument() ?: return@mapSubcommands builder
Arrays.stream(paths).map { name: String? -> Triple(name, ctcmAnn, method) } 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
}.toList() val customTCTexts = argData.annotations.filterIsInstance<CustomTabComplete>().flatMap { it.value.asList() }
for (subcmd in subcmds) { val customTCmethod = customTabCompleteMethods.firstOrNull { (name, ann, _) ->
val subpathAsOne = CommandUtils.getCommandPath(subcmd.method.getName(), ' ').trim { it <= ' ' } name == subpath && argData.name.replace("[\\[\\]<>]".toRegex(), "") == ann.param
val subpath = subpathAsOne.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() }
var scmd: CommandNode<Any> = cmd (builder as RequiredArgumentBuilder<Command2MCSender, *>).suggests { context, b ->
if (subpath[0].isNotEmpty()) { //If the method is def, it will contain one empty string val sbuilder = if (argData.greedy) { //Do it before the builder is used
for (s in subpath) { val nextTokenStart = context.input.lastIndexOf(' ') + 1
scmd = b.createOffset(nextTokenStart)
appendSubcommand(s, scmd, subcmd) //Add method name part of the path (could_be_multiple()) } 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 <param> 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<Parameter> = subcmd.method.getParameters() builder
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<S, T> = RequiredArgumentBuilder.argument(param, type)
.suggests(SuggestionProvider<S?> { context: CommandContext<S?>, 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<Any>(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<String> 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<String>.")
}
} 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<Suggestions, Throwable> { s: Suggestions, e: Throwable? -> //The list is automatically ordered
s.list.add(s.list.removeAt(0))
}) //So we need to put the <param> at the end after that
.whenComplete(BiConsumer<Suggestions, Throwable> { 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<S, T> = argb.build()
scmd.addChild(arg)
scmd = arg
}
} }
if (shouldRegister.get()) {
commodore.register(maincmd) commodore.register(mcNode as LiteralCommandNode<*>)
//MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft("")) commodore.register(literal<Command2MCSender>("${command2MC.plugin.name.lowercase()}:${mcNode.name}").redirect(mcNode))
val pluginName = command2MC.plugin.name.lowercase(Locale.getDefault()) for (alias in bukkitCommand.aliases) {
val prefixedcmd = LiteralArgumentBuilder.literal<Any>(pluginName + ":" + path.get(0)) commodore.register(literal<Command2MCSender>(alias).redirect(mcNode))
.redirect(maincmd).build() commodore.register(literal<Command2MCSender>("${command2MC.plugin.name.lowercase()}:${alias}").redirect(mcNode))
commodore.register(prefixedcmd)
for (alias in bukkitCommand.aliases) {
commodore.register(LiteralArgumentBuilder.literal<Any>(alias).redirect(maincmd).build())
commodore.register(
LiteralArgumentBuilder.literal<Any>("$pluginName:$alias").redirect(maincmd).build()
)
}
} }
} }
} }
@ -395,4 +308,6 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
return converter return converter
} }
} }
} }
private typealias CNode = CommandNode<Command2MCSender>

View file

@ -1,15 +1,17 @@
package buttondevteam.lib.chat package buttondevteam.lib.chat
import buttondevteam.lib.chat.commands.SubcommandData
import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.arguments.ArgumentType
import com.mojang.brigadier.builder.ArgumentBuilder import com.mojang.brigadier.builder.ArgumentBuilder
import com.mojang.brigadier.suggestion.SuggestionProvider import com.mojang.brigadier.suggestion.SuggestionProvider
class CoreArgumentBuilder<S, T>( class CoreArgumentBuilder<S : Command2Sender, T>(
private val name: String, private val name: String,
private val type: ArgumentType<T>, private val type: ArgumentType<T>,
private val optional: Boolean private val optional: Boolean
) : ArgumentBuilder<S, CoreArgumentBuilder<S, T>>() { ) : ArgumentBuilder<S, CoreArgumentBuilder<S, T>>() {
private var suggestionsProvider: SuggestionProvider<S>? = null private var suggestionsProvider: SuggestionProvider<S>? = null
internal lateinit var data: SubcommandData<*, S>
fun suggests(provider: SuggestionProvider<S>): CoreArgumentBuilder<S, T> { fun suggests(provider: SuggestionProvider<S>): CoreArgumentBuilder<S, T> {
suggestionsProvider = provider suggestionsProvider = provider
return this return this
@ -29,12 +31,13 @@ class CoreArgumentBuilder<S, T>(
redirectModifier, redirectModifier,
isFork, isFork,
suggestionsProvider, suggestionsProvider,
optional optional,
data
) )
} }
companion object { companion object {
fun <S, T> argument(name: String, type: ArgumentType<T>, optional: Boolean): CoreArgumentBuilder<S, T> { fun <S : Command2Sender, T> argument(name: String, type: ArgumentType<T>, optional: Boolean): CoreArgumentBuilder<S, T> {
return CoreArgumentBuilder(name, type, optional) return CoreArgumentBuilder(name, type, optional)
} }
} }

View file

@ -1,30 +1,25 @@
package buttondevteam.lib.chat; package buttondevteam.lib.chat
import com.mojang.brigadier.Command; import buttondevteam.lib.chat.commands.SubcommandData
import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.Command
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.RedirectModifier
import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.arguments.ArgumentType
import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.builder.RequiredArgumentBuilder
import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.suggestion.SuggestionProvider
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.ArgumentCommandNode
import com.mojang.brigadier.tree.CommandNode
import java.util.function.Predicate
import java.util.function.Predicate; class CoreArgumentCommandNode<S : Command2Sender, T>(
name: String?, type: ArgumentType<T>?, command: Command<S>?, requirement: Predicate<S>?, redirect: CommandNode<S>?, modifier: RedirectModifier<S>?, forks: Boolean, customSuggestions: SuggestionProvider<S>?,
val optional: Boolean, val commandData: SubcommandData<*, S>
) :
ArgumentCommandNode<S, T>(name, type, command, requirement, redirect, modifier, forks, customSuggestions) {
override fun getUsageText(): String {
return if (optional) "[$name]" else "<$name>"
}
public class CoreArgumentCommandNode<S, T> extends ArgumentCommandNode<S, T> { override fun createBuilder(): RequiredArgumentBuilder<S, T> {
private final boolean optional; return super.createBuilder()
}
public CoreArgumentCommandNode(String name, ArgumentType<T> type, Command<S> command, Predicate<S> requirement, CommandNode<S> redirect, RedirectModifier<S> modifier, boolean forks, SuggestionProvider<S> 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<S, T> createBuilder() {
return super.createBuilder();
}
}

View file

@ -3,9 +3,10 @@ package buttondevteam.lib.chat
import buttondevteam.lib.chat.commands.CommandArgument import buttondevteam.lib.chat.commands.CommandArgument
import buttondevteam.lib.chat.commands.NoOpSubcommandData import buttondevteam.lib.chat.commands.NoOpSubcommandData
import buttondevteam.lib.chat.commands.SubcommandData import buttondevteam.lib.chat.commands.SubcommandData
import com.mojang.brigadier.builder.ArgumentBuilder
import com.mojang.brigadier.builder.LiteralArgumentBuilder import com.mojang.brigadier.builder.LiteralArgumentBuilder
class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcommandData> private constructor( class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<S>, TSD : NoOpSubcommandData> private constructor(
literal: String, literal: String,
val data: TSD val data: TSD
) : LiteralArgumentBuilder<S>(literal) { ) : LiteralArgumentBuilder<S>(literal) {
@ -30,6 +31,14 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
return result return result
} }
override fun then(argument: ArgumentBuilder<S, *>): LiteralArgumentBuilder<S> {
if (argument is CoreArgumentBuilder<*, *> && data is SubcommandData<*, *>) {
@Suppress("UNCHECKED_CAST")
(argument as CoreArgumentBuilder<S, *>).data = data as SubcommandData<*, S>
}
return super.then(argument)
}
companion object { companion object {
/** /**
* Start building an executable command node. * Start building an executable command node.
@ -44,7 +53,7 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
* @param annotations All annotations implemented by the method that executes the command * @param annotations All annotations implemented by the method that executes the command
* @param fullPath The full command path of this subcommand. * @param fullPath The full command path of this subcommand.
*/ */
fun <S : Command2Sender, TC : ICommand2<*>> literal( fun <S : Command2Sender, TC : ICommand2<S>> literal(
name: String, name: String,
senderType: Class<*>, senderType: Class<*>,
arguments: Map<String, CommandArgument>, arguments: Map<String, CommandArgument>,
@ -67,7 +76,7 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
* @param name The subcommand name as written by the user * @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. * @param helpTextGetter Custom help text that can depend on the context. The function receives the sender as the command itself receives it.
*/ */
fun <S : Command2Sender, TC : ICommand2<*>> literalNoOp( fun <S : Command2Sender, TC : ICommand2<S>> literalNoOp(
name: String, name: String,
helpTextGetter: (Any) -> Array<String>, helpTextGetter: (Any) -> Array<String>,
): CoreCommandBuilder<S, TC, NoOpSubcommandData> { ): CoreCommandBuilder<S, TC, NoOpSubcommandData> {

View file

@ -4,11 +4,11 @@ package buttondevteam.lib.chat.commands
* A command argument's information to be used to construct the command. * A command argument's information to be used to construct the command.
*/ */
class CommandArgument( class CommandArgument(
val name: String, val name: String, // TODO: Remove <> from name and add it where appropriate
val type: Class<*>, val type: Class<*>,
val greedy: Boolean, val greedy: Boolean,
val limits: Pair<Double, Double>, val limits: Pair<Double, Double>,
val optional: Boolean, val optional: Boolean,
val description: String, val description: String,
val annotations: Array<Annotation> // TODO: Annotations for parameters as well val annotations: Array<Annotation>
) )

View file

@ -1,9 +1,7 @@
package buttondevteam.lib.chat.commands package buttondevteam.lib.chat.commands
import buttondevteam.lib.chat.Command2Sender import buttondevteam.lib.chat.*
import buttondevteam.lib.chat.CoreCommandNode import com.mojang.brigadier.builder.ArgumentBuilder
import buttondevteam.lib.chat.CoreExecutableNode
import buttondevteam.lib.chat.ICommand2
import com.mojang.brigadier.tree.CommandNode import com.mojang.brigadier.tree.CommandNode
import java.util.* import java.util.*
@ -20,6 +18,15 @@ object CommandUtils {
.lowercase(Locale.getDefault()) .lowercase(Locale.getDefault())
} }
/**
* Performs the given action on the given node and all of its nodes recursively and creates new nodes.
*/
fun <S : Command2Sender> mapSubcommands(node: CommandNode<S>, action: (CommandNode<S>) -> ArgumentBuilder<S, *>): CommandNode<S> {
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. * Casts the node to whatever you say. Use responsibly.
*/ */
@ -35,4 +42,8 @@ object CommandUtils {
val ret = core<TP, NoOpSubcommandData>() val ret = core<TP, NoOpSubcommandData>()
return if (ret.data is SubcommandData<*, *>) ret.core() else null return if (ret.data is SubcommandData<*, *>) ret.core() else null
} }
fun <TP : Command2Sender> CommandNode<TP>.coreArgument(): CoreArgumentCommandNode<TP, *>? {
return if (this is CoreArgumentCommandNode<*, *>) this as CoreArgumentCommandNode<TP, *> else null
}
} }