diff --git a/common/src/main/kotlin/com/undefined/stellar/argument/phrase/PhraseArgument.kt b/common/src/main/kotlin/com/undefined/stellar/argument/phrase/PhraseArgument.kt index cd2c4b19..091cb87c 100644 --- a/common/src/main/kotlin/com/undefined/stellar/argument/phrase/PhraseArgument.kt +++ b/common/src/main/kotlin/com/undefined/stellar/argument/phrase/PhraseArgument.kt @@ -42,11 +42,22 @@ class PhraseArgument(name: String) : StringArgument(name, StringType.PHRASE) { it == ' ' } - setSuggestionOffset(greedyContext.phraseInput.length) + setSuggestionOffset(0) + if (greedyContext.phraseInput.isNotEmpty()) { + var tot = 0 + for ((i, word) in greedyContext.arguments.withIndex()) { + if ((greedyContext.arguments.lastIndex == i && !greedyContext.input.endsWith(' ')) || word.isEmpty()) break + tot += word.length + 1 + addSuggestionOffset(word.length + 1) + } + } + val suggestions: MutableList = mutableListOf() val word = words[amountOfSpaces] ?: return@addSuggestion emptyList() for (stellarSuggestion in word.suggestions) - for (suggestion in stellarSuggestion.get(greedyContext)) suggestions.add(Suggestion.create(suggestion.text, suggestion.tooltip)) + for (suggestion in stellarSuggestion.get(greedyContext)) { + suggestions.add(Suggestion.create(suggestion.text, suggestion.tooltip)) + } suggestions } } @@ -55,7 +66,7 @@ class PhraseArgument(name: String) : StringArgument(name, StringType.PHRASE) { * Creates a [WordArgument] at the [index], that you can configure with [init]. Only works in Kotlin. * @return The modified [PhraseArgument] instance. */ - inline fun addWordArgument(index: Int, init: WordArgument.() -> Unit = {}): PhraseArgument { + inline fun addWordArgument(index: Int, init: WordArgument.() -> Unit = {}): PhraseArgument { val word = WordArgument() word.init() words[index] = word @@ -228,10 +239,11 @@ class PhraseArgument(name: String) : StringArgument(name, StringType.PHRASE) { val input = context.input.removePrefix("/") val words = input.split(' ').toMutableList() - val totalOtherArguments = context.args.size - 1 - for (i in (0..totalOtherArguments)) words.removeFirst() + val totalOtherArguments = if (this.name in context.args.keys) context.args.size - 1 else context.args.size + words.removeFirst() // to remove the base command like /[give] + for (i in (0 until totalOtherArguments)) words.removeFirst() - return PhraseCommandContext(context, words, context.sender, input.substring(input.indexOf(' ')), input) + return PhraseCommandContext(context, words, context.sender, words.joinToString(" "), input) } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 11451673..abf14423 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx4G group=com.undefined -version=1.1.3 \ No newline at end of file +version=1.2.0-SNAPSHOT \ No newline at end of file diff --git a/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/ArgumentExtensions.kt b/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/ArgumentExtensions.kt index 858ba96d..5a2def83 100644 --- a/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/ArgumentExtensions.kt +++ b/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/ArgumentExtensions.kt @@ -223,7 +223,7 @@ fun AbstractStellarCommand<*>.listArgument( converter: suspend CommandContext.(T) -> String? = { it.toString() }, scope: CoroutineScope = StellarConfig.scope, block: ListArgument.() -> Unit = {}, -): ListArgument = addArgument(ListArgument(StringArgument(name, StringType.WORD), { +): ListArgument = addArgument(ListArgument(StringArgument(name, StringType.WORD), { input -> scope.future { list().mapNotNull { val convertedText = converter(it).takeIf { suggestion -> suggestion?.isNotBlank() == true } @@ -249,7 +249,7 @@ fun AbstractStellarCommand<*>.listArgument( converter: suspend CommandContext.(T) -> String? = { it.toString() }, scope: CoroutineScope = StellarConfig.scope, block: ListArgument.() -> Unit = {}, -): ListArgument = addArgument(ListArgument(type, { +): ListArgument = addArgument(ListArgument(type, { input -> scope.future { list.mapNotNull { val convertedText = converter(it).takeIf { suggestion -> suggestion?.isNotBlank() == true } @@ -270,12 +270,12 @@ fun AbstractStellarCommand<*>.listArgument( */ fun AbstractStellarCommand<*>.listArgument( type: ParameterArgument<*, R>, - list: suspend CommandContext.() -> List, + list: suspend CommandContext.() -> Iterable, parse: CommandSender.(R) -> T, converter: suspend CommandContext.(T) -> String? = { it.toString() }, scope: CoroutineScope = StellarConfig.scope, block: ListArgument.() -> Unit = {}, -): ListArgument = addArgument(ListArgument(type, { +): ListArgument = addArgument(ListArgument(type, { input -> scope.future { list().mapNotNull { val convertedText = converter(it).takeIf { suggestion -> suggestion?.isNotBlank() == true } @@ -300,7 +300,7 @@ fun AbstractStellarCommand<*>.advancedListArgument( converter: suspend CommandContext.(T) -> Suggestion? = { Suggestion.withText(it.toString()) }, scope: CoroutineScope = StellarConfig.scope, block: ListArgument.() -> Unit = {}, -): ListArgument = addArgument(ListArgument(StringArgument(name, StringType.WORD), { +): ListArgument = addArgument(ListArgument(StringArgument(name, StringType.WORD), { input -> scope.future { list.mapNotNull { converter(it).takeIf { suggestion -> suggestion?.text?.isNotBlank() == true } @@ -324,7 +324,7 @@ fun AbstractStellarCommand<*>.advancedListArgument( converter: suspend CommandContext.(T) -> Suggestion? = { Suggestion.withText(it.toString()) }, scope: CoroutineScope = StellarConfig.scope, block: ListArgument.() -> Unit = {}, -): ListArgument = addArgument(ListArgument(StringArgument(name, StringType.WORD), { +): ListArgument = addArgument(ListArgument(StringArgument(name, StringType.WORD), { input -> scope.future { list().mapNotNull { converter(it).takeIf { suggestion -> suggestion?.text?.isNotBlank() == true } @@ -349,7 +349,7 @@ fun AbstractStellarCommand<*>.advancedListArgument( converter: suspend CommandContext.(T) -> Suggestion? = { Suggestion.withText(it.toString()) }, scope: CoroutineScope = StellarConfig.scope, block: ListArgument.() -> Unit = {}, -): ListArgument = addArgument(ListArgument(type, { +): ListArgument = addArgument(ListArgument(type, { input -> scope.future { list.mapNotNull { converter(it).takeIf { suggestion -> suggestion?.text?.isNotBlank() == true } @@ -374,7 +374,7 @@ fun AbstractStellarCommand<*>.advancedListArgument( converter: suspend CommandContext.(T) -> Suggestion? = { Suggestion.withText(it.toString()) }, scope: CoroutineScope = StellarConfig.scope, block: ListArgument.() -> Unit = {}, -): ListArgument = addArgument(ListArgument(type, { +): ListArgument = addArgument(ListArgument(type, { input -> scope.future { list().mapNotNull { converter(it).takeIf { suggestion -> suggestion?.text?.isNotBlank() == true } diff --git a/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/CommandExtensions.kt b/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/CommandExtensions.kt index 091b96af..6b5e291a 100644 --- a/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/CommandExtensions.kt +++ b/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/CommandExtensions.kt @@ -29,12 +29,19 @@ inline fun AbstractStellarCommand<*>.execution( } /** - * Adds an async execution to the command with the use of [CompletableFuture]. + * Adds an async execution to the command using [StellarConfig.asyncScope]. * * @param C The type of CommandSender. * @param execution The execution block. */ -inline fun AbstractStellarCommand<*>.asyncExecution(noinline execution: CommandContext.() -> Unit) = addAsyncExecution(execution) +inline fun AbstractStellarCommand<*>.asyncExecution( + scope: CoroutineScope = StellarConfig.asyncScope, + noinline execution: suspend CommandContext.() -> Unit, +): AbstractStellarCommand<*> = addAsyncExecution { + scope.launch { + execution() + } +} /** * Adds a runnable to the command. @@ -56,7 +63,7 @@ inline fun AbstractStellarCommand<*>.runnable( } /** - * Adds an async runnable to the command with the use of [CompletableFuture]. + * Adds an async runnable to the command using [StellarConfig.asyncScope]. * * @param C The type of CommandSender. * @param alwaysApplicable Whether it should always run or only when an execution is already present for the last argument. @@ -64,14 +71,20 @@ inline fun AbstractStellarCommand<*>.runnable( */ inline fun AbstractStellarCommand<*>.asyncRunnable( alwaysApplicable: Boolean = false, - noinline execution: CommandContext.() -> Boolean, -): AbstractStellarCommand<*> = addAsyncRunnable(alwaysApplicable, execution) + scope: CoroutineScope = StellarConfig.asyncScope, + noinline execution: suspend CommandContext.() -> Unit, +): AbstractStellarCommand<*> = addAsyncRunnable(alwaysApplicable) { + scope.launch { + execution() + } + true +} /** * Adds a failure execution to the command to be displayed when the command fails. * * @param C The type of CommandSender. - * @param scope The [CoroutineScope] used to create + * @param scope The [CoroutineScope] used to run the [execution] block (default: [StellarConfig.scope]). * @param execution The execution block. * @return The modified command object. */ @@ -87,8 +100,10 @@ inline fun AbstractStellarCommand<*>.failureExecutio /** * Adds a requirement that must be met for the command to be available to the player. * + * NOTE: the [requirement] block blocks the current thread. + * * @param C The type of CommandSender. - * @param scope The [CoroutineScope] used to create + * @param scope The [CoroutineScope] used to run the [requirement] block (default: [StellarConfig.scope]). * @param requirement The condition that must be met. * @return The modified command object. */ @@ -132,10 +147,10 @@ fun ParameterArgument<*, *>.suggests(vararg suggestions: Suggestion) = addSugges fun ParameterArgument<*, *>.suggests(title: String, tooltip: String? = null) = addSuggestion(title, tooltip) /** - * Adds a function that returns a list of [Suggestion] on top of the current suggestions. Only works in Kotlin. + * Adds a function that returns a list of [Suggestion] on top of the current suggestions. * * @param C The type of CommandSender. - * @param scope The [CoroutineScope] used to create + * @param scope The [CoroutineScope] used to run the [suggestion] block (default: [StellarConfig.scope]). * @return The modified [ParameterArgument]. */ inline fun ParameterArgument<*, *>.suggests( @@ -145,4 +160,20 @@ inline fun ParameterArgument<*, *>.suggests( scope.future { suggestion(input) } +} + +/** + * Adds a function that returns a list of [Suggestion] on top of the current suggestions. + * + * @param C The type of CommandSender. + * @param scope The [CoroutineScope] used to run the [suggestion] block (default: [StellarConfig.asyncScope]). + * @return The modified [ParameterArgument]. + */ +inline fun ParameterArgument<*, *>.asyncSuggests( + scope: CoroutineScope = StellarConfig.asyncScope, + noinline suggestion: suspend CommandContext.(input: String) -> List, +) = addFutureSuggestion { input -> + scope.future { + suggestion(input) + } } \ No newline at end of file diff --git a/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/KotlinStellarConfig.kt b/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/KotlinStellarConfig.kt index 4a77dd66..11ae6412 100644 --- a/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/KotlinStellarConfig.kt +++ b/kotlin/core/src/main/kotlin/com/undefined/stellar/kotlin/KotlinStellarConfig.kt @@ -7,15 +7,39 @@ import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal object KotlinStellarConfig { - var _scope: CoroutineScope? = null - val scope: CoroutineScope + private var _scope: CoroutineScope? = null + var scope: CoroutineScope get() = _scope ?: error("Scope has not been set!") + set(value) { + _scope = value + } + + + private var _asyncScope: CoroutineScope? = null + var asyncScope: CoroutineScope + get() = _asyncScope ?: _scope ?: error("Neither the async scope or the scope has been set!") + set(value) { + _asyncScope = value + } } fun StellarConfig.setScope(scope: CoroutineScope): StellarConfig = apply { - KotlinStellarConfig._scope = scope + KotlinStellarConfig.scope = scope } +fun StellarConfig.setAsyncScope(scope: CoroutineScope): StellarConfig = apply { + KotlinStellarConfig.asyncScope = scope +} + +/** + * [CoroutineScope] used for any executions or blocks that have to be run in suspend. + */ val StellarConfig.scope: CoroutineScope get() = KotlinStellarConfig.scope + +/** + * [CoroutineScope] used for any executions or blocks that are run in async that have to be run in suspend. + */ +val StellarConfig.asyncScope: CoroutineScope + get() = KotlinStellarConfig.asyncScope diff --git a/paper/api/src/main/kotlin/com/undefined/stellar/data/argument/MojangAdapter.kt b/paper/api/src/main/kotlin/com/undefined/stellar/data/argument/MojangAdapter.kt index b8cef04b..1baa6736 100644 --- a/paper/api/src/main/kotlin/com/undefined/stellar/data/argument/MojangAdapter.kt +++ b/paper/api/src/main/kotlin/com/undefined/stellar/data/argument/MojangAdapter.kt @@ -22,7 +22,7 @@ object MojangAdapter { .appendNewline() .append(Component.text("... $message").color(NamedTextColor.GRAY)) .append(Component.translatable("command.context.here")) - MessageComponentSerializer.message().serialize(component).also { println("message: $it") } + MessageComponentSerializer.message().serialize(component) } fun getStellarCommandContext(context: BrigadierCommandContext): CommandContext { diff --git a/server/build.gradle.kts b/server/build.gradle.kts index d3d9bac0..dc58e4b4 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -39,7 +39,6 @@ tasks { } shadowJar { archiveFileName = "server.jar" - outputs.upToDateWhen { false } } compileKotlin { compilerOptions.jvmTarget = JvmTarget.JVM_21 diff --git a/server/src/main/kotlin/com/undefined/stellar/BaseCommand.kt b/server/src/main/kotlin/com/undefined/stellar/BaseCommand.kt new file mode 100644 index 00000000..ab66473d --- /dev/null +++ b/server/src/main/kotlin/com/undefined/stellar/BaseCommand.kt @@ -0,0 +1,21 @@ +package com.undefined.stellar + +import com.undefined.stellar.kotlin.KotlinBaseStellarCommand +import com.undefined.stellar.kotlin.literalArgument +import com.undefined.stellar.kotlin.runnable +import org.bukkit.entity.Player + +object BaseCommand : KotlinBaseStellarCommand("base") { + override fun setup(): StellarCommand = kotlinCommand { + literalArgument("give") { + runnable { + val flags = getOrNull("flags") ?: "FUCK" + sender.sendMessage(flags) + true + } + addPhraseArgument("flags") + .addWordSuggestions(0, "-v", "-s") + .addWordSuggestions(1, "-v", "-s") + } + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/com/undefined/stellar/Main.kt b/server/src/main/kotlin/com/undefined/stellar/Main.kt index 8be922d0..ff47ecf5 100644 --- a/server/src/main/kotlin/com/undefined/stellar/Main.kt +++ b/server/src/main/kotlin/com/undefined/stellar/Main.kt @@ -1,13 +1,10 @@ package com.undefined.stellar -import com.undefined.stellar.data.argument.CommandContext -import com.undefined.stellar.data.execution.StellarExecution import com.undefined.stellar.kotlin.setScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin -import org.bukkit.profile.PlayerProfile class Main : JavaPlugin() { @@ -27,6 +24,8 @@ class Main : JavaPlugin() { sender.sendMessage("Your balance: 100 euros") } .register() + + BaseCommand.register(this) } } \ No newline at end of file diff --git a/spigot/api/src/main/kotlin/com/undefined/stellar/NMSManager.kt b/spigot/api/src/main/kotlin/com/undefined/stellar/NMSManager.kt index 844db995..60b5dfdd 100644 --- a/spigot/api/src/main/kotlin/com/undefined/stellar/NMSManager.kt +++ b/spigot/api/src/main/kotlin/com/undefined/stellar/NMSManager.kt @@ -156,18 +156,24 @@ object NMSManager { private fun handleSuggestions(argument: AbstractStellarCommand<*>, argumentBuilder: ArgumentBuilder) { if (argument !is ParameterArgument<*, *> || argument._suggestions.isEmpty() || argumentBuilder !is RequiredArgumentBuilder) return - argumentBuilder.suggests { context, builder -> - val stellarContext = MojangAdapter.getStellarCommandContext(context) - val range = StringRange.between(builder.start + argument.suggestionOffset, builder.input.length) - + argumentBuilder.suggests { context, rawBuilder -> CompletableFuture.supplyAsync { + val stellarContext = MojangAdapter.getStellarCommandContext(context) val suggestions: MutableList = mutableListOf() - for (suggestion in argument._suggestions) suggestions.addAll(suggestion.get(stellarContext, builder.remaining).get()) + for (suggestion in argument._suggestions) + suggestions.addAll(suggestion.get(stellarContext, rawBuilder.remaining).get()) - Suggestions(range, suggestions.map { suggestion -> - if (suggestion.tooltip == null || suggestion.tooltip!!.isBlank()) BrigadierSuggestion(range, suggestion.text) - else BrigadierSuggestion(range, suggestion.text) { suggestion.tooltip } - }) + val builder = if (argument.suggestionOffset != 0) rawBuilder.createOffset(rawBuilder.start + argument.suggestionOffset) else rawBuilder + + for (suggestion in suggestions) { + if (suggestion.text.startsWith(rawBuilder.remaining.substringAfterLast(' '), true)) + if (suggestion.tooltip == null) + builder.suggest(suggestion.text) + else + builder.suggest(suggestion.text) { suggestion.tooltip } + } + + builder.build() } } }