Fix and improve help text code, add subcommand list

This commit is contained in:
Norbi Peti 2023-07-24 21:59:33 +02:00
parent d42a4a8a70
commit 9dff03c18e
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
5 changed files with 90 additions and 21 deletions

View file

@ -4,8 +4,10 @@ import buttondevteam.core.MainPlugin
import buttondevteam.lib.ChromaUtils
import buttondevteam.lib.chat.commands.*
import buttondevteam.lib.chat.commands.CommandUtils.coreCommand
import buttondevteam.lib.chat.commands.CommandUtils.coreCommandNoOp
import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable
import buttondevteam.lib.chat.commands.CommandUtils.getDefaultForEasilyRepresentable
import buttondevteam.lib.chat.commands.CommandUtils.isCommand
import buttondevteam.lib.chat.commands.CommandUtils.isEasilyRepresentable
import buttondevteam.lib.chat.commands.CommandUtils.subcommandData
import buttondevteam.lib.chat.commands.CommandUtils.subcommandDataNoOp
@ -233,7 +235,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
var mainCommand: CoreCommandNode<TP, *>? = null
split.dropLast(1).forEachIndexed { i, part ->
val child = parent.getChild(part)
if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp<TP, TC>(part, getSubcommandList())
if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp<TP, TC>(part) { emptyArray() }
.executes(::executeHelpText).build().also { parent = it })
else parent = child
if (i == 0) mainCommand =
@ -318,7 +320,12 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @return Vanilla command success level (0)
*/
private fun executeHelpText(context: CommandContext<TP>): Int {
context.source.sendMessage(context.nodes.lastOrNull()?.node?.subcommandDataNoOp()?.getHelpText(context.source))
val node = context.nodes.lastOrNull()?.node ?: throw IllegalStateException()
val helpText = node.subcommandDataNoOp()?.getHelpText(context.source) ?: throw IllegalStateException()
if (node.isCommand()) {
val subs = getSubcommands(node.coreCommandNoOp()!!).map { commandChar + it.data.fullPath }
context.source.sendMessage(helpText + "${ChatColor.GOLD}---- Subcommands ----" + subs)
}
return 0
}
@ -343,7 +350,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
//TODO: Should have a prettier display of Command2 classes here
val type = sd.senderType.simpleName.fold("") { s, ch -> s + if (ch.isUpperCase()) " " + ch.lowercase() else ch }
sender.sendMessage("${ChatColor.RED}You need to be a $type to use this command.")
sender.sendMessage(sd.getHelpText(sender)) //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only
executeHelpText(context) //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only
return 0
}
@ -352,7 +359,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
// TODO: Varargs support? (colors?)
// TODO: Character handling (strlen)
executeInvokeCommand(sd, sender, convertedSender, params)
executeInvokeCommand(sd, sender, convertedSender, params, context)
return 0
}
@ -383,13 +390,13 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
/**
* Invokes the command method with the given sender and parameters.
*/
private fun executeInvokeCommand(sd: SubcommandData<TC, TP>, sender: TP, actualSender: Any, params: List<Any?>) {
private fun executeInvokeCommand(sd: SubcommandData<TC, TP>, sender: TP, actualSender: Any, params: List<Any?>, context: CommandContext<TP>) {
val invokeCommand = {
try {
val ret = sd.executeCommand(actualSender, *params.toTypedArray())
if (ret is Boolean) {
if (!ret) //Show usage
sd.sendHelpText(sender)
executeHelpText(context)
} else if (ret != null)
throw Exception("Wrong return type! Must return a boolean or void. Return value: $ret")
} catch (e: InvocationTargetException) {

View file

@ -41,6 +41,15 @@ object CommandUtils {
else null
}
/**
* Returns the node as a probably-not-executable core command node or return null if it's an argument node.
*
* This method will work for any node as opposed to [CommandUtils.coreCommand].
*/
fun <TP : Command2Sender> CommandNode<TP>.coreCommandNoOp(): CoreCommandNode<TP, NoOpSubcommandData>? {
return coreCommand()
}
/**
* Returns the node as an executable core command node or returns null if it's a no-op node.
*
@ -76,11 +85,11 @@ object CommandUtils {
@Suppress("UNCHECKED_CAST")
fun <TP : Command2Sender, TC : ICommand2<TP>> CommandNode<TP>.subcommandData(): SubcommandData<TC, TP>? {
return coreArgument()?.commandData as SubcommandData<TC, TP>?
?: coreCommand<_, SubcommandData<TC, TP>>()?.data
?: coreExecutable<TP, TC>()?.data
}
fun <TP : Command2Sender> CommandNode<TP>.subcommandDataNoOp(): NoOpSubcommandData? {
return subcommandData() ?: coreCommand<_, NoOpSubcommandData>()?.data
return subcommandData() ?: coreCommandNoOp()?.data
}
fun Class<*>.isEasilyRepresentable(): Boolean {

View file

@ -80,11 +80,4 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
method.isAccessible = true
return method.invoke(command, sender, *args)
}
/**
* Send the help text to the specified sender.
*/
fun sendHelpText(sender: TP) {
sender.sendMessage(getHelpText(sender))
}
}

View file

@ -46,9 +46,13 @@ class Command2MCTest {
assertEquals("test", coreExecutable?.data?.argumentsInOrder?.firstOrNull()?.name, "Failed to get correct argument name")
assertEquals(String::class.java, coreExecutable?.data?.arguments?.get("test")?.type, "The argument could not be found or type doesn't match")
assertEquals(Command2MCSender::class.java, coreExecutable?.data?.senderType, "The sender's type doesn't seem to be stored correctly")
MainPlugin.instance.registerCommand(NoArgTestCommand)
assertEquals("No sender parameter for method '${ErroringTestCommand::class.java.getMethod("def")}'", assertFails { MainPlugin.instance.registerCommand(ErroringTestCommand) }.message)
MainPlugin.instance.registerCommand(MultiArgTestCommand)
MainPlugin.instance.registerCommand(TestNoMainCommand1)
MainPlugin.instance.registerCommand(TestNoMainCommand2)
}
@Test
@ -72,19 +76,33 @@ class Command2MCTest {
val user = ChromaGamerBase.getUser(UUID.randomUUID().toString(), TBMCPlayer::class.java)
user.playerName = "TestPlayer"
val sender = object : Command2MCSender(user, Channel.globalChat, user) {
private var messageReceived: String? = null
private var allowMessageReceive = false
override fun sendMessage(message: String) {
if (allowMessageReceive) {
messageReceived = message
} else {
error(message)
}
}
override fun sendMessage(message: Array<String>) {
error(message.joinToString("\n"))
sendMessage(message.joinToString("\n"))
}
fun withMessageReceive(action: () -> Unit): String? {
allowMessageReceive = true
action()
allowMessageReceive = false
return messageReceived
}
}
runCommand(sender, "/test hmm", TestCommand, "hmm")
runCommand(sender, "/noargtest", NoArgTestCommand, "TestPlayer")
assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/noargtest failing") }
runFailingCommand(sender, "/erroringtest")
runCommand(sender, "/multiargtest hmm mhm", MultiArgTestCommand, "hmmmhm")
runCommand(sender, "/multiargtest test hmm mhm", MultiArgTestCommand, "hmmmhm")
runCommand(sender, "/multiargtest test2 true 19", MultiArgTestCommand, "true 19")
runCommand(sender, "/multiargtest testoptional", MultiArgTestCommand, "false")
@ -95,7 +113,19 @@ class Command2MCTest {
runCommand(sender, "/test plugin Chroma-Core", TestCommand, "Chroma-Core")
assertFails { ButtonPlugin.command2MC.handleCommand(sender, "/test playerfail TestPlayer") }
// TODO: Add expected missing params
assertEquals("Test command\n" +
"Used for testing\n" +
"§6---- Subcommands ----\n" +
"/test playerfail\n" +
"/test plugin", sender.withMessageReceive { ButtonPlugin.command2MC.handleCommand(sender, "/test") })
runCommand(sender, "/some test cmd", TestNoMainCommand1, "TestPlayer")
runCommand(sender, "/some another cmd", TestNoMainCommand2, "TestPlayer")
assertEquals("§6---- Subcommands ----\n" +
"/some test cmd\n" +
"/some another cmd", sender.withMessageReceive { ButtonPlugin.command2MC.handleCommand(sender, "/some") })
}
private fun runCommand(sender: Command2MCSender, command: String, obj: ITestCommand2MC, expected: String) {
@ -107,7 +137,7 @@ class Command2MCTest {
assert(!ButtonPlugin.command2MC.handleCommand(sender, command)) { "Could execute command $command that shouldn't work" }
}
@CommandClass
@CommandClass(helpText = ["Test command", "Used for testing"])
object TestCommand : ICommand2MC(), ITestCommand2MC {
override var testCommandReceived: String? = null
@ -154,7 +184,7 @@ class Command2MCTest {
override var testCommandReceived: String? = null
@Command2.Subcommand
fun def(sender: Command2MCSender, test: String, test2: String) {
fun test(sender: Command2MCSender, test: String, test2: String) {
testCommandReceived = test + test2
}
@ -174,6 +204,28 @@ class Command2MCTest {
}
}
@CommandClass(path = "some test cmd")
object TestNoMainCommand1 : ICommand2MC(), ITestCommand2MC {
override var testCommandReceived: String? = null
@Command2.Subcommand
override fun def(sender: Command2MCSender): Boolean {
testCommandReceived = sender.name
return true
}
}
@CommandClass(path = "some another cmd")
object TestNoMainCommand2 : ICommand2MC(), ITestCommand2MC {
override var testCommandReceived: String? = null
@Command2.Subcommand
override fun def(sender: Command2MCSender): Boolean {
testCommandReceived = sender.name
return true
}
}
companion object {
private var initialized = false
}

View file

@ -25,6 +25,14 @@ buttondevteam:
test2:
method: test2()
params: "btest ntest"
TestNoMainCommand1:
def:
method: def()
params: ''
TestNoMainCommand2:
def:
method: def()
params: ''
core:
ComponentCommand:
def: