From 9dff03c18e13195f1c79c6efb2116c0560c84c7a Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 24 Jul 2023 21:59:33 +0200 Subject: [PATCH] Fix and improve help text code, add subcommand list --- .../java/buttondevteam/lib/chat/Command2.kt | 19 ++++-- .../lib/chat/commands/CommandUtils.kt | 13 +++- .../lib/chat/commands/SubcommandData.kt | 7 -- .../lib/chat/test/Command2MCTest.kt | 64 +++++++++++++++++-- Chroma-Core/src/test/resources/commands.yml | 8 +++ 5 files changed, 90 insertions(+), 21 deletions(-) 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 aecc483..f8a213f 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt @@ -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, TP : Command2Sender>( var mainCommand: CoreCommandNode? = null split.dropLast(1).forEachIndexed { i, part -> val child = parent.getChild(part) - if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp(part, getSubcommandList()) + if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp(part) { emptyArray() } .executes(::executeHelpText).build().also { parent = it }) else parent = child if (i == 0) mainCommand = @@ -318,7 +320,12 @@ abstract class Command2, TP : Command2Sender>( * @return Vanilla command success level (0) */ private fun executeHelpText(context: CommandContext): 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, 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, 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, TP : Command2Sender>( /** * Invokes the command method with the given sender and parameters. */ - private fun executeInvokeCommand(sd: SubcommandData, sender: TP, actualSender: Any, params: List) { + private fun executeInvokeCommand(sd: SubcommandData, sender: TP, actualSender: Any, params: List, context: CommandContext) { 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) { 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 d191309..1deaf47 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 @@ -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 CommandNode.coreCommandNoOp(): CoreCommandNode? { + 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 > CommandNode.subcommandData(): SubcommandData? { return coreArgument()?.commandData as SubcommandData? - ?: coreCommand<_, SubcommandData>()?.data + ?: coreExecutable()?.data } fun CommandNode.subcommandDataNoOp(): NoOpSubcommandData? { - return subcommandData() ?: coreCommand<_, NoOpSubcommandData>()?.data + return subcommandData() ?: coreCommandNoOp()?.data } fun Class<*>.isEasilyRepresentable(): Boolean { 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 fb4c679..c3f208c 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 @@ -80,11 +80,4 @@ class SubcommandData, 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)) - } } \ No newline at end of file diff --git a/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCTest.kt b/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCTest.kt index 8ca31e8..fbebd2e 100644 --- a/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCTest.kt +++ b/Chroma-Core/src/test/kotlin/buttondevteam/lib/chat/test/Command2MCTest.kt @@ -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) { - error(message) + if (allowMessageReceive) { + messageReceived = message + } else { + error(message) + } } override fun sendMessage(message: Array) { - 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 } diff --git a/Chroma-Core/src/test/resources/commands.yml b/Chroma-Core/src/test/resources/commands.yml index 49509ce..1b77d96 100644 --- a/Chroma-Core/src/test/resources/commands.yml +++ b/Chroma-Core/src/test/resources/commands.yml @@ -25,6 +25,14 @@ buttondevteam: test2: method: test2() params: "btest ntest" + TestNoMainCommand1: + def: + method: def() + params: '' + TestNoMainCommand2: + def: + method: def() + params: '' core: ComponentCommand: def: