-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #73 from MineInAbyss/develop
feat(commands): Brigadier command DSL
- Loading branch information
Showing
19 changed files
with
387 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
build | ||
target | ||
out/ | ||
.kotlin | ||
kotlin-js-store | ||
|
||
.classpath | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/Annotations.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
@DslMarker | ||
annotation class Annotations |
25 changes: 25 additions & 0 deletions
25
...commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/CommandExtensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
@file:Suppress("UnstableApiUsage") | ||
|
||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
import io.papermc.paper.command.brigadier.Commands | ||
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents | ||
import org.bukkit.plugin.Plugin | ||
|
||
/** | ||
* Idofront brigader DSL entrypoint. | ||
*/ | ||
inline fun Commands.commands(plugin: Plugin, init: RootIdoCommands.() -> Unit) { | ||
RootIdoCommands(this, plugin).apply(init).buildEach() | ||
} | ||
|
||
/** | ||
* Idofront brigader DSL entrypoint. | ||
* | ||
* Must be registered in the plugin's onEnable or onLoad as it hooks into Paper's plugin lifecycle. | ||
*/ | ||
inline fun Plugin.commands(crossinline init: RootIdoCommands.() -> Unit) { | ||
lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS) { event -> | ||
event.registrar().commands(this, init) | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoArgument.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
import kotlin.reflect.KProperty | ||
|
||
class IdoArgument<T>( | ||
val name: String, | ||
) { | ||
operator fun getValue(thisRef: Any?, property: KProperty<*>): IdoArgument<T> { | ||
return this | ||
} | ||
} | ||
|
8 changes: 8 additions & 0 deletions
8
...ommands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoArgumentBuilder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
import com.mojang.brigadier.arguments.ArgumentType | ||
|
||
data class IdoArgumentBuilder<T>( | ||
val type: ArgumentType<out T>, | ||
val suggestions: (suspend IdoSuggestionsContext.() -> Unit)? = null, | ||
) |
107 changes: 107 additions & 0 deletions
107
idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommand.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
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.suggestion.SuggestionProvider | ||
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 | ||
|
||
@Suppress("UnstableApiUsage") | ||
@Annotations | ||
open class IdoCommand( | ||
internal val initial: LiteralArgumentBuilder<CommandSourceStack>, | ||
val name: String, | ||
val plugin: Plugin, | ||
) { | ||
private val renderSteps = mutableListOf<RenderStep>() | ||
|
||
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) } | ||
} | ||
|
||
operator fun <T> ArgumentType<T>.provideDelegate(t: T, property: KProperty<*>): IdoArgument<T> { | ||
add(RenderStep.Builder(Commands.argument(property.name, this))) | ||
return IdoArgument(property.name) | ||
} | ||
|
||
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) | ||
} | ||
|
||
/** Creates a subcommand using [Commands.literal]. */ | ||
inline operator fun String.invoke(init: IdoCommand.() -> Unit) { | ||
add(RenderStep.Command(IdoCommand(Commands.literal(this), this, plugin).apply(init))) | ||
} | ||
|
||
/** Specifies a predicate for the command to execute further, may be calculated more than once. */ | ||
inline fun requires(crossinline init: CommandSourceStack.() -> Boolean) = edit { | ||
requires { init(it) } | ||
} | ||
|
||
/** Specifies an end node for the command that runs something, only one executes block can run per command execution. */ | ||
inline fun executes(crossinline 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 | ||
} | ||
} | ||
|
||
/** [executes], ensuring the executor is a player. */ | ||
inline fun playerExecutes(crossinline run: IdoPlayerCommandContext.() -> Unit) { | ||
executes { | ||
if (executor !is Player) commandException("<red>This command can only be run by a player.".miniMsg()) | ||
run(IdoPlayerCommandContext(context)) | ||
} | ||
} | ||
|
||
@PublishedApi | ||
internal fun add(step: RenderStep) { | ||
renderSteps += step | ||
} | ||
|
||
/** Directly edit the command in Brigadier. */ | ||
inline fun edit(crossinline apply: IdoArgBuilder.() -> ArgumentBuilder<*, *>) { | ||
add(RenderStep.Apply { apply() as IdoArgBuilder }) | ||
} | ||
|
||
internal fun render(): List<RenderedCommand> { | ||
return renderSteps.foldRight(listOf()) { step, acc -> | ||
step.reduce(acc) | ||
} | ||
} | ||
|
||
internal fun build(): LiteralCommandNode<CommandSourceStack> { | ||
render().fold(initial as IdoArgBuilder) { acc, curr -> | ||
curr.foldLeft(acc) | ||
} | ||
return initial.build() | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
...commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoCommandContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
import com.mineinabyss.idofront.commands.execution.CommandExecutionFailedException | ||
import com.mineinabyss.idofront.textcomponents.miniMsg | ||
import com.mojang.brigadier.context.CommandContext | ||
import io.papermc.paper.command.brigadier.CommandSourceStack | ||
import io.papermc.paper.command.brigadier.argument.resolvers.ArgumentResolver | ||
import net.kyori.adventure.text.Component | ||
import org.bukkit.Location | ||
import org.bukkit.command.CommandSender | ||
import org.bukkit.entity.Entity | ||
|
||
@Annotations | ||
@Suppress("UnstableApiUsage") | ||
open class IdoCommandContext( | ||
val context: CommandContext<CommandSourceStack>, | ||
) { | ||
/** Stops the command, sending a [message] formatted with MiniMessage to its [sender]. */ | ||
fun commandException(message: String): Nothing = throw CommandExecutionFailedException(message.miniMsg()) | ||
|
||
/** Stops the command, sending a [message] to its [sender]. */ | ||
fun commandException(message: Component): Nothing = throw CommandExecutionFailedException(message) | ||
|
||
/** The sender that ran this command. */ | ||
val sender: CommandSender = context.source.sender | ||
|
||
/** An entity representing the [sender] on the server. */ | ||
val executor: Entity? = context.source.executor | ||
|
||
val location: Location = context.source.location | ||
|
||
@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 ((this as IdoArgument<Any?>).invoke() as ArgumentResolver<T>) | ||
.resolve(context.source) | ||
} | ||
|
||
@JvmName("invoke2") | ||
inline operator fun <reified T> IdoArgument<T>.invoke(): T { | ||
return context.getArgumentOrNull<T>(name) | ||
?: 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() | ||
} |
13 changes: 13 additions & 0 deletions
13
...ds/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoPlayerCommandContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
import com.mojang.brigadier.context.CommandContext | ||
import io.papermc.paper.command.brigadier.CommandSourceStack | ||
import org.bukkit.entity.Player | ||
|
||
@Annotations | ||
@Suppress("UnstableApiUsage") | ||
class IdoPlayerCommandContext( | ||
context: CommandContext<CommandSourceStack>, | ||
): IdoCommandContext(context) { | ||
val player = executor as Player | ||
} |
15 changes: 15 additions & 0 deletions
15
...nt-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoRootCommand.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
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?, | ||
val aliases: List<String>, | ||
plugin: Plugin, | ||
) : IdoCommand(initial, name, plugin) |
22 changes: 22 additions & 0 deletions
22
...ands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/IdoSuggestionsContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
import com.mojang.brigadier.context.CommandContext | ||
import com.mojang.brigadier.suggestion.SuggestionsBuilder | ||
import io.papermc.paper.command.brigadier.CommandSourceStack | ||
|
||
@Suppress("UnstableApiUsage") | ||
data class IdoSuggestionsContext( | ||
val context: CommandContext<CommandSourceStack>, | ||
val suggestions: SuggestionsBuilder, | ||
) { | ||
/** Add a suggestion, filtering it as the user types. */ | ||
fun suggestFiltering(name: String) { | ||
if (name.startsWith(suggestions.remaining, ignoreCase = true)) | ||
suggestions.suggest(name) | ||
} | ||
|
||
/** Add a list of suggestions, filtering them as the user types. */ | ||
fun suggest(list: List<String>) { | ||
list.forEach { suggestFiltering(it) } | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RenderStep.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
/** | ||
* These are emitted line by line to reflect what a user specifies in the DSL. | ||
*/ | ||
@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 | ||
} | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...t-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RenderedCommand.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.mineinabyss.idofront.commands.brigadier | ||
|
||
/** | ||
* [RenderStep]s get reduced into a list of commands that more directly represent Brigadier's builder structure. | ||
* | ||
* This lets us write more complex nodes more easily. | ||
*/ | ||
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) }) | ||
} | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
...t-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/RootIdoCommands.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
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, | ||
) { | ||
@PublishedApi | ||
internal val rootCommands = mutableListOf<IdoRootCommand>() | ||
|
||
/** Creates a new subcommand via a [Commands.literal] argument. */ | ||
inline operator fun String.invoke(aliases: List<String> = emptyList(), description: String? = null, init: IdoRootCommand.() -> Unit) { | ||
rootCommands += IdoRootCommand( | ||
Commands.literal(this), | ||
this, | ||
description, | ||
aliases, | ||
plugin, | ||
).apply(init) | ||
} | ||
|
||
/** Creates a new subcommand with aliases via a [Commands.literal] argument. */ | ||
inline operator fun List<String>.invoke(description: String? = null, init: IdoRootCommand.() -> Unit) = | ||
firstOrNull()?.invoke(aliases = drop(1), description = description, init = init) | ||
|
||
/** Builder for commands with aliases. */ | ||
operator fun String.div(other: String) = listOf(this, other) | ||
|
||
/** Builder for commands with aliases. */ | ||
operator fun List<String>.div(other: String) = listOf(this) + other | ||
|
||
/** Builds and registers each root level command defined in the DSL. */ | ||
@PublishedApi | ||
internal fun buildEach() { | ||
rootCommands.forEach { command -> | ||
commands.register( | ||
command.build(), | ||
command.description, | ||
command.aliases | ||
) | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
idofront-commands/src/main/kotlin/com/mineinabyss/idofront/commands/brigadier/TypeAliases.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +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 |
Oops, something went wrong.