From 4287981943b1d5f9177057d4feff68408efa424c Mon Sep 17 00:00:00 2001 From: MattMXX Date: Sat, 10 Feb 2024 15:52:00 +0000 Subject: [PATCH 01/25] Testing string command impl --- .idea/kotlinc.xml | 2 +- .idea/misc.xml | 1 + api/build.gradle.kts | 15 ++- .../commands/stringbuilder/CommandContext.kt | 9 ++ .../commands/stringbuilder/StringCommand.kt | 105 ++++++++++++++++++ .../commands/stringbuilder/arg/Argument.kt | 38 +++++++ .../stringbuilder/arg/ArgumentContext.kt | 14 +++ .../mattmx/ktgui/commands/stringbuilder/t.kt | 35 ++++++ build.gradle.kts | 2 +- 9 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/CommandContext.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 0e65cea..b1077fb 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 0c1b468..bb4f343 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,3 +1,4 @@ + diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 84c4a51..7363c04 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,8 +1,14 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - `maven-publish` + id("java") + kotlin("jvm") version "1.7.10" id("com.github.johnrengelman.shadow") version "7.0.0" + `maven-publish` +} + +repositories { + mavenCentral() } dependencies { @@ -25,6 +31,13 @@ tasks { build { dependsOn(shadowJar) } + shadowJar { + mergeServiceFiles() + exclude { +// it.path.startsWith("kotlin") && !it.path.contains("reactive") + it.name.startsWith("kotlin") + } + } } tasks.withType { diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/CommandContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/CommandContext.kt new file mode 100644 index 0000000..d54fd92 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/CommandContext.kt @@ -0,0 +1,9 @@ +package com.mattmx.ktgui.commands.stringbuilder + +import org.bukkit.command.CommandSender + +class CommandContext( + val args: List +) { + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt new file mode 100644 index 0000000..a587d36 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt @@ -0,0 +1,105 @@ +package com.mattmx.ktgui.commands.stringbuilder + +import com.mattmx.ktgui.commands.CommandInvocation +import com.mattmx.ktgui.commands.declarative.ArgumentType +import com.mattmx.ktgui.commands.stringbuilder.arg.Argument +import com.mattmx.ktgui.configuration.Configuration +import org.bukkit.command.CommandSender + +class StringCommand { + lateinit var name: String + var aliases = arrayOf() + var subcommands = arrayOf>() + var expectedArguments = arrayOf>() + private lateinit var permission: (CommandContext) -> Boolean + private lateinit var runs: (CommandContext) -> Unit + private lateinit var missing: (CommandContext) -> Unit + + init { + // todo parse params + } + + infix fun missing(block: CommandContext.() -> Unit) = apply { + this.missing = block + } + + infix fun runs(block: CommandContext.() -> Unit) = apply { + this.runs = block + } + + inline operator fun String.invoke(block: StringCommand.() -> Unit) = + StringCommand().let { + // todo could also have parameters + subcommands += it + } + + fun getSuggestions(context: CommandContext): List { + val currentArgument = getCurrentArgument(context) + val suggestions = currentArgument?.suggestions()?.invoke(context) ?: return emptyList() + val lastArgument = context.args.lastOrNull() ?: "" + return suggestions.filter { suggestion -> suggestion.startsWith(lastArgument, true) }.toList() + } + + /** + * Used to find the current argument, presuming we are on the current sub-command + * + * @param args the current arguments of the command invocation + * @param sender command sender + * @return the current argument or null if it is invalid + */ + private fun getCurrentArgument(context: CommandContext): Argument<*>? { + if (expectedArguments.isEmpty()) return null + // Greedy arguments will eat the rest of the arguments + if (expectedArguments.first().type() == Argument.Type.GREEDY) return expectedArguments.first() + + repeat(expectedArguments.size) { argIndex -> + val registered = expectedArguments.getOrNull(argIndex) ?: return null + val comparing = context.args.getOrNull(argIndex) ?: return null + // todo need to think about optional args!!! wtf do we do there + } + + return null + } + + operator fun unaryPlus() = register() + + fun register() = apply { + + } + + /** + * Builds a usage for this command. + * You can create your own method with the [Configuration] class. + * + * @return a formatted string for usage of the command + */ + fun getUsage(showDescriptions: Boolean = false, maxArgumentOptionsDisplayed: Int = 5): String { + var builder = "$name " + if (subcommands.isNotEmpty()) + builder += subcommands.joinToString("|") { subcommand -> subcommand.name } + else { + var end = "" + builder += expectedArguments.joinToString(" ") { arg -> + val suggestions = arg.getDefaultSuggestions()?.let { + if (it.size <= maxArgumentOptionsDisplayed) " = [" + it.joinToString("|") + "]" + else " = [...]" + } ?: "" + + // Apply descriptions + if (showDescriptions) { + val extra = if (arg.isRequired()) "(Required)" else "(Optional)" + end += "\n> ${arg.name()} - ${arg.description()} $extra" + } + + if (arg.isRequired()) { + "<${arg.name()}!$suggestions>" + } else "<${arg.name()}?$suggestions>" + } + builder += end + } + return builder + } +} + +inline operator fun String.invoke(block: StringCommand.() -> Unit) = + StringCommand().apply(block) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt new file mode 100644 index 0000000..b0cc687 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt @@ -0,0 +1,38 @@ +package com.mattmx.ktgui.commands.stringbuilder.arg + +import com.mattmx.ktgui.commands.stringbuilder.CommandContext +import org.bukkit.command.CommandSender +import java.util.* + +class Argument( + private val name: String, + private val type: Type, + private val description: String? = null, + private val required: Boolean = true +) { + private var suggests = Optional.empty<(CommandContext<*>) -> List?>() + + fun name() = name + + fun description() = description ?: "" + + fun type() = type + + fun isRequired() = required + + fun withSuggestions(suggest: CommandContext<*>.() -> List?) = apply { + this.suggests = Optional.of(suggest) + } + + fun suggestions() = suggests.orElse(null) + + fun getDefaultSuggestions() = if (suggests.isPresent) { + val context = CommandContext(emptyList()) + suggests.get().invoke(context) + } else listOf() + + enum class Type { + SINGLE, + GREEDY + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt new file mode 100644 index 0000000..6a25b31 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt @@ -0,0 +1,14 @@ +package com.mattmx.ktgui.commands.stringbuilder.arg + +import java.util.* + +class ArgumentContext( + private val value: Optional, + private val argument: Argument +) { + fun argument() = argument + + fun isEmpty() = value.isEmpty + + fun getOrNull() = value.orElse(null) +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt new file mode 100644 index 0000000..d60855b --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt @@ -0,0 +1,35 @@ +package com.mattmx.ktgui.commands.stringbuilder + +import org.bukkit.command.CommandSender + +fun main() { + val testCmd = + + "/test " { + runs { println("test") } + } + + val foo = + + "/foo" { + + "bar" { + runs { + println("foo bar") + } + } + + "fizz" { + runs { + println("foo fizz") + } + } + + runs { + println("foo!") + } + + } missing { + println("missing arg") + } + + println(testCmd.getUsage()) +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index cdc1402..5ede2af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.7.21" + kotlin("jvm") version "1.7.10" } val version = "2.2" From 12e1d94dc05e669f63ce1122ae8d4323111bd20e Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Sat, 10 Feb 2024 15:53:39 +0000 Subject: [PATCH 02/25] Update build.yml --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7ffbe1..b6eb517 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,7 @@ name: Build Gradle project on: push: + branches: main jobs: build-gradle-project: From 2a680db49c0c27b203325f9e914ff60d4cbcc0d6 Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Sat, 10 Feb 2024 15:54:12 +0000 Subject: [PATCH 03/25] Create test-build.yml --- .github/workflows/test-build.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/test-build.yml diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml new file mode 100644 index 0000000..6ddcaf4 --- /dev/null +++ b/.github/workflows/test-build.yml @@ -0,0 +1,24 @@ +on: + push: + branches: + - "refactor" + +jobs: + build-gradle-project: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + - name: Checkout project sources + uses: actions/checkout@v3 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Run build with Gradle Wrapper + run: ./gradlew build + - uses: actions/upload-artifact@v3 + with: + name: build-outputs + path: plugin/build/libs/ktgui-plugin-*.jar From 45e8445d7a50c1a52f8cd92565dfd8df2057d8c3 Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Sat, 10 Feb 2024 15:54:26 +0000 Subject: [PATCH 04/25] Update build.yml --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6eb517..15e2eff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,8 @@ name: Build Gradle project on: push: - branches: main + branches: + - "main" jobs: build-gradle-project: From d0b02a7d422943da4b96cb299dd0ef4814554712 Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Sat, 10 Feb 2024 15:54:48 +0000 Subject: [PATCH 05/25] Update build.yml --- .github/workflows/build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15e2eff..59d6d02 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,3 @@ jobs: uses: gradle/gradle-build-action@v2 - name: Run build with Gradle Wrapper run: ./gradlew build - - uses: actions/upload-artifact@v3 - with: - name: build-outputs - path: plugin/build/libs/ktgui-plugin-*.jar From 6b5761cadbde2677290e895a3c9450989068a040 Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Sat, 10 Feb 2024 15:55:26 +0000 Subject: [PATCH 06/25] Update test-build.yml --- .github/workflows/test-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 6ddcaf4..ac07dc0 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -4,7 +4,7 @@ on: - "refactor" jobs: - build-gradle-project: + test-build-gradle-project: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 406e332deedc9a2a54c927c3e4aaf705acf9308d Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Sat, 10 Feb 2024 15:55:51 +0000 Subject: [PATCH 07/25] Update test-build.yml --- .github/workflows/test-build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index ac07dc0..69e5c06 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -18,7 +18,3 @@ jobs: uses: gradle/gradle-build-action@v2 - name: Run build with Gradle Wrapper run: ./gradlew build - - uses: actions/upload-artifact@v3 - with: - name: build-outputs - path: plugin/build/libs/ktgui-plugin-*.jar From e0f44a02b96c50c7bf89c196640e3fb42c92a2fd Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Sat, 10 Feb 2024 15:55:57 +0000 Subject: [PATCH 08/25] Update build.yml --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59d6d02..7d2010b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: - "main" jobs: - build-gradle-project: + build-and-release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -20,3 +20,7 @@ jobs: uses: gradle/gradle-build-action@v2 - name: Run build with Gradle Wrapper run: ./gradlew build + - uses: actions/upload-artifact@v3 + with: + name: build-outputs + path: plugin/build/libs/ktgui-plugin-*.jar From 99066287ebd1cc3f41e42fe12a9fb1602aac1163 Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Mon, 12 Feb 2024 13:43:36 +0000 Subject: [PATCH 09/25] impl parser for string command dsl --- .idea/misc.xml | 1 - api/build.gradle.kts | 10 +- .../commands/stringbuilder/StringCommand.kt | 1 + .../syntax/CommandBuilderSyntax.kt | 3 + .../syntax/CommandDeclarationSyntax.kt | 12 +++ .../commands/stringbuilder/syntax/Lexer.kt | 79 ++++++++++++++++ .../commands/stringbuilder/syntax/Parser.kt | 92 +++++++++++++++++++ .../syntax/SubCommandDeclarationSyntax.kt | 11 +++ .../stringbuilder/syntax/SyntaxKind.kt | 24 +++++ .../stringbuilder/syntax/SyntaxNode.kt | 27 ++++++ .../stringbuilder/syntax/SyntaxToken.kt | 10 ++ .../syntax/VariableDeclarationSyntax.kt | 18 ++++ .../stringbuilder/syntax/VariableType.kt | 6 ++ .../mattmx/ktgui/commands/stringbuilder/t.kt | 7 +- 14 files changed, 294 insertions(+), 7 deletions(-) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/CommandBuilderSyntax.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/CommandDeclarationSyntax.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Lexer.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SubCommandDeclarationSyntax.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxKind.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxNode.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxToken.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableDeclarationSyntax.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableType.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index bb4f343..0c1b468 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 7363c04..a054707 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,7 +1,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - id("java") kotlin("jvm") version "1.7.10" id("com.github.johnrengelman.shadow") version "7.0.0" `maven-publish` @@ -13,6 +12,7 @@ repositories { dependencies { compileOnly(kotlin("reflect")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10") } tasks.test { @@ -33,10 +33,10 @@ tasks { } shadowJar { mergeServiceFiles() - exclude { -// it.path.startsWith("kotlin") && !it.path.contains("reactive") - it.name.startsWith("kotlin") - } +// exclude { +//// it.path.startsWith("kotlin") && !it.path.contains("reactive") +// it.name.startsWith("kotlin") +// } } } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt index a587d36..f2b3a3c 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt @@ -17,6 +17,7 @@ class StringCommand { init { // todo parse params + name = "test" } infix fun missing(block: CommandContext.() -> Unit) = apply { diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/CommandBuilderSyntax.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/CommandBuilderSyntax.kt new file mode 100644 index 0000000..0819f5e --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/CommandBuilderSyntax.kt @@ -0,0 +1,3 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +abstract class CommandBuilderSyntax : SyntaxNode() \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/CommandDeclarationSyntax.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/CommandDeclarationSyntax.kt new file mode 100644 index 0000000..b33c118 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/CommandDeclarationSyntax.kt @@ -0,0 +1,12 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +class CommandDeclarationSyntax( + private val slashToken: SyntaxToken, + private val commandNameToken: SyntaxToken +) : CommandBuilderSyntax() { + override fun kind() = SyntaxKind.COMMAND_DECLARATION + + fun getName() = commandNameToken.text!! + + override fun children() = listOf(slashToken, commandNameToken) +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Lexer.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Lexer.kt new file mode 100644 index 0000000..4b0e607 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Lexer.kt @@ -0,0 +1,79 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +class Lexer( + private val text: String +) { + private var position: Int = 0 + private var start: Int = 0 + private var kind = SyntaxKind.BAD_TOKEN + private var value: Any? = null + + private fun current() = peek(0) + private fun next() = position++ + private fun peek(i: Int): Char { + val index = position + i + return if (index >= text.length) '\u0000' + else text[index] + } + + fun lex(): SyntaxToken { + start = position + kind = SyntaxKind.BAD_TOKEN + value = null + + val current = current() + if (current.isWhitespace()) { + readWhitespaceToken() + } else if (current.isLetter()) { + readIdentifierToken() + } else { + var value: SyntaxKind? = null + for (possible in SyntaxKind.values()) { + var isMatch = true + while (isMatch) { + if (possible.chars == null) { + isMatch = false + continue + } + + val localIndex = position - start + + if (localIndex >= possible.chars.length) { + value = possible + break + } + + isMatch = current() == possible.chars[localIndex] + + position++ + } + + if (value != null) break + position = start + } + kind = value ?: kind + } + + val subText = if (kind == SyntaxKind.END_OF_FILE) "\u0000" else text.substring(start, position) + + return SyntaxToken(kind, start, subText, value) + } + + private fun readWhitespaceToken() { + while (current().isWhitespace()) + next() + kind = SyntaxKind.WHITESPACE + } + + private fun readIdentifierToken() { + while (isVarNameChar(current())) + next() + + kind = SyntaxKind.IDENTIFIER + this.value = text.substring(start, position) + } + + private fun isVarNameChar(char: Char) = + char.isLetterOrDigit() || char in listOf('_', '-') + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt new file mode 100644 index 0000000..36d72b2 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt @@ -0,0 +1,92 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +class Parser( + val source: String +) { + private val lexer = Lexer(source) + private val tokens = arrayListOf() + private var position = 0 + + init { + var token: SyntaxToken? = null + + while (token?.kind != SyntaxKind.END_OF_FILE && token?.kind != SyntaxKind.BAD_TOKEN) { + token = lexer.lex() + if (token.kind != SyntaxKind.WHITESPACE && token.kind != SyntaxKind.BAD_TOKEN) { + tokens += token + } + } + } + + private fun peek(i: Int): SyntaxToken { + val index = position + i + if (index >= tokens.size) + return tokens.last() + return tokens[index] + } + + private fun current() = peek(0) + + private fun nextToken(): SyntaxToken { + val current = current() + position++ + return current + } + + private fun matchToken(kind: SyntaxKind): SyntaxToken { + if (current().kind == kind) { + return nextToken() + } + return SyntaxToken(SyntaxKind.BAD_TOKEN, current().start, "null", null) + } + + fun parse(): ArrayList { + val parts = arrayListOf() + while (position < tokens.size - 1) { + val part = when (current().kind()) { + SyntaxKind.OPEN_DIAMOND -> parseVariableDeclaration() + SyntaxKind.FORWARD_SLASH -> parseCommand() + SyntaxKind.IDENTIFIER -> parseSubCommand() + else -> error("Unexpected syntax token ${current().kind()}") + } + + parts += part + } + return parts + } + + /** + * Variable declaration sysntax: '' + */ + private fun parseVariableDeclaration(): VariableDeclarationSyntax { + val openDiamondBraces = matchToken(SyntaxKind.OPEN_DIAMOND) + val varName = matchToken(SyntaxKind.IDENTIFIER) + val colon = matchToken(SyntaxKind.COLON) + val type = matchToken(SyntaxKind.IDENTIFIER) + val ellipsis = matchToken(SyntaxKind.ELLIPSIS) + val closeDiamondBraces = matchToken(SyntaxKind.CLOSE_DIAMOND) + + return VariableDeclarationSyntax(openDiamondBraces, varName, colon, type, ellipsis, closeDiamondBraces) + } + + private fun parseCommand(): CommandDeclarationSyntax { + val slash = matchToken(SyntaxKind.FORWARD_SLASH) + val commandName = matchToken(SyntaxKind.IDENTIFIER) + + return CommandDeclarationSyntax(slash, commandName) + } + + private fun parseSubCommand(): SubCommandDeclarationSyntax { + val commandName = matchToken(SyntaxKind.IDENTIFIER) + + return SubCommandDeclarationSyntax(commandName) + } +} + +fun main() { + val parsed = Parser("/test foo ").parse() + + println((parsed[0] as CommandDeclarationSyntax).getName()) + println((parsed[1] as SubCommandDeclarationSyntax).getName()) + println((parsed[2] as VariableDeclarationSyntax).getType()) +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SubCommandDeclarationSyntax.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SubCommandDeclarationSyntax.kt new file mode 100644 index 0000000..71ccc5a --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SubCommandDeclarationSyntax.kt @@ -0,0 +1,11 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +class SubCommandDeclarationSyntax( + private val commandName: SyntaxToken +) : CommandBuilderSyntax() { + override fun kind() = SyntaxKind.SUB_COMMAND_DECLARATION + + fun getName() = commandName.text!! + + override fun children() = listOf(commandName) +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxKind.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxKind.kt new file mode 100644 index 0000000..cf62145 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxKind.kt @@ -0,0 +1,24 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +enum class SyntaxKind( + val chars: String? = null +) { + IDENTIFIER, + + COLON(":"), + FORWARD_SLASH("/"), + OPEN_DIAMOND("<"), + CLOSE_DIAMOND(">"), + ELLIPSIS("..."), + OPEN_SQUARE("["), + CLOSE_SQUARE("]"), + + WHITESPACE, + + END_OF_FILE("\u0000"), + BAD_TOKEN, + + COMMAND_DECLARATION, + SUB_COMMAND_DECLARATION, + VARIABLE_DECLARATION, +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxNode.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxNode.kt new file mode 100644 index 0000000..6bfd2e5 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxNode.kt @@ -0,0 +1,27 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +abstract class SyntaxNode { + + abstract fun kind() : SyntaxKind + + open fun children() : List { + val children = arrayListOf() + // default impl of children, you should override this. + for (member in this.javaClass.declaredFields) { + if (member.type.isAssignableFrom(SyntaxNode::class.java)) { + member.isAccessible = true + val value = member.get(this) + value?.let { children.add(value as SyntaxNode) } + } else if (member.type.isAssignableFrom(Iterable::class.java)) { + try { + member.isAccessible = true + val values = member.get(this) as Iterable + children.addAll(values) + } catch (_: ClassCastException) { + } + } + } + return children + } + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxToken.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxToken.kt new file mode 100644 index 0000000..7a886d1 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxToken.kt @@ -0,0 +1,10 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +class SyntaxToken( + val kind: SyntaxKind, + val start: Int, + val text: String?, + val value: Any? +) : SyntaxNode() { + override fun kind() = kind +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableDeclarationSyntax.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableDeclarationSyntax.kt new file mode 100644 index 0000000..9b07be3 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableDeclarationSyntax.kt @@ -0,0 +1,18 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +class VariableDeclarationSyntax( + private val openDiamondBracesToken: SyntaxToken, + private val varNameToken: SyntaxToken, + private val colonToken: SyntaxToken, + private val typeToken: SyntaxToken, + private val ellipsisToken: SyntaxToken, + private val closeDiamondBracesToken: SyntaxToken, +) : CommandBuilderSyntax() { + override fun kind() = SyntaxKind.VARIABLE_DECLARATION + + fun getName() = varNameToken.text!! + + fun getType() = VariableType(typeToken.text!!, ellipsisToken.text != null) + + override fun children() = listOf(openDiamondBracesToken, varNameToken, colonToken, typeToken, closeDiamondBracesToken) +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableType.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableType.kt new file mode 100644 index 0000000..c129da4 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableType.kt @@ -0,0 +1,6 @@ +package com.mattmx.ktgui.commands.stringbuilder.syntax + +data class VariableType( + val typeName: String, + val isVararg: Boolean +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt index d60855b..9fcc608 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt @@ -5,7 +5,12 @@ import org.bukkit.command.CommandSender fun main() { val testCmd = + "/test " { - runs { println("test") } + runs { +// val player by argument() +// val msg by argument() + + println("test") + } } val foo = + From fa01cdfa01ad4c5d1e38cbfedbbbbbdd14424ae8 Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:22:54 +0000 Subject: [PATCH 10/25] working parsing commands (will not build rn tho) --- .../commands/declarative/CommandContext.kt | 2 +- .../stringbuilder/MissingArgContext.kt | 9 ++ ...CommandContext.kt => RawCommandContext.kt} | 4 +- .../stringbuilder/RunnableCommandContext.kt | 17 ++++ .../commands/stringbuilder/StringCommand.kt | 86 ++++++++++++++----- .../commands/stringbuilder/arg/Argument.kt | 11 +-- .../stringbuilder/arg/ArgumentContext.kt | 2 + .../commands/stringbuilder/syntax/Parser.kt | 13 +-- .../stringbuilder/syntax/SyntaxKind.kt | 1 + .../syntax/VariableDeclarationSyntax.kt | 3 +- .../stringbuilder/syntax/VariableType.kt | 3 +- .../mattmx/ktgui/commands/stringbuilder/t.kt | 25 +++++- 12 files changed, 131 insertions(+), 45 deletions(-) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/MissingArgContext.kt rename api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/{CommandContext.kt => RawCommandContext.kt} (54%) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RunnableCommandContext.kt diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/declarative/CommandContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/declarative/CommandContext.kt index 933dfa1..ebb46ed 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/declarative/CommandContext.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/declarative/CommandContext.kt @@ -3,7 +3,7 @@ package com.mattmx.ktgui.commands.declarative import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty -class CommandContext( +open class CommandContext( val sender: S, val args: Array, ) { diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/MissingArgContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/MissingArgContext.kt new file mode 100644 index 0000000..7a2dba0 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/MissingArgContext.kt @@ -0,0 +1,9 @@ +package com.mattmx.ktgui.commands.stringbuilder + +import com.mattmx.ktgui.commands.stringbuilder.arg.Argument +import org.bukkit.command.CommandSender + +class MissingArgContext( + val argument: Argument<*>, + args: List +) : RawCommandContext(args) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/CommandContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RawCommandContext.kt similarity index 54% rename from api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/CommandContext.kt rename to api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RawCommandContext.kt index d54fd92..bf138ee 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/CommandContext.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RawCommandContext.kt @@ -2,8 +2,8 @@ package com.mattmx.ktgui.commands.stringbuilder import org.bukkit.command.CommandSender -class CommandContext( - val args: List +open class RawCommandContext( + val rawArgs: List ) { } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RunnableCommandContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RunnableCommandContext.kt new file mode 100644 index 0000000..7a10554 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RunnableCommandContext.kt @@ -0,0 +1,17 @@ +package com.mattmx.ktgui.commands.stringbuilder + +import com.mattmx.ktgui.commands.stringbuilder.arg.ArgumentContext +import org.bukkit.command.CommandSender +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class RunnableCommandContext( + private val providedArgs: HashMap>, + rawArgs: List +) : RawCommandContext(rawArgs) { + + fun argument() = ReadOnlyProperty { owner: Nothing?, prop: KProperty<*> -> + providedArgs[prop.name] ?: error("Unknown argument '${prop.name}'") + } + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt index f2b3a3c..ba65313 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt @@ -1,43 +1,63 @@ package com.mattmx.ktgui.commands.stringbuilder -import com.mattmx.ktgui.commands.CommandInvocation -import com.mattmx.ktgui.commands.declarative.ArgumentType import com.mattmx.ktgui.commands.stringbuilder.arg.Argument +import com.mattmx.ktgui.commands.stringbuilder.arg.ArgumentContext +import com.mattmx.ktgui.commands.stringbuilder.syntax.CommandDeclarationSyntax +import com.mattmx.ktgui.commands.stringbuilder.syntax.Parser +import com.mattmx.ktgui.commands.stringbuilder.syntax.SubCommandDeclarationSyntax +import com.mattmx.ktgui.commands.stringbuilder.syntax.VariableDeclarationSyntax import com.mattmx.ktgui.configuration.Configuration import org.bukkit.command.CommandSender +import java.util.Optional -class StringCommand { +class StringCommand( + source: String +) { lateinit var name: String var aliases = arrayOf() var subcommands = arrayOf>() var expectedArguments = arrayOf>() - private lateinit var permission: (CommandContext) -> Boolean - private lateinit var runs: (CommandContext) -> Unit - private lateinit var missing: (CommandContext) -> Unit + private var permission: Optional<(RunnableCommandContext) -> Boolean> = Optional.empty() + private var runs: Optional<(RunnableCommandContext) -> Unit> = Optional.empty() + private var missing: Optional<(MissingArgContext) -> Unit> = Optional.empty() init { - // todo parse params - name = "test" + val parsed = Parser(source).parse() + + for (syntax in parsed) { + when (syntax) { + is VariableDeclarationSyntax -> { + expectedArguments += Argument(syntax.getName(), syntax.getType(), null, !syntax.getType().isOptional) + } + is CommandDeclarationSyntax -> { + // todo should be top level only + name = syntax.getName() + } + is SubCommandDeclarationSyntax -> { + name = syntax.getName() + } + } + } } - infix fun missing(block: CommandContext.() -> Unit) = apply { - this.missing = block + infix fun missing(block: MissingArgContext.() -> Unit) = apply { + this.missing = Optional.of(block) } - infix fun runs(block: CommandContext.() -> Unit) = apply { - this.runs = block + infix fun runs(block: RunnableCommandContext.() -> Unit) = apply { + this.runs = Optional.of(block) } inline operator fun String.invoke(block: StringCommand.() -> Unit) = - StringCommand().let { + StringCommand(this).let { // todo could also have parameters subcommands += it } - fun getSuggestions(context: CommandContext): List { + fun getSuggestions(context: RawCommandContext): List { val currentArgument = getCurrentArgument(context) val suggestions = currentArgument?.suggestions()?.invoke(context) ?: return emptyList() - val lastArgument = context.args.lastOrNull() ?: "" + val lastArgument = context.rawArgs.lastOrNull() ?: "" return suggestions.filter { suggestion -> suggestion.startsWith(lastArgument, true) }.toList() } @@ -48,14 +68,15 @@ class StringCommand { * @param sender command sender * @return the current argument or null if it is invalid */ - private fun getCurrentArgument(context: CommandContext): Argument<*>? { + private fun getCurrentArgument(context: RawCommandContext): Argument<*>? { if (expectedArguments.isEmpty()) return null - // Greedy arguments will eat the rest of the arguments - if (expectedArguments.first().type() == Argument.Type.GREEDY) return expectedArguments.first() + + // Greedy arguments will eat the rest of the arguments todo + if (expectedArguments.first().type().isVararg) return expectedArguments.first() repeat(expectedArguments.size) { argIndex -> val registered = expectedArguments.getOrNull(argIndex) ?: return null - val comparing = context.args.getOrNull(argIndex) ?: return null + val comparing = context.rawArgs.getOrNull(argIndex) ?: return null // todo need to think about optional args!!! wtf do we do there } @@ -68,6 +89,27 @@ class StringCommand { } + fun invoke(context: RawCommandContext) { + + // Set variables + val argumentValues = hashMapOf>() + for ((index, arg) in expectedArguments.withIndex()) { + // todo var offset for sub-commands? + val value = context.rawArgs.getOrNull(index) + + if (arg.isRequired() && value == null) { + val missingArgContext = MissingArgContext(arg, context.rawArgs) + missing.ifPresent { it.invoke(missingArgContext) } + return + } else { + argumentValues[arg.name()] = ArgumentContext(Optional.ofNullable(value), arg as Argument) + } + } + + val runnableContext = RunnableCommandContext(argumentValues, context.rawArgs) + runs.ifPresent { it.invoke(runnableContext) } + } + /** * Builds a usage for this command. * You can create your own method with the [Configuration] class. @@ -82,8 +124,8 @@ class StringCommand { var end = "" builder += expectedArguments.joinToString(" ") { arg -> val suggestions = arg.getDefaultSuggestions()?.let { - if (it.size <= maxArgumentOptionsDisplayed) " = [" + it.joinToString("|") + "]" - else " = [...]" + if (it.isNotEmpty() && it.size <= maxArgumentOptionsDisplayed) " = [" + it.joinToString("|") + "]" + else ":${arg.type().typeName}" } ?: "" // Apply descriptions @@ -103,4 +145,4 @@ class StringCommand { } inline operator fun String.invoke(block: StringCommand.() -> Unit) = - StringCommand().apply(block) \ No newline at end of file + StringCommand(this).apply(block) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt index b0cc687..8ffefa5 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt @@ -1,16 +1,17 @@ package com.mattmx.ktgui.commands.stringbuilder.arg -import com.mattmx.ktgui.commands.stringbuilder.CommandContext +import com.mattmx.ktgui.commands.stringbuilder.RawCommandContext +import com.mattmx.ktgui.commands.stringbuilder.syntax.VariableType import org.bukkit.command.CommandSender import java.util.* class Argument( private val name: String, - private val type: Type, + private val type: VariableType, private val description: String? = null, private val required: Boolean = true ) { - private var suggests = Optional.empty<(CommandContext<*>) -> List?>() + private var suggests = Optional.empty<(RawCommandContext<*>) -> List?>() fun name() = name @@ -20,14 +21,14 @@ class Argument( fun isRequired() = required - fun withSuggestions(suggest: CommandContext<*>.() -> List?) = apply { + fun withSuggestions(suggest: RawCommandContext<*>.() -> List?) = apply { this.suggests = Optional.of(suggest) } fun suggestions() = suggests.orElse(null) fun getDefaultSuggestions() = if (suggests.isPresent) { - val context = CommandContext(emptyList()) + val context = RawCommandContext(emptyList()) suggests.get().invoke(context) } else listOf() diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt index 6a25b31..fd613b4 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt @@ -11,4 +11,6 @@ class ArgumentContext( fun isEmpty() = value.isEmpty fun getOrNull() = value.orElse(null) + + override fun toString() = getOrNull().toString() } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt index 36d72b2..8cb056a 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt @@ -37,7 +37,7 @@ class Parser( if (current().kind == kind) { return nextToken() } - return SyntaxToken(SyntaxKind.BAD_TOKEN, current().start, "null", null) + return SyntaxToken(SyntaxKind.BAD_TOKEN, current().start, null, null) } fun parse(): ArrayList { @@ -64,9 +64,10 @@ class Parser( val colon = matchToken(SyntaxKind.COLON) val type = matchToken(SyntaxKind.IDENTIFIER) val ellipsis = matchToken(SyntaxKind.ELLIPSIS) + val optional = matchToken(SyntaxKind.QUESTION) val closeDiamondBraces = matchToken(SyntaxKind.CLOSE_DIAMOND) - return VariableDeclarationSyntax(openDiamondBraces, varName, colon, type, ellipsis, closeDiamondBraces) + return VariableDeclarationSyntax(openDiamondBraces, varName, colon, type, ellipsis, optional, closeDiamondBraces) } private fun parseCommand(): CommandDeclarationSyntax { @@ -81,12 +82,4 @@ class Parser( return SubCommandDeclarationSyntax(commandName) } -} - -fun main() { - val parsed = Parser("/test foo ").parse() - - println((parsed[0] as CommandDeclarationSyntax).getName()) - println((parsed[1] as SubCommandDeclarationSyntax).getName()) - println((parsed[2] as VariableDeclarationSyntax).getType()) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxKind.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxKind.kt index cf62145..9cc4715 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxKind.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/SyntaxKind.kt @@ -6,6 +6,7 @@ enum class SyntaxKind( IDENTIFIER, COLON(":"), + QUESTION("?"), FORWARD_SLASH("/"), OPEN_DIAMOND("<"), CLOSE_DIAMOND(">"), diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableDeclarationSyntax.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableDeclarationSyntax.kt index 9b07be3..7dfc8ee 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableDeclarationSyntax.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableDeclarationSyntax.kt @@ -6,13 +6,14 @@ class VariableDeclarationSyntax( private val colonToken: SyntaxToken, private val typeToken: SyntaxToken, private val ellipsisToken: SyntaxToken, + private val optional: SyntaxToken, private val closeDiamondBracesToken: SyntaxToken, ) : CommandBuilderSyntax() { override fun kind() = SyntaxKind.VARIABLE_DECLARATION fun getName() = varNameToken.text!! - fun getType() = VariableType(typeToken.text!!, ellipsisToken.text != null) + fun getType() = VariableType(typeToken.text!!, ellipsisToken.text != null, optional.text != null) override fun children() = listOf(openDiamondBracesToken, varNameToken, colonToken, typeToken, closeDiamondBracesToken) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableType.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableType.kt index c129da4..8dc7680 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableType.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/VariableType.kt @@ -2,5 +2,6 @@ package com.mattmx.ktgui.commands.stringbuilder.syntax data class VariableType( val typeName: String, - val isVararg: Boolean + val isVararg: Boolean, + val isOptional: Boolean ) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt index 9fcc608..df59602 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt @@ -6,13 +6,32 @@ fun main() { val testCmd = + "/test " { runs { -// val player by argument() -// val msg by argument() + val player by argument() + val msg by argument() - println("test") + println("[${player}]: $msg") + } + } missing { + println("Missing arg '${argument.name()}'") + } + + "/hello" { + runs { + println("hello") + } + } + "world" { + runs { + println("hello world") + } + } + "" { + runs { + val msg by argument() + println("hello world ($msg)") } } + testCmd.invoke(RawCommandContext(listOf("mattmx", "hello", "world"))) + val foo = + "/foo" { From 6b330e56a716fa14cf9d16f51bfb3b0ee91fb6f3 Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Tue, 13 Feb 2024 15:43:03 +0000 Subject: [PATCH 11/25] working arguments, missing args, descriptions etc --- .../stringbuilder/MissingArgContext.kt | 2 +- .../commands/stringbuilder/StringCommand.kt | 55 +++++++++++---- .../commands/stringbuilder/arg/Argument.kt | 4 +- .../mattmx/ktgui/commands/stringbuilder/t.kt | 69 ++++++++++++------- .../commands/usage/CommandUsageOptions.kt | 44 ++++++++++++ 5 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/commands/usage/CommandUsageOptions.kt diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/MissingArgContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/MissingArgContext.kt index 7a2dba0..1954674 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/MissingArgContext.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/MissingArgContext.kt @@ -4,6 +4,6 @@ import com.mattmx.ktgui.commands.stringbuilder.arg.Argument import org.bukkit.command.CommandSender class MissingArgContext( - val argument: Argument<*>, + val missingArgument: Argument<*>, args: List ) : RawCommandContext(args) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt index ba65313..1b04abe 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/StringCommand.kt @@ -6,6 +6,7 @@ import com.mattmx.ktgui.commands.stringbuilder.syntax.CommandDeclarationSyntax import com.mattmx.ktgui.commands.stringbuilder.syntax.Parser import com.mattmx.ktgui.commands.stringbuilder.syntax.SubCommandDeclarationSyntax import com.mattmx.ktgui.commands.stringbuilder.syntax.VariableDeclarationSyntax +import com.mattmx.ktgui.commands.usage.CommandUsageOptions import com.mattmx.ktgui.configuration.Configuration import org.bukkit.command.CommandSender import java.util.Optional @@ -27,12 +28,19 @@ class StringCommand( for (syntax in parsed) { when (syntax) { is VariableDeclarationSyntax -> { - expectedArguments += Argument(syntax.getName(), syntax.getType(), null, !syntax.getType().isOptional) + expectedArguments += Argument( + syntax.getName(), + syntax.getType(), + null, + !syntax.getType().isOptional + ) } + is CommandDeclarationSyntax -> { // todo should be top level only name = syntax.getName() } + is SubCommandDeclarationSyntax -> { name = syntax.getName() } @@ -48,6 +56,17 @@ class StringCommand( this.runs = Optional.of(block) } + infix fun args(block: ArgumentOptions.() -> Unit) = apply { + ArgumentOptions(this).apply(block) + } + + class ArgumentOptions(private val command: StringCommand) { + operator fun String.invoke(block: Argument<*>.() -> Unit) = + command.expectedArguments.firstOrNull { it.name() == this } + ?.apply(block) + ?: error("Unregistered argument '$this'.") + } + inline operator fun String.invoke(block: StringCommand.() -> Unit) = StringCommand(this).let { // todo could also have parameters @@ -95,7 +114,9 @@ class StringCommand( val argumentValues = hashMapOf>() for ((index, arg) in expectedArguments.withIndex()) { // todo var offset for sub-commands? - val value = context.rawArgs.getOrNull(index) + val value = if (arg.type().isVararg) { + context.rawArgs.subList(index, context.rawArgs.size).joinToString(" ") + } else context.rawArgs.getOrNull(index) if (arg.isRequired() && value == null) { val missingArgContext = MissingArgContext(arg, context.rawArgs) @@ -116,27 +137,31 @@ class StringCommand( * * @return a formatted string for usage of the command */ - fun getUsage(showDescriptions: Boolean = false, maxArgumentOptionsDisplayed: Int = 5): String { - var builder = "$name " + fun getUsage(options: CommandUsageOptions = CommandUsageOptions()): String { + var builder = "${options.namePrefix}$name${options.gap}" if (subcommands.isNotEmpty()) - builder += subcommands.joinToString("|") { subcommand -> subcommand.name } + builder += subcommands.joinToString(options.subCommands.divider) { subcommand -> subcommand.name } else { var end = "" builder += expectedArguments.joinToString(" ") { arg -> - val suggestions = arg.getDefaultSuggestions()?.let { - if (it.isNotEmpty() && it.size <= maxArgumentOptionsDisplayed) " = [" + it.joinToString("|") + "]" - else ":${arg.type().typeName}" - } ?: "" + + val suggestions = + if (options.arguments.showSuggestions) { + val suggestions = arg.getDefaultSuggestions() + if (!suggestions.isNullOrEmpty()) { + val opt = options.arguments + "${opt.suggestionsChar}${opt.suggestionsPrefix}${suggestions.joinToString(opt.suggestionsDivider)}${opt.suggestionsSuffix}" + } else "${options.arguments.typeChar}${arg.type().typeName}" + } else "${options.arguments.typeChar}${arg.type().typeName}" // Apply descriptions - if (showDescriptions) { - val extra = if (arg.isRequired()) "(Required)" else "(Optional)" - end += "\n> ${arg.name()} - ${arg.description()} $extra" + if (options.arguments.showDescriptions) { + val extra = + if (arg.isRequired()) options.arguments.descriptionsRequired else options.arguments.descriptionsOptional + end += "\n${options.arguments.descriptionsPrefix}${arg.name()}${options.arguments.descriptionDivider}${arg.description()}$extra" } - if (arg.isRequired()) { - "<${arg.name()}!$suggestions>" - } else "<${arg.name()}?$suggestions>" + "${options.arguments.prefix}${arg.name()}${if (arg.isRequired()) options.arguments.required else options.arguments.optional}$suggestions${options.arguments.suffix}" } builder += end } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt index 8ffefa5..47661e8 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/Argument.kt @@ -8,7 +8,7 @@ import java.util.* class Argument( private val name: String, private val type: VariableType, - private val description: String? = null, + var description: String? = null, private val required: Boolean = true ) { private var suggests = Optional.empty<(RawCommandContext<*>) -> List?>() @@ -21,7 +21,7 @@ class Argument( fun isRequired() = required - fun withSuggestions(suggest: RawCommandContext<*>.() -> List?) = apply { + fun suggests(suggest: RawCommandContext<*>.() -> List?) = apply { this.suggests = Optional.of(suggest) } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt index df59602..8a39301 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt @@ -1,36 +1,57 @@ package com.mattmx.ktgui.commands.stringbuilder +import com.mattmx.ktgui.commands.usage.CommandUsageOptions import org.bukkit.command.CommandSender fun main() { - val testCmd = + - "/test " { - runs { - val player by argument() - val msg by argument() + val testCmd = + "/test " { + runs { + val player by argument() + val msg by argument() - println("[${player}]: $msg") + println("[${player}]: $msg") + } + } missing { + println("Missing argument '${missingArgument.name()}'") + } args { + "player" { + description = "The username of the player you wish to message. " + suggests { listOf("mattmx", "gabs") } + } + "msg" { + description = "The message to send to the player. " + } } - } missing { - println("Missing arg '${argument.name()}'") - } - "/hello" { - runs { - println("hello") - } - } + "world" { - runs { - println("hello world") - } - } + "" { - runs { - val msg by argument() - println("hello world ($msg)") - } - } +// "/hello" { +// runs { +// println("hello") +// } +// } + "world" { +// runs { +// println("hello world") +// } +// } + "" { +// runs { +// val msg by argument() +// println("hello world ($msg)") +// } +// } testCmd.invoke(RawCommandContext(listOf("mattmx", "hello", "world"))) + testCmd.invoke(RawCommandContext(listOf("1etho", "foo", "bar"))) + // Empty command invocation + testCmd.invoke(RawCommandContext(listOf())) + + println(testCmd.getUsage(CommandUsageOptions().invoke { + arguments { + prefix = "<" + suffix = " />" + showDescriptions = true + descriptionsPrefix = "[ ] " + } + })) val foo = + "/foo" { @@ -54,6 +75,4 @@ fun main() { } missing { println("missing arg") } - - println(testCmd.getUsage()) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/usage/CommandUsageOptions.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/usage/CommandUsageOptions.kt new file mode 100644 index 0000000..160e843 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/usage/CommandUsageOptions.kt @@ -0,0 +1,44 @@ +package com.mattmx.ktgui.commands.usage + +class CommandUsageOptions { + var namePrefix = "/" + var gap = " " + + val arguments = ArgumentUsageOptions() + val subCommands = SubCommandOptions() + + class ArgumentUsageOptions { + var prefix = "<" + + var typeChar = ":" + var required = "!" + var optional = "?" + + var showSuggestions = false + var maxSuggestions = 5 + var suggestionsPrefix = "[" + var suggestionsChar = "=" + var suggestionsDivider = "|" + var suggestionsSuffix = "]" + + var suffix = ">" + + var showDescriptions = false + var descriptionsPrefix = "> " + var descriptionDivider = " - " + var descriptionsRequired = "(Required)" + var descriptionsOptional = "(Optional)" + + inline operator fun invoke(block: ArgumentUsageOptions.() -> Unit) = apply(block) + } + + class SubCommandOptions { + var prefix = "" + var divider = "|" + var suffix = "" + + inline operator fun invoke(block: SubCommandOptions.() -> Unit) = apply(block) + } + + inline operator fun invoke(block: CommandUsageOptions.() -> Unit) = apply(block) +} \ No newline at end of file From 9f80711d9db7929a90b092f5ea3f99d7993e0eba Mon Sep 17 00:00:00 2001 From: MattMXX Date: Wed, 14 Feb 2024 01:03:31 +0000 Subject: [PATCH 12/25] Adding additional java support. --- .../ktgui/commands/SimpleCommandBuilder.kt | 3 + .../stringbuilder/RunnableCommandContext.kt | 5 ++ .../stringbuilder/arg/ArgumentContext.kt | 4 +- .../mattmx/ktgui/components/ClickCallback.kt | 2 +- .../ktgui/components/button/GuiButton.kt | 45 ++++++++++--- .../components/screen/GuiMultiPageScreen.kt | 3 +- .../ktgui/components/screen/GuiScreen.kt | 34 +++++++--- .../ktgui/components/screen/IGuiScreen.kt | 2 +- .../ktgui/components/signal/GuiSignalOwner.kt | 2 +- .../mattmx/ktgui/components/signal/Signal.kt | 8 ++- .../ktgui/components/signal/SignalOwner.kt | 2 +- .../ktgui/components/signal/extension.kt | 4 +- .../mattmx/ktgui/utils/JavaCompatibility.kt | 3 + .../mattmx/ktgui/examples/JavaGuiExample.java | 12 +++- .../examples/JavaUpdateCommandExample.java | 32 +++++++++ .../ktgui/examples/JavaUpdateExample.java | 66 +++++++++++++++++++ .../main/kotlin/com/mattmx/ktgui/KotlinGui.kt | 4 +- 17 files changed, 199 insertions(+), 32 deletions(-) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt create mode 100644 plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateCommandExample.java create mode 100644 plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/SimpleCommandBuilder.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/SimpleCommandBuilder.kt index 7ed0c9d..fe2e58d 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/SimpleCommandBuilder.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/SimpleCommandBuilder.kt @@ -32,6 +32,9 @@ open class SimpleCommandBuilder( var noPermissions: (CommandInvocation.() -> Unit)? = null private set + constructor(name: String) : this(name, null) + constructor(name: String, vararg alias: String) : this(name, null, *alias) + infix fun permission(permission: String) : SimpleCommandBuilder { this.permission = permission return this diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RunnableCommandContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RunnableCommandContext.kt index 7a10554..fb4bdee 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RunnableCommandContext.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/RunnableCommandContext.kt @@ -1,6 +1,7 @@ package com.mattmx.ktgui.commands.stringbuilder import com.mattmx.ktgui.commands.stringbuilder.arg.ArgumentContext +import com.mattmx.ktgui.utils.JavaCompatibility import org.bukkit.command.CommandSender import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -11,7 +12,11 @@ class RunnableCommandContext( ) : RawCommandContext(rawArgs) { fun argument() = ReadOnlyProperty { owner: Nothing?, prop: KProperty<*> -> + // todo might be optional providedArgs[prop.name] ?: error("Unknown argument '${prop.name}'") } + @JavaCompatibility + fun getArgument(name: String) = providedArgs[name]?.let { it as ArgumentContext } + } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt index fd613b4..123f3fa 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/arg/ArgumentContext.kt @@ -10,7 +10,9 @@ class ArgumentContext( fun isEmpty() = value.isEmpty - fun getOrNull() = value.orElse(null) + fun getOrNull(): T? = value.orElse(null) + + fun optional() = value override fun toString() = getOrNull().toString() } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/ClickCallback.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/ClickCallback.kt index bdead4f..602ab2e 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/ClickCallback.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/ClickCallback.kt @@ -71,7 +71,7 @@ class ClickCallback> { * @param clickType click types to handle * @param callback callback for when clicked */ - fun handleClicks(vararg clickType: ClickType, callback: ButtonClickedEvent.() -> Unit) { + fun handleClicks(callback: ButtonClickedEvent.() -> Unit, vararg clickType: ClickType, ) { callbacks[clickType.asList().toTypedArray()] = callback } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/button/GuiButton.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/button/GuiButton.kt index c972e97..4942111 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/button/GuiButton.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/button/GuiButton.kt @@ -5,18 +5,27 @@ import com.mattmx.ktgui.components.ClickCallback import com.mattmx.ktgui.components.screen.IGuiScreen import com.mattmx.ktgui.extensions.setEnchantments import com.mattmx.ktgui.item.DslIBuilder +import com.mattmx.ktgui.utils.JavaCompatibility import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.TextDecoration import org.bukkit.Material import org.bukkit.enchantments.Enchantment import org.bukkit.entity.Player +import org.bukkit.event.inventory.ClickType import org.bukkit.event.inventory.InventoryDragEvent import org.bukkit.inventory.ItemStack +import java.lang.StringBuilder +import java.util.StringJoiner +import java.util.function.Consumer -open class GuiButton>( +open class GuiButton>( material: Material = Material.STONE, var item: ItemStack? = ItemStack(material) ) : IGuiButton { + + constructor(material: Material) : this(material, null) + constructor(item: ItemStack) : this(item.type, item) + lateinit var parent: IGuiScreen protected set @@ -38,12 +47,18 @@ open class GuiButton>( if (item == null) item = ItemStack(material) } - open fun lore(lore: MutableList.() -> Unit) : T { - item?.itemMeta?.let { - val newLore = mutableListOf() - lore.invoke(newLore) + open fun lore(block: MutableList.() -> Unit) : T { + item?.editMeta { + val newLore = mutableListOf().apply(block) it.lore(newLore.map { line -> Component.empty().decoration(TextDecoration.ITALIC, false).append(line) }) - item?.itemMeta = it + } + return this as T + } + + @JavaCompatibility + fun lore(vararg lines: Component) : T { + item?.editMeta { + it.lore(lines.map { line -> Component.empty().decoration(TextDecoration.ITALIC, false).append(line) }) } return this as T } @@ -140,17 +155,27 @@ open class GuiButton>( return item } - inline fun click(block: ClickCallback.() -> Unit) : T { + @JavaCompatibility + fun click(type: ClickType, block: Consumer>) : T { + click.handleClicks({ block.accept(this) }, type) + return this as T + } + + fun test(b: Consumer) { + + } + + inline infix fun click(block: ClickCallback.() -> Unit) : T { block.invoke(click) return this as T } - fun drag(cb: InventoryDragEvent.() -> Unit) : T { + infix fun drag(cb: InventoryDragEvent.() -> Unit) : T { dragCallback = cb return this as T } - inline fun enchant(ce: MutableMap.() -> Unit) : T { + inline infix fun enchant(ce: MutableMap.() -> Unit) : T { val enchantments = item?.itemMeta?.enchants?.toMutableMap() ?: mutableMapOf() ce.invoke(enchantments) val itemMeta = item?.itemMeta @@ -180,7 +205,7 @@ open class GuiButton>( return slots?.toMutableList() ?: parent.getSlots(this) } - fun update(player: Player) : T { + infix fun update(player: Player) : T { val itemStack = formatIntoItemStack(player) // get all slots that this item exists in // update every slot to this new [ItemStack] diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiMultiPageScreen.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiMultiPageScreen.kt index c800c8a..d5a147f 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiMultiPageScreen.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiMultiPageScreen.kt @@ -1,7 +1,6 @@ package com.mattmx.ktgui.components.screen import com.mattmx.ktgui.components.button.GuiButton -import com.mattmx.ktgui.components.button.IGuiButton import net.kyori.adventure.text.Component import org.bukkit.entity.Player @@ -80,7 +79,7 @@ open class GuiMultiPageScreen( screen.itemList = itemList.map { it.copy(screen) }.toMutableList() as ArrayList> screen.type = type screen.rows = rows - screen.clickCallback = clickCallback + screen.click = click screen.moveCallback = moveCallback screen.closeCallback = closeCallback screen.quitCallback = quitCallback diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt index 4f20627..7b56bfe 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt @@ -7,10 +7,12 @@ import com.mattmx.ktgui.components.button.ButtonClickedEvent import com.mattmx.ktgui.components.button.GuiButton import com.mattmx.ktgui.components.button.IGuiButton import com.mattmx.ktgui.components.signal.GuiSignalOwner +import com.mattmx.ktgui.components.signal.Signal import com.mattmx.ktgui.event.PreGuiBuildEvent import com.mattmx.ktgui.event.PreGuiOpenEvent import com.mattmx.ktgui.extensions.setOpenGui import com.mattmx.ktgui.scheduling.isAsync +import com.mattmx.ktgui.utils.JavaCompatibility import com.mattmx.ktgui.utils.legacy import net.kyori.adventure.text.Component import org.bukkit.Bukkit @@ -25,14 +27,19 @@ import org.bukkit.inventory.Inventory import org.bukkit.inventory.ItemStack import java.lang.Integer.max import java.lang.Integer.min -import java.util.UUID +import java.util.* import java.util.concurrent.Future +import kotlin.collections.HashMap open class GuiScreen( title: Component = Component.empty(), var rows: Int = 1, var type: InventoryType? = null ) : IGuiScreen, GuiSignalOwner> { + + constructor(title: Component, rows: Int) : this(title, rows, null) + constructor(title: Component, type: InventoryType) : this(title, 0, type) + var title: Component = title set(value) { field = value @@ -46,7 +53,8 @@ open class GuiScreen( var items = hashMapOf>() override var currentlyProcessing: EffectBlock? = null - protected lateinit var clickCallback: ClickCallback<*> + var click = ClickCallback>() + private set protected lateinit var closeCallback: (InventoryCloseEvent) -> Unit protected lateinit var quitCallback: (PlayerQuitEvent) -> Unit protected lateinit var moveCallback: (PlayerMoveEvent) -> Unit @@ -173,7 +181,7 @@ open class GuiScreen( screen.items = items.mapValues { it.value.copy(screen) }.toMutableMap() as HashMap> screen.type = type screen.rows = rows - screen.clickCallback = clickCallback + screen.click = click screen.closeCallback = closeCallback screen.moveCallback = moveCallback screen.quitCallback = quitCallback @@ -190,7 +198,7 @@ open class GuiScreen( fun open(openCallback: (Player) -> Unit) = apply { this.openCallback = openCallback } infix fun click(clickCallbackBuilder: (ClickCallback<*>) -> Unit) = apply { - this.clickCallback = ClickCallback>().apply(clickCallbackBuilder) + this.click.apply(clickCallbackBuilder) } fun addEffect(effect: EffectBlock) { @@ -199,7 +207,7 @@ open class GuiScreen( currentlyProcessing = null } - override fun addChild(child: IGuiButton<*>) { + override fun addChild(child: IGuiButton<*>) = apply { child.slots()?.forEach { items[it] = child as GuiButton<*> } @@ -211,10 +219,12 @@ open class GuiScreen( val event = ButtonClickedEvent>(e.whoClicked as Player, e) if (button != null) event.button = button - if (::clickCallback.isInitialized) { - clickCallback.run(event) + + click.run(event) + + if (event.shouldContinueCallback()) { + button?.onButtonClick(event) } - button?.onButtonClick(event) } override fun drag(e: InventoryDragEvent) { @@ -236,6 +246,14 @@ open class GuiScreen( return 0 } + @JavaCompatibility + infix fun createSignal(initial: S) = Signal(initial, this) + + @JavaCompatibility + infix fun effect(block: Runnable) = apply { + EffectBlock(this) { block.run() }.apply { addEffect(this) } + } + override fun close(e: InventoryCloseEvent) { if (::closeCallback.isInitialized) closeCallback(e) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/IGuiScreen.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/IGuiScreen.kt index b393592..4d0d5f1 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/IGuiScreen.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/IGuiScreen.kt @@ -24,7 +24,7 @@ interface IGuiScreen { fun destroy() {} - fun addChild(child: IGuiButton<*>) + fun addChild(child: IGuiButton<*>) : IGuiScreen /** * Will be called if a player with this gui diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/signal/GuiSignalOwner.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/signal/GuiSignalOwner.kt index 1769a98..b54c9de 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/signal/GuiSignalOwner.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/signal/GuiSignalOwner.kt @@ -3,7 +3,7 @@ package com.mattmx.ktgui.components.signal interface GuiSignalOwner> : SignalOwner { var currentlyProcessing: T? - override fun addDependency(signal: Signal) { + override fun addDependency(signal: Signal) { if (currentlyProcessing == null) return signal.addDependency(currentlyProcessing!! as SignalListener) } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/signal/Signal.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/signal/Signal.kt index f594870..dd5bbae 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/signal/Signal.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/signal/Signal.kt @@ -19,14 +19,14 @@ import kotlin.reflect.KProperty * @param initial value of the [Signal] * @param owner owner object of this [Signal] */ -class Signal(initial: V, private val owner: SignalOwner) : ReadWriteProperty { +class Signal(initial: V, private val owner: SignalOwner) : ReadWriteProperty { private var value: V = initial private val listeners = arrayListOf>() /** * Returns current value of [value], will also call [GuiSignalOwner.addDependency]. */ - override fun getValue(thisRef: T, property: KProperty<*>): V { + override fun getValue(thisRef: Nothing?, property: KProperty<*>): V { owner.addDependency(this) return this.value } @@ -34,7 +34,7 @@ class Signal(initial: V, private val owner: SignalOwner) : ReadWriteProper /** * Sets the value of [value] */ - override fun setValue(thisRef: T, property: KProperty<*>, value: V) { + override fun setValue(thisRef: Nothing?, property: KProperty<*>, value: V) { this.value = value invokeListeners() } @@ -48,6 +48,8 @@ class Signal(initial: V, private val owner: SignalOwner) : ReadWriteProper owner.addDependency(this@Signal) } + fun get() = invoke() + /** * Set the value of [value]. * Updates all [listeners]. diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/signal/SignalOwner.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/signal/SignalOwner.kt index f035bb0..31c0df4 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/signal/SignalOwner.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/signal/SignalOwner.kt @@ -1,5 +1,5 @@ package com.mattmx.ktgui.components.signal interface SignalOwner { - fun addDependency(signal: Signal) + fun addDependency(signal: Signal) } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/signal/extension.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/signal/extension.kt index ec4eda2..6f9585a 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/signal/extension.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/signal/extension.kt @@ -8,5 +8,5 @@ import com.mattmx.ktgui.components.screen.GuiScreen * @param value initial value for the [Signal] * @return the [Signal] object */ -fun GuiScreen.signal(value: V) = - Signal(value, this) \ No newline at end of file +fun GuiScreen.signal(value: V) = + Signal(value, this) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt b/api/src/main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt new file mode 100644 index 0000000..f70af68 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt @@ -0,0 +1,3 @@ +package com.mattmx.ktgui.utils + +annotation class JavaCompatibility() diff --git a/plugin/src/main/java/com/mattmx/ktgui/examples/JavaGuiExample.java b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaGuiExample.java index 9c816a9..57fd5db 100644 --- a/plugin/src/main/java/com/mattmx/ktgui/examples/JavaGuiExample.java +++ b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaGuiExample.java @@ -4,12 +4,16 @@ import com.mattmx.ktgui.components.button.GuiToggleButton; import com.mattmx.ktgui.components.screen.GuiScreen; import org.bukkit.Material; +import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; +import org.jetbrains.annotations.NotNull; import static com.mattmx.ktgui.item.ItemBuilderDslKt.itemBuilder; import static com.mattmx.ktgui.utils.ColorKt.component; -public class JavaGuiExample extends GuiScreen { +public class JavaGuiExample extends GuiScreen implements Example { + + @SuppressWarnings({"rawtypes"}) public JavaGuiExample() { super(component("f52ffJava Example GUI"), 3, null); new GuiButton() @@ -31,4 +35,10 @@ public JavaGuiExample() { return null; }).slot(22).childOf(this); } + + + @Override + public void run(@NotNull Player player) { + open(player); + } } diff --git a/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateCommandExample.java b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateCommandExample.java new file mode 100644 index 0000000..108d366 --- /dev/null +++ b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateCommandExample.java @@ -0,0 +1,32 @@ +package com.mattmx.ktgui.examples; + +import com.mattmx.ktgui.commands.SimpleCommandBuilder; +import com.mattmx.ktgui.commands.stringbuilder.StringCommand; +import com.mattmx.ktgui.commands.stringbuilder.arg.ArgumentContext; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; + +public class JavaUpdateCommandExample { + private void test() { + new SimpleCommandBuilder("mattmx") + .permission("ktgui.command.mattmx") + .executes((invocation) -> { + invocation.player().sendMessage(Component.text("Command ran")); + return null; + }) + .register(false); + + new StringCommand("/hello ") + .runs((context) -> { + ArgumentContext arg = context.getArgument("arg"); + + if (arg == null) { + System.out.println("arg was null"); + return null; + } + + System.out.println(arg.getOrNull()); + return null; + }); + } +} diff --git a/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java new file mode 100644 index 0000000..7fdc39c --- /dev/null +++ b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java @@ -0,0 +1,66 @@ +package com.mattmx.ktgui.examples; + +import com.mattmx.ktgui.components.GuiPattern; +import com.mattmx.ktgui.components.button.ButtonClickedEvent; +import com.mattmx.ktgui.components.button.GuiButton; +import com.mattmx.ktgui.components.screen.GuiScreen; +import com.mattmx.ktgui.components.signal.Signal; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.jetbrains.annotations.NotNull; + +public class JavaUpdateExample implements Example { + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public void run(@NotNull Player player) { + GuiScreen gui = new GuiScreen(Component.text("Title"), 3) + .addChild( + new GuiButton(Material.DIRT) + .named(Component.text("Item")) + .click(ClickType.LEFT, (event) -> { + // todo find a way to reduce this cast, for some reason `event` is `Object`, Kotlin -> Java issue + ((ButtonClickedEvent) event).getPlayer().sendMessage(Component.text("clicked")); + }) + .lore(Component.text("Lore line one")) + .lore(Component.text("Lore line two")) + .slot(3) + ); + + // Test Patterns + GuiPattern pattern = new GuiPattern(""" + --------- + --------- + --------x + """); + pattern.set('x', + new GuiButton(Material.SPECTRAL_ARROW) + .named(Component.text("Close")) + .click(ClickType.LEFT, (event) -> { + gui.forceClose(((ButtonClickedEvent) event).getPlayer()); + }) + ); + pattern.apply(gui); + + // Test Signals + Signal signalExample = gui.createSignal("mattmx"); + + gui.effect(() -> new GuiButton(Material.DIAMOND) + .named(Component.text(signalExample.get())) + .slot(5) + .childOf(gui) + ); + + // Test Callbacks + gui.open((p) -> { + p.sendMessage(Component.text("opened.")); + return null; + }).close((e) -> { + e.getPlayer().sendMessage(Component.text("closed.")); + return null; + }); + + gui.open(player); + } +} diff --git a/plugin/src/main/kotlin/com/mattmx/ktgui/KotlinGui.kt b/plugin/src/main/kotlin/com/mattmx/ktgui/KotlinGui.kt index 703d85b..08c8005 100644 --- a/plugin/src/main/kotlin/com/mattmx/ktgui/KotlinGui.kt +++ b/plugin/src/main/kotlin/com/mattmx/ktgui/KotlinGui.kt @@ -35,7 +35,9 @@ class KotlinGui : JavaPlugin() { "counter" to { TitleCounterExample() }, "signals" to { SignalsExample() }, "signals-list" to { SignalsListExample() }, - "hook" to { GuiHookExample() } + "hook" to { GuiHookExample() }, + "java-simple" to { JavaGuiExample() }, + "java-new" to { JavaUpdateExample() } ) GuiHookExample.registerListener(this) From a8b87449982a2785b5305a3283b84341f786842b Mon Sep 17 00:00:00 2001 From: MattMX <39436418+Matt-MX@users.noreply.github.com> Date: Wed, 14 Feb 2024 01:04:32 +0000 Subject: [PATCH 13/25] Update test-build.yml --- .github/workflows/test-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 69e5c06..6692ca2 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -1,7 +1,7 @@ on: push: branches: - - "refactor" + - "dev" jobs: test-build-gradle-project: From f07a8f79b81621bd48ef23ec58019fee11551e31 Mon Sep 17 00:00:00 2001 From: MattMXX Date: Wed, 14 Feb 2024 01:09:18 +0000 Subject: [PATCH 14/25] remove conflicting `effect` method name --- .../kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt | 2 +- .../main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt | 4 ++++ .../java/com/mattmx/ktgui/examples/JavaUpdateExample.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt index 7b56bfe..2ac2b33 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt @@ -250,7 +250,7 @@ open class GuiScreen( infix fun createSignal(initial: S) = Signal(initial, this) @JavaCompatibility - infix fun effect(block: Runnable) = apply { + infix fun effectBlock(block: Runnable) = apply { EffectBlock(this) { block.run() }.apply { addEffect(this) } } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt b/api/src/main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt index f70af68..24fda53 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/utils/JavaCompatibility.kt @@ -1,3 +1,7 @@ package com.mattmx.ktgui.utils +/** + * Marker annotation to help identify functions added + * specifically for Java compatibility. + */ annotation class JavaCompatibility() diff --git a/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java index 7fdc39c..3132ad2 100644 --- a/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java +++ b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java @@ -46,7 +46,7 @@ public void run(@NotNull Player player) { // Test Signals Signal signalExample = gui.createSignal("mattmx"); - gui.effect(() -> new GuiButton(Material.DIAMOND) + gui.effectBlock(() -> new GuiButton(Material.DIAMOND) .named(Component.text(signalExample.get())) .slot(5) .childOf(gui) From f84c7c1d8f571490f5f99faeab81073060ecb0e6 Mon Sep 17 00:00:00 2001 From: MattMXX Date: Wed, 14 Feb 2024 01:11:14 +0000 Subject: [PATCH 15/25] make click assignable to child classes --- .../main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt index 2ac2b33..d5ae294 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt @@ -54,7 +54,7 @@ open class GuiScreen( override var currentlyProcessing: EffectBlock? = null var click = ClickCallback>() - private set + protected set protected lateinit var closeCallback: (InventoryCloseEvent) -> Unit protected lateinit var quitCallback: (PlayerQuitEvent) -> Unit protected lateinit var moveCallback: (PlayerMoveEvent) -> Unit From e2329f828cfb44b03bf649e82e05e9c58631da7e Mon Sep 17 00:00:00 2001 From: MattMXX Date: Wed, 14 Feb 2024 01:11:41 +0000 Subject: [PATCH 16/25] Update SignalsListExample.kt --- .../main/kotlin/com/mattmx/ktgui/examples/SignalsListExample.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/kotlin/com/mattmx/ktgui/examples/SignalsListExample.kt b/plugin/src/main/kotlin/com/mattmx/ktgui/examples/SignalsListExample.kt index 70765a3..00c2c62 100644 --- a/plugin/src/main/kotlin/com/mattmx/ktgui/examples/SignalsListExample.kt +++ b/plugin/src/main/kotlin/com/mattmx/ktgui/examples/SignalsListExample.kt @@ -20,7 +20,7 @@ import java.util.* class SignalsListExample : Example { val gui = gui(!"Signals (List)", InventoryType.HOPPER) { // Instead of using 'by' keyword we just assign the signal - val list = signal>(arrayListOf()) + val list = signal(arrayListOf()) effect { button(Material.KNOWLEDGE_BOOK) { From af7ce0aa9b2c4b853b2a21f2f88e0593f4d881aa Mon Sep 17 00:00:00 2001 From: MattMXX Date: Wed, 14 Feb 2024 15:10:43 +0000 Subject: [PATCH 17/25] Further java support, adding numeric formatting utility functions i commonly use --- .../main/kotlin/com/mattmx/ktgui/dsl/event.kt | 57 ++++++++++-- .../ktgui/extensions/PlayerExtensions.kt | 1 + .../ktgui/scoreboards/ScoreboardBuilder.kt | 19 ++++ .../com/mattmx/ktgui/utils/formatting.kt | 86 +++++++++++++++++++ .../ktgui/examples/JavaUpdateExample.java | 54 +++++++++++- 5 files changed, 208 insertions(+), 9 deletions(-) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/utils/formatting.kt diff --git a/api/src/main/kotlin/com/mattmx/ktgui/dsl/event.kt b/api/src/main/kotlin/com/mattmx/ktgui/dsl/event.kt index fc51274..df62140 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/dsl/event.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/dsl/event.kt @@ -1,13 +1,13 @@ package com.mattmx.ktgui.dsl +import com.mattmx.ktgui.utils.JavaCompatibility import org.bukkit.Bukkit import org.bukkit.event.Event import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.plugin.java.JavaPlugin -import kotlin.reflect.KClass -class KListener : Listener +class KListener : Listener @JvmName("event1") inline fun JavaPlugin.event( @@ -15,7 +15,7 @@ inline fun JavaPlugin.event( ignoreCancelled: Boolean = false, noinline block: T.() -> Unit ) { - val listener = KListener() + val listener = KListener() listener.event(this, priority, ignoreCancelled, block) } inline fun event( @@ -24,32 +24,73 @@ inline fun event( ignoreCancelled: Boolean = false, noinline block: T.() -> Unit ) { - val listener = KListener() + val listener = KListener() listener.event(plugin, priority, ignoreCancelled, block) } +@JavaCompatibility +fun javaEvent( + plugin: JavaPlugin, + clazz: Class, + block: T.() -> Unit +) { + javaEvent(plugin, clazz, EventPriority.NORMAL, false, block) +} + +@JavaCompatibility +fun javaEvent( + plugin: JavaPlugin, + clazz: Class, + priority: EventPriority, + block: T.() -> Unit +) { + javaEvent(plugin, clazz, priority, false, block) +} + +@JavaCompatibility +fun javaEvent( + plugin: JavaPlugin, + clazz: Class, + ignoreCancelled: Boolean, + block: T.() -> Unit +) { + javaEvent(plugin, clazz, EventPriority.NORMAL, ignoreCancelled, block) +} + +@JavaCompatibility +fun javaEvent( + plugin: JavaPlugin, + clazz: Class, + priority: EventPriority = EventPriority.NORMAL, + ignoreCancelled: Boolean = false, + block: T.() -> Unit +) { + val listener = KListener() + listener.event(plugin, clazz, priority, ignoreCancelled, block) +} + inline fun Listener.event( plugin: JavaPlugin, priority: EventPriority = EventPriority.NORMAL, ignoreCancelled: Boolean = false, noinline block: T.() -> Unit ) { - event(plugin, T::class, priority, ignoreCancelled, block) + event(plugin, T::class.java, priority, ignoreCancelled, block) } inline fun Listener.event( plugin: JavaPlugin, - type: KClass, + type: Class, priority: EventPriority = EventPriority.NORMAL, ignoreCancelled: Boolean = false, crossinline callback: T.() -> Unit ) { Bukkit.getServer().pluginManager.registerEvent( - type.java, + type, this, priority, { _, event -> if(type.isInstance(event)) callback(event as T) }, plugin, ignoreCancelled ) -} +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/extensions/PlayerExtensions.kt b/api/src/main/kotlin/com/mattmx/ktgui/extensions/PlayerExtensions.kt index caec6ef..0ca2af4 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/extensions/PlayerExtensions.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/extensions/PlayerExtensions.kt @@ -2,6 +2,7 @@ package com.mattmx.ktgui.extensions import com.mattmx.ktgui.GuiManager import com.mattmx.ktgui.components.screen.IGuiScreen +import com.mattmx.ktgui.utils.JavaCompatibility import org.bukkit.Bukkit import org.bukkit.entity.Player diff --git a/api/src/main/kotlin/com/mattmx/ktgui/scoreboards/ScoreboardBuilder.kt b/api/src/main/kotlin/com/mattmx/ktgui/scoreboards/ScoreboardBuilder.kt index 0674e0e..6f5ee9e 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/scoreboards/ScoreboardBuilder.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/scoreboards/ScoreboardBuilder.kt @@ -1,7 +1,9 @@ package com.mattmx.ktgui.scoreboards +import com.mattmx.ktgui.extensions.removeScoreboard import com.mattmx.ktgui.utils.legacyColor import com.mattmx.ktgui.scheduling.not +import com.mattmx.ktgui.utils.JavaCompatibility import com.mattmx.ktgui.utils.not import net.kyori.adventure.text.Component import org.bukkit.Bukkit @@ -22,6 +24,8 @@ open class ScoreboardBuilder( field = value } + constructor(title: Component) : this(title, UUID.randomUUID().toString().replace("-", "")) + // Holds the lines of text, we are able to remove them because of this private val scoreboardLines = arrayListOf() @@ -35,6 +39,18 @@ open class ScoreboardBuilder( */ operator fun Component.unaryPlus() = add(this) + /** + * Adds a line and returns itself, to be used as a builder for + * java compatibility. + * + * @param line line of text to add. + * @return this + */ + @JavaCompatibility + fun addLine(line: Component) = apply { + add(line) + } + /** * Append a new line to the scoreboard. * @@ -97,6 +113,9 @@ open class ScoreboardBuilder( fun showFor(player: Player) { player.scoreboard = scoreboard } + fun removeFor(player: Player) { + player.removeScoreboard() + } } fun scoreboard(title: Component, block: ScoreboardBuilder.() -> Unit) = ScoreboardBuilder(title).apply(block) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/utils/formatting.kt b/api/src/main/kotlin/com/mattmx/ktgui/utils/formatting.kt new file mode 100644 index 0000000..3257243 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/utils/formatting.kt @@ -0,0 +1,86 @@ +package com.mattmx.ktgui.utils + +import java.math.RoundingMode +import java.text.DecimalFormat +import java.time.Duration +import kotlin.math.floor +import kotlin.math.log10 +import kotlin.math.pow + +/** + * Format a [Duration] into a pretty string. + * + * `e.g "2d 1h 30m 1s"` + * + * @return formatted string + */ +fun Duration.pretty(): String { + val days = toDaysPart() + val hours = toHoursPart() + val minutes = toMinutesPart() + val seconds = toSecondsPart() + return ( + (if (days > 0) "${days}d " else "") + + (if (hours > 0) "${hours}h " else "") + + (if (minutes > 0) "${minutes}m " else "") + + "${seconds}s" + ) +} + +/** + * Round a [Double] to a decimal place. + * + * @param decimalPlace number of following digits. + * @return rounded [Double] + */ +fun Double.dp(decimalPlace: Int = 2): Double { + val df = DecimalFormat("#." + "#".repeat(decimalPlace)) + df.roundingMode = RoundingMode.CEILING + + return df.format(this).toDouble() +} + +/** + * Round a [Float] to a decimal place. + * + * @param decimalPlace number of following digits. + * @return rounded [Float] + */ +fun Float.dp(decimalPlace: Int = 2) : Float { + val df = DecimalFormat("#." + "#".repeat(decimalPlace)) + df.roundingMode = RoundingMode.CEILING + + return df.format(this).toFloat() +} + +/** + * Format larger numbers to be shorter. + * + * `e.g 142332 -> 142.3k` + * + * @return formatted number. + */ +fun Number.pretty(): String { + val suffix = charArrayOf(' ', 'k', 'M', 'B', 'T', 'P', 'E') + val numValue = this.toLong() + val value = floor(log10(numValue.toDouble())).toInt() + val base = value / 3 + return if (value >= 3 && base < suffix.size) { + DecimalFormat("#0.0") + .apply { roundingMode = RoundingMode.FLOOR } + .format(numValue / 10.toDouble().pow(base * 3)) + suffix[base] + } else { + DecimalFormat("#,##0") + .apply { roundingMode = RoundingMode.FLOOR } + .format(numValue) + } +} + +/** + * Insert commas every 3 digits. + * + * `e.g 142332 -> 142,332` + * + * @return formatted [Int] as a [String] + */ +fun Int.commas() = "%,d".format(this) \ No newline at end of file diff --git a/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java index 3132ad2..eaf455e 100644 --- a/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java +++ b/plugin/src/main/java/com/mattmx/ktgui/examples/JavaUpdateExample.java @@ -1,16 +1,25 @@ package com.mattmx.ktgui.examples; +import com.mattmx.ktgui.GuiManager; import com.mattmx.ktgui.components.GuiPattern; import com.mattmx.ktgui.components.button.ButtonClickedEvent; import com.mattmx.ktgui.components.button.GuiButton; import com.mattmx.ktgui.components.screen.GuiScreen; import com.mattmx.ktgui.components.signal.Signal; +import com.mattmx.ktgui.event.PreGuiBuildEvent; +import com.mattmx.ktgui.scheduling.TaskTracker; +import com.mattmx.ktgui.scoreboards.ScoreboardBuilder; import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import static com.mattmx.ktgui.dsl.EventKt.javaEvent; + public class JavaUpdateExample implements Example { @Override @SuppressWarnings({"unchecked", "rawtypes"}) @@ -44,10 +53,11 @@ public void run(@NotNull Player player) { pattern.apply(gui); // Test Signals - Signal signalExample = gui.createSignal("mattmx"); + Signal signalExample = gui.createSignal("foo"); gui.effectBlock(() -> new GuiButton(Material.DIAMOND) .named(Component.text(signalExample.get())) + .click(ClickType.LEFT, (event) -> signalExample.setTo(signalExample.get().equals("foo") ? "bar" : "foo")) .slot(5) .childOf(gui) ); @@ -63,4 +73,46 @@ public void run(@NotNull Player player) { gui.open(player); } + + public void eventsTest(JavaPlugin plugin) { + javaEvent(plugin, PlayerJoinEvent.class, (event) -> { + event.getPlayer().sendMessage(Component.text("welcome!")); + return null; + }); + } + + public void scoreboard(Player player) { + ScoreboardBuilder builder = new ScoreboardBuilder(Component.text("Scoreboard test")) + .addLine(Component.text("Test")) + .addLine(Component.text("two")); + + Bukkit.getScheduler().runTaskLater(GuiManager.owningPlugin, () -> { + builder.set(0, Component.text("5 seconds have passed")); + }, 20 * 5L); + + builder.showFor(player); + } + + public void hookTest(PreGuiBuildEvent event) { + if (!(event.getGui() instanceof GuiScreen)) return; + GuiScreen gui = (GuiScreen) event.getGui(); + if (!gui.getId().equals("ktgui.example.hook")) return; + + // todo add stuff + } + + public void testTaskTracker() { + TaskTracker tracker = new TaskTracker(); + + tracker.runAsyncLater(20L, (task) -> { + System.out.println("1 second passed"); + tracker.cancelAll(); + return null; + }); + + tracker.runAsyncLater(40L, (task) -> { + System.out.println("Will not execute."); + return null; + }); + } } From bfaed7b6e74a5154489fb4767c674908f89f2903 Mon Sep 17 00:00:00 2001 From: MattMXX Date: Wed, 14 Feb 2024 23:57:38 +0000 Subject: [PATCH 18/25] Created new adaptable conversation api design --- .../kotlin/com/mattmx/ktgui/GuiManager.kt | 1 + .../refactor/ConversationWrapper.kt | 74 ++++++++++++++ .../conversation/refactor/conversation.kt | 36 +++++++ .../refactor/result/ConversationEnd.kt | 14 +++ .../refactor/result/ConversationResult.kt | 10 ++ .../refactor/steps/ChoiceConversationStep.kt | 15 +++ .../refactor/steps/EnumConversationStep.kt | 17 ++++ .../refactor/steps/NumberConversationStep.kt | 40 ++++++++ .../refactor/steps/RawConversationStep.kt | 96 +++++++++++++++++++ .../refactor/steps/RegExpConversationStep.kt | 17 ++++ .../ktgui/conversation/refactor/steps/Step.kt | 9 ++ .../refactor/steps/StringConversationStep.kt | 7 ++ .../mattmx/ktgui/conversation/refactor/t.kt | 68 +++++++++++++ 13 files changed, 404 insertions(+) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/conversation.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationEnd.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationResult.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/ChoiceConversationStep.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/EnumConversationStep.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/NumberConversationStep.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RegExpConversationStep.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/Step.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/StringConversationStep.kt create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt diff --git a/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt b/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt index d6250c6..a1b8fa0 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt @@ -128,6 +128,7 @@ object GuiManager : Listener { gui?.let { gui.destroy() players.remove(player) + player.openInventory.close() } } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt new file mode 100644 index 0000000..60afdb1 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt @@ -0,0 +1,74 @@ +package com.mattmx.ktgui.conversation.refactor + +import com.mattmx.ktgui.GuiManager +import com.mattmx.ktgui.conversation.refactor.result.ConversationEnd +import com.mattmx.ktgui.conversation.refactor.steps.Step +import com.mattmx.ktgui.extensions.getOpenGui +import com.mattmx.ktgui.extensions.setOpenGui +import org.bukkit.conversations.Conversable +import org.bukkit.conversations.Conversation +import org.bukkit.conversations.ConversationAbandonedEvent +import org.bukkit.conversations.ConversationAbandonedListener +import org.bukkit.conversations.ConversationFactory +import org.bukkit.entity.Player +import org.bukkit.plugin.java.JavaPlugin +import java.util.* + +class ConversationWrapper( + plugin: JavaPlugin +) { + private val factory = ConversationFactory(plugin) + private val steps = arrayListOf() + private var exit = Optional.empty<(ConversationAbandonedEvent) -> Unit>() + private var start = Optional.empty<(T) -> Unit>() + var closeGuiOnStart = true + + var exitOn: String + get() = "" + set(value) { + factory.withEscapeSequence(value) + } + + infix fun exit(block: ConversationAbandonedEvent.() -> Unit) = apply { + this.exit = Optional.of(block) + factory.addConversationAbandonedListener { + block(it) + } + } + + infix fun start(block: T.() -> Unit) = apply { + this.start = Optional.of(block) + } + + fun add(step: S) = step.apply { + steps.add(this) + } + + private fun build() { + if (steps.isNotEmpty()) { + factory.withFirstPrompt(steps.first()) + } + // todo add end prompt? + + for ((index, step) in steps.withIndex()) { + val nextStep = steps.getOrNull(index + 1) + + if (nextStep != null) { + step.next(nextStep) + } + } + } + + fun begin(conversable: T): Conversation { + build() + val conversation = factory.buildConversation(conversable).apply { begin() } + + if (closeGuiOnStart && conversable is Player) { + GuiManager.forceClose(conversable) + } + + start.ifPresent { it.invoke(conversable) } + + return conversation + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/conversation.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/conversation.kt new file mode 100644 index 0000000..fad94ae --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/conversation.kt @@ -0,0 +1,36 @@ +package com.mattmx.ktgui.conversation.refactor + +import com.mattmx.ktgui.GuiManager +import com.mattmx.ktgui.conversation.refactor.steps.* +import org.bukkit.conversations.Conversable +import org.bukkit.plugin.java.JavaPlugin + +fun conversation(plugin: JavaPlugin, block: ConversationWrapper.() -> Unit) = + ConversationWrapper(plugin).apply(block) + +fun conversation(block: ConversationWrapper.() -> Unit) = + ConversationWrapper(GuiManager.owningPlugin).apply(block) + +fun ConversationWrapper.getString(block: StringConversationStep.() -> Unit) = + add(StringConversationStep().apply(block)) + +fun ConversationWrapper.getChoice(block: ChoiceConversationStep.() -> Unit) = + add(ChoiceConversationStep().apply(block)) + +inline fun , T : Conversable> ConversationWrapper.getEnum(block: EnumConversationStep.() -> Unit) = + add(EnumConversationStep(E::class.java).apply(block)) + +fun ConversationWrapper.getRegExp(block: RegExpConversationStep.() -> Unit) = + add(RegExpConversationStep().apply(block)) + +fun ConversationWrapper.getInteger(block: IntegerConversationStep.() -> Unit) = + add(IntegerConversationStep().apply(block)) + +fun ConversationWrapper.getLong(block: LongConversationStep.() -> Unit) = + add(LongConversationStep().apply(block)) + +fun ConversationWrapper.getDouble(block: DoubleConversationStep.() -> Unit) = + add(DoubleConversationStep().apply(block)) + +fun ConversationWrapper.getFloat(block: FloatConversationStep.() -> Unit) = + add(FloatConversationStep().apply(block)) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationEnd.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationEnd.kt new file mode 100644 index 0000000..c5bb12e --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationEnd.kt @@ -0,0 +1,14 @@ +package com.mattmx.ktgui.conversation.refactor.result + +import org.bukkit.conversations.Conversable + +class ConversationEnd( + val conversable: Conversable, + val reason: Reason +) { + enum class Reason { + END_ABANDON, + QUIT, + COMPLETED + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationResult.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationResult.kt new file mode 100644 index 0000000..b3f44de --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationResult.kt @@ -0,0 +1,10 @@ +package com.mattmx.ktgui.conversation.refactor.result + +import org.bukkit.conversations.ConversationContext +import java.util.* + +class ConversationResult( + val context: ConversationContext, + val conversable: C, + val result: Optional +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/ChoiceConversationStep.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/ChoiceConversationStep.kt new file mode 100644 index 0000000..6246ac6 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/ChoiceConversationStep.kt @@ -0,0 +1,15 @@ +package com.mattmx.ktgui.conversation.refactor.steps + +import org.bukkit.conversations.Conversable +import java.util.* + +class ChoiceConversationStep : RawConversationStep() { + var choices = mutableListOf() + + override fun validate(input: String?): Optional { + if (input == null) return Optional.empty() + + return Optional.ofNullable(choices.firstOrNull { it == input }) + } + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/EnumConversationStep.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/EnumConversationStep.kt new file mode 100644 index 0000000..5ff257a --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/EnumConversationStep.kt @@ -0,0 +1,17 @@ +package com.mattmx.ktgui.conversation.refactor.steps + +import org.bukkit.conversations.Conversable +import java.util.* + +class EnumConversationStep, C : Conversable>( + private val clazz: Class +) : RawConversationStep() { + + override fun validate(input: String?): Optional { + if (input == null) return Optional.empty() + + return Optional.ofNullable(clazz.enumConstants.firstOrNull { it.name == input }) + } + + enum class Empty +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/NumberConversationStep.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/NumberConversationStep.kt new file mode 100644 index 0000000..c868ccf --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/NumberConversationStep.kt @@ -0,0 +1,40 @@ +package com.mattmx.ktgui.conversation.refactor.steps + +import org.bukkit.conversations.Conversable +import java.util.* + +abstract class NumberConversationStep, C : Conversable> : RawConversationStep() { + lateinit var range: ClosedRange + + abstract fun get(str: String) : T? + + override fun validate(input: String?): Optional { + if (input == null) return Optional.empty() + + val numValue = get(input) + ?: return Optional.empty() + + if (::range.isInitialized) { + return if (numValue in range) Optional.of(numValue) else Optional.empty() + } + + return Optional.of(numValue) + } + +} + +class IntegerConversationStep : NumberConversationStep() { + override fun get(str: String) = str.toIntOrNull() +} + +class LongConversationStep : NumberConversationStep() { + override fun get(str: String) = str.toLongOrNull() +} + +class DoubleConversationStep : NumberConversationStep() { + override fun get(str: String) = str.toDoubleOrNull() +} + +class FloatConversationStep : NumberConversationStep() { + override fun get(str: String) = str.toFloatOrNull() +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt new file mode 100644 index 0000000..33bde12 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt @@ -0,0 +1,96 @@ +package com.mattmx.ktgui.conversation.refactor.steps + +import com.mattmx.ktgui.conversation.refactor.result.ConversationResult +import com.mattmx.ktgui.utils.legacy +import net.kyori.adventure.text.Component +import net.kyori.adventure.title.Title +import org.bukkit.conversations.Conversable +import org.bukkit.conversations.ConversationContext +import org.bukkit.conversations.Prompt +import org.bukkit.conversations.StringPrompt +import org.bukkit.entity.Player +import java.time.Duration +import java.util.* + +open class RawConversationStep : StringPrompt(), Step { + var next: Optional = Optional.empty() + private set + + var message: Component = Component.empty() + var messageCallback: Optional<(ConversationContext) -> Component> = Optional.empty() + var title: Component = Component.empty() + var subtitle: Component = Component.empty() + var callback: Optional<(ConversationResult) -> Unit> = Optional.empty() + var check: Optional<(ConversationResult) -> Boolean> = Optional.empty() + var invalid: Optional<(ConversationResult) -> Unit> = Optional.empty() + var repeatIfInvalid = true + + override fun getPromptText(context: ConversationContext): String { + + if (messageCallback.isPresent) { + return messageCallback.get().invoke(context).legacy() + } + + if (context.forWhom is Player) { + (context.forWhom as Player).showTitle( + Title.title( + title, + subtitle, + Title.Times.times( + Duration.ofMillis(0), + Duration.ofDays(100), + Duration.ofMillis(0) + ) + ) + ) + } + + return message.legacy() + } + + override fun acceptInput(context: ConversationContext, input: String?): Prompt? { + val validated = validate(input) + val result = ConversationResult(context, context.forWhom as C, validated) + + val isValid = validated.isPresent && check.isPresent && check.get().invoke(result) + + if (isValid) { + callback.ifPresent { + it.invoke(result) + } + } else { + invalid.ifPresent { + it.invoke(result) + } + // Repeat if this is invalid + if (repeatIfInvalid) { + return this + } + } + + return next.orElse(null) + } + + infix fun runs(block: ConversationResult.() -> Unit) = apply { + this.callback = Optional.of(block) + } + + infix fun message(block: ConversationContext.() -> Component) = apply { + this.messageCallback = Optional.of(block) + } + + infix fun matches(block: ConversationResult.() -> Boolean) = apply { + this.check = Optional.of(block) + } + + infix fun invalid(block: ConversationResult.() -> Unit) = apply { + this.callback = Optional.of(block) + } + + open fun validate(input: String?): Optional = Optional.ofNullable(input as T?) + + override fun next(step: Step) { + this.next = Optional.of(step) + } + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RegExpConversationStep.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RegExpConversationStep.kt new file mode 100644 index 0000000..31f52b6 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RegExpConversationStep.kt @@ -0,0 +1,17 @@ +package com.mattmx.ktgui.conversation.refactor.steps + +import org.bukkit.conversations.Conversable +import java.util.* + +class RegExpConversationStep : StringConversationStep() { + lateinit var regex: Regex + + override fun validate(input: String?): Optional { + if (input == null) return Optional.empty() + + return if (::regex.isInitialized) { + if (regex.matches(input)) Optional.of(input) else Optional.empty() + } else Optional.empty() + } + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/Step.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/Step.kt new file mode 100644 index 0000000..c4cf325 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/Step.kt @@ -0,0 +1,9 @@ +package com.mattmx.ktgui.conversation.refactor.steps + +import org.bukkit.conversations.Prompt + +interface Step : Prompt { + + fun next(step: Step) + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/StringConversationStep.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/StringConversationStep.kt new file mode 100644 index 0000000..603f525 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/StringConversationStep.kt @@ -0,0 +1,7 @@ +package com.mattmx.ktgui.conversation.refactor.steps + +import org.bukkit.conversations.Conversable + +open class StringConversationStep : RawConversationStep() { + +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt new file mode 100644 index 0000000..5282216 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt @@ -0,0 +1,68 @@ +package com.mattmx.ktgui.conversation.refactor + +import com.mattmx.ktgui.conversation.refactor.steps.EnumConversationStep +import com.mattmx.ktgui.utils.not +import org.bukkit.Bukkit +import org.bukkit.entity.Player + +fun main() { + conversation { + exitOn = "cancel" + + exit { + println("exit") + } + + start { + println("start") + } + + getEnum { + runs { + println(result.orElse(null)) + } invalid { + println("${result.get()} is not a valid choice.") + } + } + + getChoice { + choices = mutableListOf("yes", "no") + runs { + println(result.orElse(null)) + } invalid { + println("${result.get()} is not a valid choice.") + } + } + + getString { + message = !""" + &7Please input a player's username. + """.trimIndent() + + matches { + Bukkit.getOnlinePlayers() + .firstOrNull { it.name == result.orElse(null) } != null + } invalid { + println("not a valid username '${result.get()}'") + } runs { + println(result.get()) + } + } + + getRegExp { + regex = "[A-Za-z0-9_]{3,16}".toRegex() + runs { + println(result.get()) + } invalid { + println("${result.get()} doesn't match") + } + } + + getInteger { + range = (1..20) + runs { + println(result.get() * 2) + } + } + } +} \ No newline at end of file From 0633569d14ef2deae4a729db851b5c2d5eb6df1e Mon Sep 17 00:00:00 2001 From: MattMXX Date: Thu, 15 Feb 2024 00:12:05 +0000 Subject: [PATCH 19/25] #19 Can now access `Conversation` object from `ConversationResult` callbacks --- .../refactor/ConversationWrapper.kt | 33 ++++++++++++++++++- .../refactor/result/ConversationResult.kt | 2 ++ .../refactor/steps/RawConversationStep.kt | 7 +++- .../mattmx/ktgui/conversation/refactor/t.kt | 3 +- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt index 60afdb1..e1e6a6a 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt @@ -9,15 +9,20 @@ import org.bukkit.conversations.Conversable import org.bukkit.conversations.Conversation import org.bukkit.conversations.ConversationAbandonedEvent import org.bukkit.conversations.ConversationAbandonedListener +import org.bukkit.conversations.ConversationContext import org.bukkit.conversations.ConversationFactory +import org.bukkit.conversations.ConversationPrefix import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin +import java.time.Duration import java.util.* class ConversationWrapper( plugin: JavaPlugin ) { - private val factory = ConversationFactory(plugin) + val factory = ConversationFactory(plugin) + .withModality(true) + .withLocalEcho(false) private val steps = arrayListOf() private var exit = Optional.empty<(ConversationAbandonedEvent) -> Unit>() private var start = Optional.empty<(T) -> Unit>() @@ -33,9 +38,26 @@ class ConversationWrapper( this.exit = Optional.of(block) factory.addConversationAbandonedListener { block(it) + ongoingConversations.remove(it.context) } } + infix fun timeout(duration: Duration) = apply { + factory.withTimeout(duration.toSeconds().toInt()) + } + + infix fun timeout(seconds: Int) = apply { + factory.withTimeout(seconds) + } + + infix fun prefix(prefix: ConversationPrefix) = apply { + factory.withPrefix(prefix) + } + + infix fun initialSessionData(data: Map) = apply { + factory.withInitialSessionData(data) + } + infix fun start(block: T.() -> Unit) = apply { this.start = Optional.of(block) } @@ -69,6 +91,15 @@ class ConversationWrapper( start.ifPresent { it.invoke(conversable) } + ongoingConversations[conversation.context] = conversation + return conversation } + + companion object { + // todo need to remove on any sort of ending! + private val ongoingConversations = hashMapOf() + + fun getConversation(context: ConversationContext) = ongoingConversations[context] + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationResult.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationResult.kt index b3f44de..cde0d2e 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationResult.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/result/ConversationResult.kt @@ -1,10 +1,12 @@ package com.mattmx.ktgui.conversation.refactor.result +import org.bukkit.conversations.Conversation import org.bukkit.conversations.ConversationContext import java.util.* class ConversationResult( val context: ConversationContext, + val conversation: Conversation, val conversable: C, val result: Optional ) \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt index 33bde12..d974f06 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt @@ -1,5 +1,6 @@ package com.mattmx.ktgui.conversation.refactor.steps +import com.mattmx.ktgui.conversation.refactor.ConversationWrapper import com.mattmx.ktgui.conversation.refactor.result.ConversationResult import com.mattmx.ktgui.utils.legacy import net.kyori.adventure.text.Component @@ -50,7 +51,11 @@ open class RawConversationStep : StringPrompt(), Step override fun acceptInput(context: ConversationContext, input: String?): Prompt? { val validated = validate(input) - val result = ConversationResult(context, context.forWhom as C, validated) + + val conversation = ConversationWrapper.getConversation(context) + ?: error("Unknown conversation, please report this bug to the developer, or read the documentation.") + + val result = ConversationResult(context, conversation, context.forWhom as C, validated) val isValid = validated.isPresent && check.isPresent && check.get().invoke(result) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt index 5282216..ec1e8d1 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt @@ -4,6 +4,7 @@ import com.mattmx.ktgui.conversation.refactor.steps.EnumConversationStep import com.mattmx.ktgui.utils.not import org.bukkit.Bukkit import org.bukkit.entity.Player +import java.time.Duration fun main() { conversation { @@ -64,5 +65,5 @@ fun main() { println(result.get() * 2) } } - } + } timeout 30 } \ No newline at end of file From edfcfc06470dea92bcbf8e4a3d0576b2c4745f5e Mon Sep 17 00:00:00 2001 From: MattMXX Date: Thu, 15 Feb 2024 00:13:33 +0000 Subject: [PATCH 20/25] add infix modifier --- .../mattmx/ktgui/conversation/refactor/ConversationWrapper.kt | 2 +- api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt index e1e6a6a..5b5b612 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt @@ -81,7 +81,7 @@ class ConversationWrapper( } } - fun begin(conversable: T): Conversation { + infix fun begin(conversable: T): Conversation { build() val conversation = factory.buildConversation(conversable).apply { begin() } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt index ec1e8d1..048dbb5 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/t.kt @@ -4,7 +4,6 @@ import com.mattmx.ktgui.conversation.refactor.steps.EnumConversationStep import com.mattmx.ktgui.utils.not import org.bukkit.Bukkit import org.bukkit.entity.Player -import java.time.Duration fun main() { conversation { From 3d37a75803b42a2c95b05f89657b7cf28b96c0db Mon Sep 17 00:00:00 2001 From: MattMXX Date: Thu, 15 Feb 2024 00:24:43 +0000 Subject: [PATCH 21/25] testing performance for string commands parsing (is decent) --- .../commands/stringbuilder/syntax/Parser.kt | 3 +++ .../mattmx/ktgui/commands/stringbuilder/t.kt | 1 + .../com/mattmx/ktgui/utils/StopWatch.kt | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/utils/StopWatch.kt diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt index 8cb056a..8a3b695 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/syntax/Parser.kt @@ -1,5 +1,8 @@ package com.mattmx.ktgui.commands.stringbuilder.syntax +import com.google.common.base.Stopwatch +import com.mattmx.ktgui.utils.StopWatch + class Parser( val source: String ) { diff --git a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt index 8a39301..5c2783c 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/commands/stringbuilder/t.kt @@ -1,6 +1,7 @@ package com.mattmx.ktgui.commands.stringbuilder import com.mattmx.ktgui.commands.usage.CommandUsageOptions +import com.mattmx.ktgui.utils.StopWatch import org.bukkit.command.CommandSender fun main() { diff --git a/api/src/main/kotlin/com/mattmx/ktgui/utils/StopWatch.kt b/api/src/main/kotlin/com/mattmx/ktgui/utils/StopWatch.kt new file mode 100644 index 0000000..6715ee0 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/utils/StopWatch.kt @@ -0,0 +1,20 @@ +package com.mattmx.ktgui.utils + +import java.time.Duration + +class StopWatch( + private val name: String = "sw" +) { + private var timeStart = 0L + + fun start() = apply { + timeStart = System.nanoTime() + } + + fun elapsed() = System.nanoTime() - timeStart + + fun debug() { + val nanos = elapsed() + println(" [$name] took ${nanos}ns (${nanos / 1_000_000}ms${if (nanos > 100_000_000) ", " + Duration.ofNanos(nanos).pretty() else ""})") + } +} \ No newline at end of file From c99f2aa7aa551f5969aa1b813f24dbb82154de4c Mon Sep 17 00:00:00 2001 From: MattMXX Date: Thu, 15 Feb 2024 00:27:36 +0000 Subject: [PATCH 22/25] could have encountered multithreading issues? not sure --- api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt b/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt index a1b8fa0..50e1aa2 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt @@ -18,13 +18,14 @@ import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.event.server.PluginDisableEvent import org.bukkit.plugin.java.JavaPlugin import org.jetbrains.annotations.ApiStatus +import java.util.Collections /** * Handles all GUI click events, as well * as events we might need to know while in a GUI. */ object GuiManager : Listener { - private val players = hashMapOf() + private val players = Collections.synchronizedMap(hashMapOf()) private var initialized = false private val defaultConfiguration = Configuration() private val configurations = hashMapOf() From 49fa87eaa95aa95dcecbd633de3b63e8c03abd85 Mon Sep 17 00:00:00 2001 From: MattMXX Date: Wed, 28 Feb 2024 15:47:31 +0000 Subject: [PATCH 23/25] Conversation example and wrapper changes --- .../components/screen/GuiInfiniteScreen.kt | 8 +-- .../refactor/ConversationWrapper.kt | 32 ++++++++- .../ktgui/examples/ConversationGuiExample.kt | 70 +++++++++++-------- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiInfiniteScreen.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiInfiniteScreen.kt index a0cbe24..6b22bd3 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiInfiniteScreen.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiInfiniteScreen.kt @@ -31,10 +31,10 @@ open class GuiInfiniteScreen( if (firePreBuildEvent(player)) return - items2D.forEach { (coord, item) -> - if (coord.first >= x && coord.first <= x + 9 - && coord.second >= y && coord.second <= y + rows) { - val normalSlot = (coord.second - y) * 9 + (coord.first - x) + items2D.forEach { (pos, item) -> + if (pos.first >= x && pos.first <= x + 9 + && pos.second >= y && pos.second <= y + rows) { + val normalSlot = (pos.second - y) * 9 + (pos.first - x) if (normalSlot >= 0 && normalSlot <= last()) { inv.setItem(normalSlot, item.formatIntoItemStack(player)) } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt index 5b5b612..dba418c 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/ConversationWrapper.kt @@ -1,10 +1,12 @@ package com.mattmx.ktgui.conversation.refactor import com.mattmx.ktgui.GuiManager +import com.mattmx.ktgui.components.screen.IGuiScreen import com.mattmx.ktgui.conversation.refactor.result.ConversationEnd import com.mattmx.ktgui.conversation.refactor.steps.Step import com.mattmx.ktgui.extensions.getOpenGui import com.mattmx.ktgui.extensions.setOpenGui +import com.mattmx.ktgui.scheduling.sync import org.bukkit.conversations.Conversable import org.bukkit.conversations.Conversation import org.bukkit.conversations.ConversationAbandonedEvent @@ -26,7 +28,15 @@ class ConversationWrapper( private val steps = arrayListOf() private var exit = Optional.empty<(ConversationAbandonedEvent) -> Unit>() private var start = Optional.empty<(T) -> Unit>() + + /** + * More than often, the conversation is created as the result of a button + * click in a gui. This means that we can skip boilerplate by initially + * closing the gui, and re-opening it. + */ + private var startingGui = Optional.empty() var closeGuiOnStart = true + var openGuiOnEnd = true var exitOn: String get() = "" @@ -39,6 +49,19 @@ class ConversationWrapper( factory.addConversationAbandonedListener { block(it) ongoingConversations.remove(it.context) + + // Next tick, in-case developer opened another + sync { + // Open previous gui if set and enabled + if (openGuiOnEnd && startingGui.isPresent && it.context.forWhom is Player) { + val player = it.context.forWhom as Player + + // If not already has one open, open the old one by default. + if (player.getOpenGui() == null) { + startingGui.get().open(player) + } + } + } } } @@ -85,8 +108,13 @@ class ConversationWrapper( build() val conversation = factory.buildConversation(conversable).apply { begin() } - if (closeGuiOnStart && conversable is Player) { - GuiManager.forceClose(conversable) + if (conversable is Player) { + if (closeGuiOnStart) { + GuiManager.forceClose(conversable) + } + if (openGuiOnEnd) { + startingGui = Optional.ofNullable(conversable.getOpenGui()) + } } start.ifPresent { it.invoke(conversable) } diff --git a/plugin/src/main/kotlin/com/mattmx/ktgui/examples/ConversationGuiExample.kt b/plugin/src/main/kotlin/com/mattmx/ktgui/examples/ConversationGuiExample.kt index 23d16fa..780f913 100644 --- a/plugin/src/main/kotlin/com/mattmx/ktgui/examples/ConversationGuiExample.kt +++ b/plugin/src/main/kotlin/com/mattmx/ktgui/examples/ConversationGuiExample.kt @@ -3,6 +3,9 @@ package com.mattmx.ktgui.examples import com.mattmx.ktgui.KotlinGui import com.mattmx.ktgui.components.button.GuiButton import com.mattmx.ktgui.components.screen.GuiScreen +import com.mattmx.ktgui.conversation.refactor.conversation +import com.mattmx.ktgui.conversation.refactor.getInteger +import com.mattmx.ktgui.conversation.refactor.getString import com.mattmx.ktgui.dsl.conversation import com.mattmx.ktgui.utils.legacyColor import com.mattmx.ktgui.scheduling.not @@ -21,35 +24,41 @@ class ConversationGuiExample() : GuiScreen(!"Conversation API", 1), Example { GuiButton() .click { ClickType.LEFT { - // Make sure to close the GUI, so they can type in chat - forceClose(player) // Create a new conversation player.showTitle(Title.title(!"&6&lConversation Started", !"&eUse the chat!")) - conversation(KotlinGui.plugin!!) { + + conversation { /** * The way you specify what you want to do is in order. * We'll start with asking what their favorite fruit is. */ - stringPrompt("&6&lWhat's your fave fruit".legacyColor()) { c, i -> - if (i == "oranges") { - /** - * If the input is "oranges" then reply with something else - * KtGui blocks other messages to the client to not "clog" chat. - * This means you should use "c.fromWhom.sendRawMessage" to send messages. - */ - c.forWhom.sendRawMessage("&eI love oranges".legacyColor()) - } else c.forWhom.sendRawMessage("&eEwwww".legacyColor()) + getString { + message = !"&6&lWhat's your fave fruit" + runs { + if (result.orElse(null) == "oranges") { + /** + * If the input is "oranges" then reply with something else + * KtGui blocks other messages to the client to not "clog" chat. + * This means you should use "c.fromWhom.sendRawMessage" to send messages. + */ + conversable.sendMessage("&eI love oranges".legacyColor()) + } else conversable.sendMessage("&eEwwww".legacyColor()) + context.setSessionData("fruit", result) + } } /** * We can also get numeric inputs, and specify a range of valid responses. * We are going to ask how many fruit they eat. * If the response isn't between 5-10 inclusive then we should tell them off. */ - numberPrompt( - "&6&lHow many fruit do you eat a day?".legacyColor(), - "&cYou should be eating 5, go fix that".legacyColor(), - (5..10).toList()) { c, i -> - c.forWhom.sendRawMessage("&eYou inputted $i".legacyColor()) + getInteger { + range = (5..10) + runs { + val fruit = context.getSessionData("fruit") ?: "fruit" + conversable.sendMessage(!"&cYou ate $result ${fruit}s today.") + } invalid { + conversable.sendMessage(!"&cYou must be eating 5-10 fruit per day, go and fix that and come back.") + } } /** * Finally let's add a custom finish statement. @@ -59,26 +68,29 @@ class ConversationGuiExample() : GuiScreen(!"Conversation API", 1), Example { // Open the GUI again open(player) } - }.abandon { - open(player) - player.sendMessage("&cExited Conversation".legacyColor()) - }.exitOn("exit") // If the player types "exit" the conversation is ended. - .build(player).begin() // Build and begin the conversation. + + exitOn = "cancel" + exit { + player.sendMessage(!"&bExited Conversation") + } + } begin player } ClickType.RIGHT { - forceClose(player) - conversation(KotlinGui.plugin!!) { + conversation { /** * If the player right clicks then they can rename the current GUI title. * They can type "exit" to cancel. */ - stringPrompt("&6&lEnter a GUI name".legacyColor()) { c, i -> - i?.let { title(!i) } + getString { + message = !"&6&lEnter a new GUI name" + runs { + title = !result.get() + } } - abandon { - openAndFormat(player) + exit { + open(player) } - }.exitOn("exit").build(player).begin() + } begin player } }.lore { add(!"&eLeft &7for fruit questions") From d59d143f8842b97813c90b1e46353461ee95469e Mon Sep 17 00:00:00 2001 From: MattMXX Date: Wed, 28 Feb 2024 15:47:47 +0000 Subject: [PATCH 24/25] Implemented refresh block + example --- .../kotlin/com/mattmx/ktgui/GuiManager.kt | 8 +++- .../mattmx/ktgui/components/RefreshBlock.kt | 14 ++++++ .../ktgui/components/screen/GuiScreen.kt | 17 ++++++- .../main/kotlin/com/mattmx/ktgui/dsl/gui.kt | 5 +- .../main/kotlin/com/mattmx/ktgui/KotlinGui.kt | 3 +- .../ktgui/examples/RefreshBlockExample.kt | 47 +++++++++++++++++++ 6 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 api/src/main/kotlin/com/mattmx/ktgui/components/RefreshBlock.kt create mode 100644 plugin/src/main/kotlin/com/mattmx/ktgui/examples/RefreshBlockExample.kt diff --git a/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt b/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt index 50e1aa2..c0fe8db 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt @@ -107,7 +107,9 @@ object GuiManager : Listener { val gui = e.player.getOpenGui() gui?.let { gui.quit(e) - gui.destroy() + if (getPlayers(gui).isEmpty()) { + gui.destroy() + } players.remove(e.player) } } @@ -127,7 +129,9 @@ object GuiManager : Listener { fun forceClose(player: Player) { val gui = player.getOpenGui() gui?.let { - gui.destroy() + if (getPlayers(gui).isEmpty()) { + gui.destroy() + } players.remove(player) player.openInventory.close() } diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/RefreshBlock.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/RefreshBlock.kt new file mode 100644 index 0000000..46b11a9 --- /dev/null +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/RefreshBlock.kt @@ -0,0 +1,14 @@ +package com.mattmx.ktgui.components + +import com.mattmx.ktgui.components.screen.GuiScreen + +class RefreshBlock( + val repeat: Long, + private val owner: T, + val block: (T) -> Unit +) { + fun refresh() { + block.invoke(owner) + refresh() + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt index d5ae294..af2961f 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/components/screen/GuiScreen.kt @@ -3,6 +3,7 @@ package com.mattmx.ktgui.components.screen import com.mattmx.ktgui.GuiManager import com.mattmx.ktgui.components.ClickCallback import com.mattmx.ktgui.components.EffectBlock +import com.mattmx.ktgui.components.RefreshBlock import com.mattmx.ktgui.components.button.ButtonClickedEvent import com.mattmx.ktgui.components.button.GuiButton import com.mattmx.ktgui.components.button.IGuiButton @@ -11,6 +12,7 @@ import com.mattmx.ktgui.components.signal.Signal import com.mattmx.ktgui.event.PreGuiBuildEvent import com.mattmx.ktgui.event.PreGuiOpenEvent import com.mattmx.ktgui.extensions.setOpenGui +import com.mattmx.ktgui.scheduling.TaskTracker import com.mattmx.ktgui.scheduling.isAsync import com.mattmx.ktgui.utils.JavaCompatibility import com.mattmx.ktgui.utils.legacy @@ -29,14 +31,12 @@ import java.lang.Integer.max import java.lang.Integer.min import java.util.* import java.util.concurrent.Future -import kotlin.collections.HashMap open class GuiScreen( title: Component = Component.empty(), var rows: Int = 1, var type: InventoryType? = null ) : IGuiScreen, GuiSignalOwner> { - constructor(title: Component, rows: Int) : this(title, rows, null) constructor(title: Component, type: InventoryType) : this(title, 0, type) @@ -51,6 +51,7 @@ open class GuiScreen( // Can be used to identify dsl guis var id: String = UUID.randomUUID().toString() var items = hashMapOf>() + private val taskTracker = TaskTracker() override var currentlyProcessing: EffectBlock? = null var click = ClickCallback>() @@ -246,6 +247,14 @@ open class GuiScreen( return 0 } + fun addRefreshBlock(block: RefreshBlock) { + this.taskTracker.runAsyncRepeat(block.repeat, block.repeat) { + // todo only change slots modified! + block.block.invoke(this@GuiScreen) + refresh() + } + } + @JavaCompatibility infix fun createSignal(initial: S) = Signal(initial, this) @@ -268,4 +277,8 @@ open class GuiScreen( if (::moveCallback.isInitialized) moveCallback(e) } + + override fun destroy() { + taskTracker.cancelAll() + } } \ No newline at end of file diff --git a/api/src/main/kotlin/com/mattmx/ktgui/dsl/gui.kt b/api/src/main/kotlin/com/mattmx/ktgui/dsl/gui.kt index 2f55b25..eae8857 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/dsl/gui.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/dsl/gui.kt @@ -1,6 +1,7 @@ package com.mattmx.ktgui.dsl import com.mattmx.ktgui.components.EffectBlock +import com.mattmx.ktgui.components.RefreshBlock import com.mattmx.ktgui.components.button.GuiButton import com.mattmx.ktgui.components.button.IGuiButton import com.mattmx.ktgui.components.button.SignalButton @@ -33,4 +34,6 @@ fun IGuiScreen.signalButton(material: Material, block: SignalButton.() -> Unit) SignalButton(material, block).apply { childOf(this@signalButton) } fun GuiScreen.effect(block: GuiScreen.() -> Unit) = - EffectBlock(this, block).apply { this@effect.addEffect(this) } \ No newline at end of file + EffectBlock(this, block).apply { this@effect.addEffect(this) } +fun GuiScreen.refresh(repeat: Long, block: GuiScreen.() -> Unit) = + RefreshBlock(repeat, this, block).apply { this@refresh.addRefreshBlock(this) } \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/mattmx/ktgui/KotlinGui.kt b/plugin/src/main/kotlin/com/mattmx/ktgui/KotlinGui.kt index 08c8005..98a22ac 100644 --- a/plugin/src/main/kotlin/com/mattmx/ktgui/KotlinGui.kt +++ b/plugin/src/main/kotlin/com/mattmx/ktgui/KotlinGui.kt @@ -37,7 +37,8 @@ class KotlinGui : JavaPlugin() { "signals-list" to { SignalsListExample() }, "hook" to { GuiHookExample() }, "java-simple" to { JavaGuiExample() }, - "java-new" to { JavaUpdateExample() } + "java-new" to { JavaUpdateExample() }, + "refresh" to { RefreshBlockExample() } ) GuiHookExample.registerListener(this) diff --git a/plugin/src/main/kotlin/com/mattmx/ktgui/examples/RefreshBlockExample.kt b/plugin/src/main/kotlin/com/mattmx/ktgui/examples/RefreshBlockExample.kt new file mode 100644 index 0000000..51578b3 --- /dev/null +++ b/plugin/src/main/kotlin/com/mattmx/ktgui/examples/RefreshBlockExample.kt @@ -0,0 +1,47 @@ +package com.mattmx.ktgui.examples + +import com.mattmx.ktgui.dsl.button +import com.mattmx.ktgui.dsl.gui +import com.mattmx.ktgui.dsl.refresh +import com.mattmx.ktgui.utils.not +import com.mattmx.ktgui.utils.pretty +import net.kyori.adventure.text.Component +import org.bukkit.Material +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryType +import java.text.DateFormat +import java.time.Duration +import java.util.* + +class RefreshBlockExample : Example { + val gui = gui(!"0s", InventoryType.HOPPER) { + val timeOpened = System.currentTimeMillis() + + val block = refresh(20) { + val timeElapsed = Duration.ofMillis(System.currentTimeMillis() - timeOpened) + button(Material.CLOCK) { + named(!"&fOpen for &7${timeElapsed.pretty()}") + lore { + add(Component.empty()) + val date = Date() + val format = DateFormat.getTimeInstance() + add(!"&a${format.format(date)}") + } + } slot 2 + title = !timeElapsed.pretty() + } + + button(Material.REDSTONE) { + named(!"&aManually refresh") + + click.left { + block.refresh() + } + + } slot last() + } + + override fun run(player: Player) { + gui.open(player) + } +} \ No newline at end of file From 69e75c9ae63adbd8b0035fd98c21c7c68d7149ba Mon Sep 17 00:00:00 2001 From: MattMX Date: Tue, 19 Mar 2024 16:06:02 +0000 Subject: [PATCH 25/25] fix build issues --- .../refactor/steps/RawConversationStep.kt | 8 ++++---- build.gradle.kts | 2 +- .../ktgui/examples/ConversationGuiExample.kt | 14 +++----------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt index d974f06..943c751 100644 --- a/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt +++ b/api/src/main/kotlin/com/mattmx/ktgui/conversation/refactor/steps/RawConversationStep.kt @@ -18,12 +18,12 @@ open class RawConversationStep : StringPrompt(), Step private set var message: Component = Component.empty() - var messageCallback: Optional<(ConversationContext) -> Component> = Optional.empty() + private var messageCallback: Optional<(ConversationContext) -> Component> = Optional.empty() var title: Component = Component.empty() var subtitle: Component = Component.empty() - var callback: Optional<(ConversationResult) -> Unit> = Optional.empty() - var check: Optional<(ConversationResult) -> Boolean> = Optional.empty() - var invalid: Optional<(ConversationResult) -> Unit> = Optional.empty() + private var callback: Optional<(ConversationResult) -> Unit> = Optional.empty() + private var check: Optional<(ConversationResult) -> Boolean> = Optional.empty() + private var invalid: Optional<(ConversationResult) -> Unit> = Optional.empty() var repeatIfInvalid = true override fun getPromptText(context: ConversationContext): String { diff --git a/build.gradle.kts b/build.gradle.kts index 5ede2af..78dfb14 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { kotlin("jvm") version "1.7.10" } -val version = "2.2" +val version = "2.3" rootProject.version = version diff --git a/plugin/src/main/kotlin/com/mattmx/ktgui/examples/ConversationGuiExample.kt b/plugin/src/main/kotlin/com/mattmx/ktgui/examples/ConversationGuiExample.kt index 780f913..0d45f84 100644 --- a/plugin/src/main/kotlin/com/mattmx/ktgui/examples/ConversationGuiExample.kt +++ b/plugin/src/main/kotlin/com/mattmx/ktgui/examples/ConversationGuiExample.kt @@ -25,15 +25,15 @@ class ConversationGuiExample() : GuiScreen(!"Conversation API", 1), Example { .click { ClickType.LEFT { // Create a new conversation - player.showTitle(Title.title(!"&6&lConversation Started", !"&eUse the chat!")) - conversation { /** * The way you specify what you want to do is in order. * We'll start with asking what their favorite fruit is. */ getString { - message = !"&6&lWhat's your fave fruit" + title = !"&6&lWhat's your fave fruit" + subtitle = !"&7Type in chat." + runs { if (result.orElse(null) == "oranges") { /** @@ -60,14 +60,6 @@ class ConversationGuiExample() : GuiScreen(!"Conversation API", 1), Example { conversable.sendMessage(!"&cYou must be eating 5-10 fruit per day, go and fix that and come back.") } } - /** - * Finally let's add a custom finish statement. - * If you don't call this then KtGui will add one automatically. - */ - finish("&cNice talking to you!".legacyColor()) { - // Open the GUI again - open(player) - } exitOn = "cancel" exit {