Status
Goal
The goal is to find a way to create multiplatform commands using mojang's Brigadier.
Command Builder API
Using paper, Forge we are using something like that
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
Commands.literal(literal)
Commands.argument(name, argumentType)
Which leads us to create
interface MultiplatformCommands<CommandSourceStack> {
fun literal(literal: String): LiteralArgumentBuilder<CommandSourceStack>
fun <T : Any> argument(
name: String,
argumentType: ArgumentType<T>
): RequiredArgumentBuilder<CommandSourceStack, T>
}
After, we can create multiplatform commands
class MultiplatformCommand<CommandSourceStack>(
val commands: MultiplatformCommands<CommandSourceStack>
) {
fun command(
alias: String,
block: LiteralArgumentBuilder<CommandSourceStack>.() -> Unit
): LiteralArgumentBuilder<CommandSourceStack> {
val literal = commands.literal(alias)
literal.block()
return literal
}
fun LiteralArgumentBuilder<CommandSourceStack>.literal(
alias: String,
block: LiteralArgumentBuilder<CommandSourceStack>.() -> Unit
) {
val literal = commands.literal(alias)
literal.block()
this.then(literal)
}
data class BrigadierArgument<T : Any>(
val alias: String,
val type: ArgumentType<T>,
val clazz: Class<T>
)
inline fun <reified T : Any> LiteralArgumentBuilder<CommandSourceStack>.argument(
alias: String,
type: ArgumentType<T>,
noinline block: RequiredArgumentBuilder<CommandSourceStack, T>.(BrigadierArgument<T>) -> Unit
) = argument(
alias = alias,
type = type,
clazz = T::class.java,
block = block
)
fun <T : Any> LiteralArgumentBuilder<CommandSourceStack>.argument(
alias: String,
type: ArgumentType<T>,
clazz: Class<T>,
block: RequiredArgumentBuilder<CommandSourceStack, T>.(BrigadierArgument<T>) -> Unit
) {
val argument = commands.argument(alias, type)
val brigadierArgument = BrigadierArgument(
alias = alias,
type = type,
clazz = clazz
)
argument.block(brigadierArgument)
this.then(argument)
}
inline fun <reified T : Any> RequiredArgumentBuilder<CommandSourceStack, *>.argument(
alias: String,
type: ArgumentType<T>,
noinline block: RequiredArgumentBuilder<CommandSourceStack, T>.(BrigadierArgument<T>) -> Unit
) = argument(
alias = alias,
type = type,
clazz = T::class.java,
block = block
)
fun <T : Any> RequiredArgumentBuilder<CommandSourceStack, *>.argument(
alias: String,
type: ArgumentType<T>,
clazz: Class<T>,
block: RequiredArgumentBuilder<CommandSourceStack, T>.(BrigadierArgument<T>) -> Unit
) {
val argument = commands.argument(alias, type)
val brigadierArgument = BrigadierArgument(
alias = alias,
type = type,
clazz = clazz
)
argument.block(brigadierArgument)
this.then(argument)
}
fun RequiredArgumentBuilder<CommandSourceStack, *>.runs(
onFailure: (CommandContext<CommandSourceStack>, Throwable) -> Unit = { _, _ -> },
block: (RequiredArgumentBuilder<CommandSourceStack, *>.(CommandContext<CommandSourceStack>) -> Unit)
) {
executes { ctx ->
runCatching { block.invoke(this, ctx) }
.onFailure { onFailure.invoke(ctx, it) }
Command.SINGLE_SUCCESS
}
}
fun LiteralArgumentBuilder<CommandSourceStack>.runs(
onFailure: (CommandContext<CommandSourceStack>, Throwable) -> Unit = { _, _ -> },
block: LiteralArgumentBuilder<CommandSourceStack>.(CommandContext<CommandSourceStack>) -> Unit
) {
executes { ctx ->
runCatching { block.invoke(this, ctx) }
.onFailure { onFailure.invoke(ctx, it) }
Command.SINGLE_SUCCESS
}
}
fun RequiredArgumentBuilder<CommandSourceStack, *>.hints(block: (CommandContext<CommandSourceStack>) -> List<String>) {
suggests { context, builder ->
block.invoke(context).forEach(builder::suggest)
builder.buildFuture()
}
}
@Throws(IllegalArgumentException::class)
fun <T : Any> CommandContext<CommandSourceStack>.requireArgument(bArgument: BrigadierArgument<T>): T {
return getArgument(bArgument.alias, bArgument.clazz)
}
@Throws(ArgumentConverterException::class)
fun <T : Any> CommandContext<CommandSourceStack>.requireArgument(
bArgument: BrigadierArgument<String>,
converter: ArgumentConverter<T>
): T {
val string = getArgument(bArgument.alias, bArgument.clazz)
return converter.transform(string)
}
}
Command Context problem
However, we can't use CommandContext here
val command = MultiplatformCommand(PaperMultiplatformCommands)
fun multiplatform(command: MultiplatformCommand<*>) {
with(command) {
command("") {
literal("") {
argument("asd", StringArgumentType.string()) { strArg ->
runs { ctx ->
val str = ctx.requireArgument(strArg)
val custom = ctx.requireArgument(strArg, IntArgumentConverter)
// The problem
val source: Any? = ctx.source
}
}
}
}
}
}
Which means, we should create custom CommandContext wrapper
sealed interface MultiplatformCommandExecutor {
data class Player(val player: MinecraftPlayer) : MultiplatformCommandExecutor
data object Console : MultiplatformCommandExecutor
}
interface MultiplatformCommandContext {
val executor: MultiplatformCommandExecutor
}
interface MultiplatformCommands<CommandSourceStack> {
// ....
fun CommandContext<CommandSourceStack>.toMultiplatformCommandContext(): MultiplatformCommandContext
}
class PaperMultiplatformCommandContext(
ctx: CommandContext<CommandSourceStack>
) : MultiplatformCommandContext {
override val executor: MultiplatformCommandExecutor = let {
(ctx.source.sender as? ConsoleCommandSender)?.let {
MultiplatformCommandExecutor.Console
} ?: (ctx.source.sender as? Player)?.let {
MultiplatformCommandExecutor.Player(it.asOnlineMinecraftPlayer())
} ?: error("Unknown executor")
}
}
val command = MultiplatformCommand(PaperMultiplatformCommands)
fun multiplatform(command: MultiplatformCommand<CommandSourceStack>) {
with(command) {
command("") {
literal("") {
argument("asd", StringArgumentType.string()) { strArg ->
runs { ctx ->
val str = ctx.requireArgument(strArg)
val custom = ctx.requireArgument(strArg, IntArgumentConverter)
// The problem
val source: Any? = ctx.source
val mppCtx = ctx.toMultiplatformCommandContext()
val executor: MultiplatformCommandExecutor = mppCtx.executor
}
}
}
}
}
}
Status
Goal
The goal is to find a way to create multiplatform commands using mojang's Brigadier.
Command Builder API
Using paper, Forge we are using something like that
Which leads us to create
After, we can create multiplatform commands
Command Context problem
However, we can't use CommandContext here
Which means, we should create custom CommandContext wrapper