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:
parent
ee7e531dc0
commit
5e1f378ec7
9 changed files with 137 additions and 214 deletions
|
@ -62,7 +62,7 @@
|
|||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<version>3.4.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
|
|
|
@ -17,6 +17,7 @@ abstract class TBMCChatEventBase(
|
|||
*/
|
||||
val groupID: String,
|
||||
) : Event(true), Cancellable {
|
||||
@JvmField
|
||||
var isCancelled: Boolean = false
|
||||
|
||||
/**
|
||||
|
|
|
@ -139,18 +139,9 @@ abstract class Command2<TC : ICommand2<TP>, 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<TC : ICommand2<TP>, 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<CommandNode<TP>, CoreCommandNode<TP, *>?, String> {
|
||||
private fun registerNodeFromPath(path: String): Triple<CommandNode<TP>, CoreCommandNode<TP, *>, String> {
|
||||
val split = path.split(" ")
|
||||
var parent: CommandNode<TP> = dispatcher.root
|
||||
var mainCommand: CoreCommandNode<TP, *>? = null
|
||||
|
@ -214,7 +205,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
if (i == 0) mainCommand =
|
||||
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> {
|
||||
|
@ -425,17 +416,15 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
|||
fun getSubcommands(
|
||||
mainCommand: LiteralCommandNode<TP>,
|
||||
deep: Boolean = true
|
||||
): List<CoreCommandNode<TP, SubcommandData<TC, TP>>> {
|
||||
return getSubcommands(mainCommand, deep, mainCommand.core())
|
||||
): List<CoreExecutableNode<TP, TC>> {
|
||||
return getSubcommands(deep, mainCommand.core())
|
||||
}
|
||||
|
||||
private fun getSubcommands(
|
||||
mainCommand: LiteralCommandNode<TP>,
|
||||
deep: Boolean = true,
|
||||
root: CoreCommandNode<TP, NoOpSubcommandData>
|
||||
): List<CoreCommandNode<TP, SubcommandData<TC, TP>>> {
|
||||
|
||||
root: CoreNoOpNode<TP>
|
||||
): List<CoreExecutableNode<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()
|
||||
}
|
||||
}
|
|
@ -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<ICommand2MC, Command2MCSender>('/', 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<ICommand2MC, Command2MCSender>('/', true), Listener
|
|||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override fun tabComplete(
|
||||
sender: CommandSender,
|
||||
alias: String,
|
||||
args: Array<out String>?,
|
||||
location: Location?
|
||||
): MutableList<String> {
|
||||
override fun tabComplete(sender: CommandSender, alias: String, args: Array<out String>?, location: Location?): MutableList<String> {
|
||||
return mutableListOf()
|
||||
}
|
||||
}
|
||||
|
@ -234,151 +223,75 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', 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<Any?>("un") // TODO: This is a test
|
||||
.redirect(
|
||||
RequiredArgumentBuilder.argument<Any?, String>(
|
||||
"unsomething",
|
||||
StringArgumentType.word()
|
||||
).suggests { _, builder ->
|
||||
builder.suggest("untest").buildFuture()
|
||||
}.build()
|
||||
literal<Any?>("un") // TODO: This is a test
|
||||
.redirect(argument<Any?, String>("unsomething", StringArgumentType.word())
|
||||
.suggests { _, builder -> builder.suggest("untest").buildFuture() }.build()
|
||||
)
|
||||
)
|
||||
commodore
|
||||
}
|
||||
|
||||
fun registerTabcomplete(
|
||||
command2MC: ICommand2MC,
|
||||
commandNode: LiteralCommandNode<Command2MCSender>,
|
||||
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<Any> = 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<Command2MCSender, *>, 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<CustomTabComplete>().flatMap { it.value.asList() }
|
||||
val customTCmethod = customTabCompleteMethods.firstOrNull { (name, ann, _) ->
|
||||
name == subpath && argData.name.replace("[\\[\\]<>]".toRegex(), "") == ann.param
|
||||
}
|
||||
(builder as RequiredArgumentBuilder<Command2MCSender, *>).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 <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()
|
||||
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
|
||||
}
|
||||
builder
|
||||
}
|
||||
if (shouldRegister.get()) {
|
||||
commodore.register(maincmd)
|
||||
//MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft(""))
|
||||
val pluginName = command2MC.plugin.name.lowercase(Locale.getDefault())
|
||||
val prefixedcmd = LiteralArgumentBuilder.literal<Any>(pluginName + ":" + path.get(0))
|
||||
.redirect(maincmd).build()
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
commodore.register(mcNode as LiteralCommandNode<*>)
|
||||
commodore.register(literal<Command2MCSender>("${command2MC.plugin.name.lowercase()}:${mcNode.name}").redirect(mcNode))
|
||||
for (alias in bukkitCommand.aliases) {
|
||||
commodore.register(literal<Command2MCSender>(alias).redirect(mcNode))
|
||||
commodore.register(literal<Command2MCSender>("${command2MC.plugin.name.lowercase()}:${alias}").redirect(mcNode))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -395,4 +308,6 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
|||
return converter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private typealias CNode = CommandNode<Command2MCSender>
|
||||
|
|
|
@ -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<S, T>(
|
||||
class CoreArgumentBuilder<S : Command2Sender, T>(
|
||||
private val name: String,
|
||||
private val type: ArgumentType<T>,
|
||||
private val optional: Boolean
|
||||
) : ArgumentBuilder<S, CoreArgumentBuilder<S, T>>() {
|
||||
private var suggestionsProvider: SuggestionProvider<S>? = null
|
||||
internal lateinit var data: SubcommandData<*, S>
|
||||
fun suggests(provider: SuggestionProvider<S>): CoreArgumentBuilder<S, T> {
|
||||
suggestionsProvider = provider
|
||||
return this
|
||||
|
@ -29,12 +31,13 @@ class CoreArgumentBuilder<S, T>(
|
|||
redirectModifier,
|
||||
isFork,
|
||||
suggestionsProvider,
|
||||
optional
|
||||
optional,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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> {
|
||||
private final boolean optional;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
override fun createBuilder(): RequiredArgumentBuilder<S, T> {
|
||||
return super.createBuilder()
|
||||
}
|
||||
}
|
|
@ -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<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcommandData> private constructor(
|
||||
class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<S>, TSD : NoOpSubcommandData> private constructor(
|
||||
literal: String,
|
||||
val data: TSD
|
||||
) : LiteralArgumentBuilder<S>(literal) {
|
||||
|
@ -30,6 +31,14 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
|
|||
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 {
|
||||
/**
|
||||
* 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 fullPath The full command path of this subcommand.
|
||||
*/
|
||||
fun <S : Command2Sender, TC : ICommand2<*>> literal(
|
||||
fun <S : Command2Sender, TC : ICommand2<S>> literal(
|
||||
name: String,
|
||||
senderType: Class<*>,
|
||||
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 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,
|
||||
helpTextGetter: (Any) -> Array<String>,
|
||||
): CoreCommandBuilder<S, TC, NoOpSubcommandData> {
|
||||
|
|
|
@ -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<Double, Double>,
|
||||
val optional: Boolean,
|
||||
val description: String,
|
||||
val annotations: Array<Annotation> // TODO: Annotations for parameters as well
|
||||
val annotations: Array<Annotation>
|
||||
)
|
||||
|
|
|
@ -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 <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.
|
||||
*/
|
||||
|
@ -35,4 +42,8 @@ object CommandUtils {
|
|||
val ret = core<TP, NoOpSubcommandData>()
|
||||
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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue