Skip to content

Commit

Permalink
feat(commands): Custom argument suggestions, command aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
0ffz committed Jun 6, 2024
1 parent e6b9206 commit 944f4b6
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 83 deletions.
2 changes: 2 additions & 0 deletions idofront-commands/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ plugins {
dependencies {
implementation(project(":idofront-logging"))
implementation(project(":idofront-text-components"))
implementation(libs.minecraft.mccoroutine)
implementation(libs.kotlinx.coroutines)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

/**
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import kotlin.reflect.KProperty

class IdoArgument<T>(
val name: String,
val default: (IdoCommandContext.() -> T)? = null,
) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): IdoArgument<T> {
return this
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T>(
val type: ArgumentType<out T>,
val suggestions: (suspend IdoSuggestionsContext.() -> Unit)? = null,
// val default: (IdoCommandContext.() -> T)?,
)

data class IdoSuggestionsContext(
val context: CommandContext<CommandSourceStack>,
val suggestions: SuggestionsBuilder,
) {
fun suggestFiltering(name: String) {
if (name.startsWith(suggestions.remaining, ignoreCase = true))
suggestions.suggest(name)
}

fun suggest(list: List<String>) {
list.forEach { suggestFiltering(it) }
}
}

@Suppress("UnstableApiUsage")
@Annotations
open class IdoCommand(
internal val initial: LiteralArgumentBuilder<CommandSourceStack>,
val name: String,
val plugin: Plugin,
) {
val buildSteps = mutableListOf<BuildStep>(BuildStep.Built(initial))
private val renderSteps = mutableListOf<RenderStep>()

private fun add(step: RenderStep) {
renderSteps += step
}

fun <T> ArgumentType<T>.suggests(suggestions: suspend IdoSuggestionsContext.() -> Unit): IdoArgumentBuilder<T?> {
return IdoArgumentBuilder(this, suggestions)
}

fun <T> ArgumentType<T>.suggests(provider: SuggestionProvider<CommandSourceStack>): IdoArgumentBuilder<T?> {
return IdoArgumentBuilder(this) { provider.getSuggestions(context, suggestions) }
}
//
// fun <T> ArgumentType<T>.orElse(default: IdoCommandContext.() -> T): DefaultingArg<T> {
// return DefaultingArg(this, default)
// }
//
// fun <T> ArgumentType<T>.orError(): DefaultingArg<T> {
// return DefaultingArg(this, null)
// }

operator fun <T> ArgumentType<T>.provideDelegate(t: T, property: KProperty<*>): IdoArgument<T> {
val arg = IdoArgument<T>(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 <T> IdoArgumentBuilder<T>.provideDelegate(thisRef: Any?, property: KProperty<*>): IdoArgument<T?> {
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) {
Expand All @@ -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<RenderedCommand> {
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<CommandSourceStack> {
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>) : RenderedCommand {
override fun foldLeft(acc: IdoArgBuilder) = acc.apply {
then(list.fold(initial) { acc, next -> next.foldLeft(acc) })
}
}
}
// acc.then(optional.etc()).etc()
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@ open class IdoCommandContext(
@JvmName("invoke1")
inline operator fun <reified T> IdoArgument<out ArgumentResolver<T>>.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<T>)
return ((this as IdoArgument<Any?>).invoke() as ArgumentResolver<T>)
.resolve(context.source)
}

@JvmName("invoke2")
inline operator fun <reified T> IdoArgument<T>.invoke(): T {
return context.getArgument(name, T::class.java)
return context.getArgumentOrNull<T>(name)
?: default?.let { it() }
?: commandException("<red>Argument $name not found".miniMsg())
}

@PublishedApi
internal inline fun <reified T> CommandContext<CommandSourceStack>.getArgumentOrNull(name: String): T? = runCatching {
context.getArgument(name, T::class.java)
}.getOrNull()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ 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")
class IdoRootCommand(
initial: LiteralArgumentBuilder<CommandSourceStack>,
name: String,
val description: String?,
) : IdoCommand(initial, name)
val aliases: List<String>,
plugin: Plugin,
) : IdoCommand(initial, name, plugin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.mineinabyss.idofront.commands.brigadier

@Suppress("UnstableApiUsage")
sealed interface RenderStep {
fun reduce(rightAcc: List<RenderedCommand>): List<RenderedCommand>

class Builder(val builder: IdoArgBuilder) : RenderStep {
override fun reduce(rightAcc: List<RenderedCommand>): List<RenderedCommand> {
return listOf(RenderedCommand.ThenFold(builder, rightAcc))
}
}

class Command(val command: IdoCommand) : RenderStep {
override fun reduce(rightAcc: List<RenderedCommand>): List<RenderedCommand> {
return listOf(RenderedCommand.ThenFold(command.initial, command.render())) + rightAcc
}
}

class Apply(val apply: IdoArgBuilder.() -> Unit) : RenderStep {
override fun reduce(rightAcc: List<RenderedCommand>): List<RenderedCommand> {
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())
// }
// }
// }
}
Original file line number Diff line number Diff line change
@@ -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<IdoRootCommand>()

operator fun String.invoke(description: String? = null, init: IdoRootCommand.() -> Unit) {
operator fun String.invoke(aliases: List<String>, description: String? = null, init: IdoRootCommand.() -> Unit) {
rootCommands += IdoRootCommand(
Commands.literal(this),
this,
description,
aliases,
plugin,
).apply(init)
}

operator fun List<String>.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<String>.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
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CommandSourceStack, *>
internal typealias IdoCommandNode = CommandNode<CommandSourceStack>

fun IdoArgBuilder.thenCast(other: IdoArgBuilder): IdoArgBuilder = then(other) as IdoArgBuilder

fun IdoArgBuilder.thenCast(other: IdoCommandNode): IdoArgBuilder = then(other) as IdoArgBuilder

0 comments on commit 944f4b6

Please sign in to comment.