diff --git a/idofront-commands/build.gradle.kts b/idofront-commands/build.gradle.kts index 1c132b5..df73c16 100644 --- a/idofront-commands/build.gradle.kts +++ b/idofront-commands/build.gradle.kts @@ -7,4 +7,6 @@ plugins { dependencies { implementation(project(":idofront-logging")) implementation(project(":idofront-text-components")) + implementation(libs.minecraft.mccoroutine) + implementation(libs.kotlinx.coroutines) } diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/BuildStep.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/BuildStep.kt deleted file mode 100644 index 6a556a9..0000000 --- a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/BuildStep.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.mineinabyss.idofront.commands.brigadier - -import com.mojang.brigadier.arguments.ArgumentType -import io.papermc.paper.command.brigadier.Commands - -@Suppress("UnstableApiUsage") -sealed interface BuildStep { - fun builder(): IdoArgBuilder - - class Built(val builder: IdoArgBuilder) : BuildStep { - override fun builder() = builder - } - - class Argument(val name: String, val type: ArgumentType) : BuildStep { - override fun builder() = Commands.argument(name, type) - } - - class Command(val command: IdoCommand) : BuildStep { - override fun builder() = command.applyToInitial() - } - - class Executes( - val on: BuildStep, - var executes: IdoCommandContext.() -> Unit = { }, - ) : BuildStep { - @Suppress("UNCHECKED_CAST") // Paper's api always uses CommandSourceStack - override fun builder(): IdoArgBuilder = on.builder().executes { context -> - executes(IdoCommandContext(context)) - com.mojang.brigadier.Command.SINGLE_SUCCESS - } as IdoArgBuilder - } - - class Nested( - val previous: BuildStep, - val inner: BuildStep, - ) : BuildStep { - override fun builder(): IdoArgBuilder { - return previous.builder().apply { - then(inner.builder()) - } - } - } -} diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/Commands.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/Commands.kt index 338ff2d..f51ecfd 100644 --- a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/Commands.kt +++ b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/Commands.kt @@ -9,8 +9,8 @@ import org.bukkit.plugin.Plugin /** * Idofront brigader DSL entrypoint. */ -fun Commands.commands(init: RootIdoCommands.() -> Unit) { - RootIdoCommands(this).apply(init).buildEach() +fun Commands.commands(plugin: Plugin, init: RootIdoCommands.() -> Unit) { + RootIdoCommands(this, plugin).apply(init).buildEach() } /** @@ -20,6 +20,6 @@ fun Commands.commands(init: RootIdoCommands.() -> Unit) { */ fun Plugin.commands(init: RootIdoCommands.() -> Unit) { lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS) { event -> - event.registrar().commands(init) + event.registrar().commands(this, init) } } diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoArgument.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoArgument.kt index e079285..d6752fa 100644 --- a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoArgument.kt +++ b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoArgument.kt @@ -4,6 +4,7 @@ import kotlin.reflect.KProperty class IdoArgument( val name: String, + val default: (IdoCommandContext.() -> T)? = null, ) { operator fun getValue(thisRef: Any?, property: KProperty<*>): IdoArgument { return this diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommand.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommand.kt index 6a5ef72..f3f06d6 100644 --- a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommand.kt +++ b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommand.kt @@ -1,49 +1,109 @@ package com.mineinabyss.idofront.commands.brigadier +import com.github.shynixn.mccoroutine.bukkit.asyncDispatcher import com.mineinabyss.idofront.commands.execution.CommandExecutionFailedException import com.mineinabyss.idofront.textcomponents.miniMsg import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.builder.ArgumentBuilder import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.suggestion.SuggestionProvider +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import com.mojang.brigadier.tree.LiteralCommandNode import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin import kotlin.reflect.KProperty +data class IdoArgumentBuilder( + val type: ArgumentType, + val suggestions: (suspend IdoSuggestionsContext.() -> Unit)? = null, +// val default: (IdoCommandContext.() -> T)?, +) + +data class IdoSuggestionsContext( + val context: CommandContext, + val suggestions: SuggestionsBuilder, +) { + fun suggestFiltering(name: String) { + if (name.startsWith(suggestions.remaining, ignoreCase = true)) + suggestions.suggest(name) + } + + fun suggest(list: List) { + list.forEach { suggestFiltering(it) } + } +} + @Suppress("UnstableApiUsage") @Annotations open class IdoCommand( internal val initial: LiteralArgumentBuilder, val name: String, + val plugin: Plugin, ) { - val buildSteps = mutableListOf(BuildStep.Built(initial)) + private val renderSteps = mutableListOf() + + private fun add(step: RenderStep) { + renderSteps += step + } + + fun ArgumentType.suggests(suggestions: suspend IdoSuggestionsContext.() -> Unit): IdoArgumentBuilder { + return IdoArgumentBuilder(this, suggestions) + } + + fun ArgumentType.suggests(provider: SuggestionProvider): IdoArgumentBuilder { + return IdoArgumentBuilder(this) { provider.getSuggestions(context, suggestions) } + } +// +// fun ArgumentType.orElse(default: IdoCommandContext.() -> T): DefaultingArg { +// return DefaultingArg(this, default) +// } +// +// fun ArgumentType.orError(): DefaultingArg { +// return DefaultingArg(this, null) +// } operator fun ArgumentType.provideDelegate(t: T, property: KProperty<*>): IdoArgument { val arg = IdoArgument(property.name) - buildSteps += BuildStep.Argument(property.name, this) + add(RenderStep.Builder(Commands.argument(property.name, this))) return arg } - operator fun String.invoke(init: IdoCommand.() -> Unit) { - nested(BuildStep.Command(IdoCommand(Commands.literal(this), this).apply(init))) + operator fun IdoArgumentBuilder.provideDelegate(thisRef: Any?, property: KProperty<*>): IdoArgument { + add(RenderStep.Builder(Commands.argument(property.name, type).apply { + if (this@provideDelegate.suggestions != null) + suggests { context, builder -> + CoroutineScope(plugin.asyncDispatcher).async { + this@provideDelegate.suggestions.invoke(IdoSuggestionsContext(context, builder)) + builder.build() + }.asCompletableFuture() + } + })) + return IdoArgument(property.name) } - private fun nested(inner: BuildStep) { - val last = buildSteps.last() - buildSteps.removeLast() - buildSteps += BuildStep.Nested(last, inner) + operator fun String.invoke(init: IdoCommand.() -> Unit) { + add(RenderStep.Command(IdoCommand(Commands.literal(this), this, plugin).apply(init))) } - fun requires(init: CommandSourceStack.() -> Boolean) { - builder { - requires { init(it) } - } + fun requires(init: CommandSourceStack.() -> Boolean) = edit { + requires { init(it) } } - fun builder(apply: IdoArgBuilder.() -> ArgumentBuilder<*, *>) { - val last = buildSteps.last() - buildSteps.removeLast() - buildSteps += BuildStep.Built(last.builder().apply() as IdoArgBuilder) + fun executes(run: IdoCommandContext.() -> Unit) = edit { + executes { context -> + try { + run(IdoCommandContext(context)) + } catch (e: CommandExecutionFailedException) { + e.replyWith?.let { context.source.sender.sendMessage(it) } + } + com.mojang.brigadier.Command.SINGLE_SUCCESS + } } fun playerExecutes(run: IdoPlayerCommandContext.() -> Unit) { @@ -53,23 +113,35 @@ open class IdoCommand( } } - fun executes(run: IdoCommandContext.() -> Unit) { - builder { - executes { context -> - try { - run(IdoCommandContext(context)) - } catch (e: CommandExecutionFailedException) { - e.replyWith?.let { context.source.sender.sendMessage(it) } - } - com.mojang.brigadier.Command.SINGLE_SUCCESS - } + fun edit(apply: IdoArgBuilder.() -> ArgumentBuilder<*, *>) { + add(RenderStep.Apply { apply() as IdoArgBuilder }) + } + + internal fun render(): List { + return renderSteps.foldRight(listOf()) { step, acc -> + step.reduce(acc) } } - fun applyToInitial(): IdoArgBuilder { - return buildSteps.map { it.builder() }.reduceRight { step, acc -> - @Suppress("UNCHECKED_CAST") // Implicitly guaranteed by Paper's API - step.then(acc) as IdoArgBuilder + internal fun build(): LiteralCommandNode { + render().fold(initial as IdoArgBuilder) { acc, curr -> + curr.foldLeft(acc) + } + return initial.build() + } +} + +sealed interface RenderedCommand { + fun foldLeft(acc: IdoArgBuilder): IdoArgBuilder + + data class Apply(val apply: IdoArgBuilder.() -> Unit) : RenderedCommand { + override fun foldLeft(acc: IdoArgBuilder) = acc.apply(apply) + } + + data class ThenFold(val initial: IdoArgBuilder, val list: List) : RenderedCommand { + override fun foldLeft(acc: IdoArgBuilder) = acc.apply { + then(list.fold(initial) { acc, next -> next.foldLeft(acc) }) } } } +// acc.then(optional.etc()).etc() diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommandContext.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommandContext.kt index c494b70..535f435 100644 --- a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommandContext.kt +++ b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommandContext.kt @@ -25,12 +25,19 @@ open class IdoCommandContext( @JvmName("invoke1") inline operator fun IdoArgument>.invoke(): T { @Suppress("UNCHECKED_CAST") // getArgument logic ensures this cast always succeeds if the argument was registered - return (context.getArgument(name, Any::class.java) as ArgumentResolver) + return ((this as IdoArgument).invoke() as ArgumentResolver) .resolve(context.source) } @JvmName("invoke2") inline operator fun IdoArgument.invoke(): T { - return context.getArgument(name, T::class.java) + return context.getArgumentOrNull(name) + ?: default?.let { it() } + ?: commandException("Argument $name not found".miniMsg()) } + + @PublishedApi + internal inline fun CommandContext.getArgumentOrNull(name: String): T? = runCatching { + context.getArgument(name, T::class.java) + }.getOrNull() } diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoRootCommand.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoRootCommand.kt index e8540f7..e7d812c 100644 --- a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoRootCommand.kt +++ b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoRootCommand.kt @@ -2,6 +2,7 @@ package com.mineinabyss.idofront.commands.brigadier import com.mojang.brigadier.builder.LiteralArgumentBuilder import io.papermc.paper.command.brigadier.CommandSourceStack +import org.bukkit.plugin.Plugin @Annotations @Suppress("UnstableApiUsage") @@ -9,4 +10,6 @@ class IdoRootCommand( initial: LiteralArgumentBuilder, name: String, val description: String?, -) : IdoCommand(initial, name) + val aliases: List, + plugin: Plugin, +) : IdoCommand(initial, name, plugin) diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RenderStep.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RenderStep.kt new file mode 100644 index 0000000..891f8a7 --- /dev/null +++ b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RenderStep.kt @@ -0,0 +1,46 @@ +package com.mineinabyss.idofront.commands.brigadier + +@Suppress("UnstableApiUsage") +sealed interface RenderStep { + fun reduce(rightAcc: List): List + + class Builder(val builder: IdoArgBuilder) : RenderStep { + override fun reduce(rightAcc: List): List { + return listOf(RenderedCommand.ThenFold(builder, rightAcc)) + } + } + + class Command(val command: IdoCommand) : RenderStep { + override fun reduce(rightAcc: List): List { + return listOf(RenderedCommand.ThenFold(command.initial, command.render())) + rightAcc + } + } + + class Apply(val apply: IdoArgBuilder.() -> Unit) : RenderStep { + override fun reduce(rightAcc: List): List { + return listOf(RenderedCommand.Apply(apply)) + rightAcc + } + } + +// class Executes( +// val on: RenderStep, +// var executes: IdoCommandContext.() -> Unit = { }, +// ) : RenderStep { +// @Suppress("UNCHECKED_CAST") // Paper's api always uses CommandSourceStack +// override fun builder(): IdoArgBuilder = on.builder().executes { context -> +// executes(IdoCommandContext(context)) +// com.mojang.brigadier.Command.SINGLE_SUCCESS +// } as IdoArgBuilder +// } + +// class Nested( +// val previous: RenderStep, +// val inner: RenderStep, +// ) : RenderStep { +// override fun builder(): IdoArgBuilder { +// return previous.builder().apply { +// then(inner.builder()) +// } +// } +// } +} diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RootIdoCommands.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RootIdoCommands.kt index e8dafcd..9ddbe6e 100644 --- a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RootIdoCommands.kt +++ b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RootIdoCommands.kt @@ -1,27 +1,37 @@ package com.mineinabyss.idofront.commands.brigadier import io.papermc.paper.command.brigadier.Commands +import org.bukkit.plugin.Plugin @Suppress("UnstableApiUsage") class RootIdoCommands( val commands: Commands, + val plugin: Plugin, ) { private val rootCommands = mutableListOf() - operator fun String.invoke(description: String? = null, init: IdoRootCommand.() -> Unit) { + operator fun String.invoke(aliases: List, description: String? = null, init: IdoRootCommand.() -> Unit) { rootCommands += IdoRootCommand( Commands.literal(this), this, description, + aliases, + plugin, ).apply(init) } + operator fun List.invoke(description: String? = null, init: IdoRootCommand.() -> Unit) = + firstOrNull()?.invoke(aliases = drop(1), description = description, init = init) + + operator fun String.div(other: String) = listOf(this, other) + operator fun List.div(other: String) = listOf(this) + other + fun buildEach() { rootCommands.forEach { command -> - command.applyToInitial() commands.register( - command.initial.build(), - command.description + command.build(), + command.description, + command.aliases ) } } diff --git a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/TypeAliases.kt b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/TypeAliases.kt index 24573d8..ce303b9 100644 --- a/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/TypeAliases.kt +++ b/idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/TypeAliases.kt @@ -1,7 +1,13 @@ package com.mineinabyss.idofront.commands.brigadier import com.mojang.brigadier.builder.ArgumentBuilder +import com.mojang.brigadier.tree.CommandNode import io.papermc.paper.command.brigadier.CommandSourceStack @Suppress("UnstableApiUsage") internal typealias IdoArgBuilder = ArgumentBuilder +internal typealias IdoCommandNode = CommandNode + +fun IdoArgBuilder.thenCast(other: IdoArgBuilder): IdoArgBuilder = then(other) as IdoArgBuilder + +fun IdoArgBuilder.thenCast(other: IdoCommandNode): IdoArgBuilder = then(other) as IdoArgBuilder