package moe.nea.firmament.commands import com.mojang.brigadier.arguments.ArgumentType import com.mojang.brigadier.builder.ArgumentBuilder import com.mojang.brigadier.builder.RequiredArgumentBuilder import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.suggestion.SuggestionProvider import kotlinx.coroutines.launch import moe.nea.firmament.Firmament import moe.nea.firmament.util.MinecraftDispatcher import moe.nea.firmament.util.iterate import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.TypeVariable typealias DefaultSource = FabricClientCommandSource inline val > T.context get() = this operator fun > C.get(arg: TypeSafeArg): T { return arg.get(this) } fun literal( name: String, block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit ): CaseInsensitiveLiteralCommandNode.Builder = CaseInsensitiveLiteralCommandNode.Builder(name).also(block) private fun normalizeGeneric(argument: Type): Class<*> { return when (argument) { is Class<*> -> argument is TypeVariable<*> -> normalizeGeneric(argument.bounds[0]) is ParameterizedType -> normalizeGeneric(argument.rawType) else -> Any::class.java } } data class TypeSafeArg(val name: String, val argument: ArgumentType) { val argClass by lazy { argument.javaClass .iterate>> { it.superclass } .flatMap { it.genericInterfaces.toList() } .filterIsInstance() .find { it.rawType == ArgumentType::class.java }!! .let { normalizeGeneric(it.actualTypeArguments[0]) } } @JvmName("getWithThis") fun CommandContext.get(): T = get(this) fun get(ctx: CommandContext): T { try { return ctx.getArgument(name, argClass) as T } catch (e: Exception) { if (ctx.child != null) { return get(ctx.child) } throw e } } } fun argument( name: String, argument: ArgumentType, block: RequiredArgumentBuilder.(TypeSafeArg) -> Unit ): RequiredArgumentBuilder = RequiredArgumentBuilder.argument(name, argument).also { block(it, TypeSafeArg(name, argument)) } fun , AT : Any> T.thenArgument( name: String, argument: ArgumentType, block: RequiredArgumentBuilder.(TypeSafeArg) -> Unit ): T = then(argument(name, argument, block)) fun > T.suggestsList(provider: CommandContext.() -> Iterable) { suggests(SuggestionProvider { context, builder -> provider(context) .asSequence() .filter { it.startsWith(builder.remaining, ignoreCase = true) } .forEach { builder.suggest(it) } builder.buildFuture() }) } fun > T.thenLiteral( name: String, block: CaseInsensitiveLiteralCommandNode.Builder.() -> Unit ): T = then(literal(name, block)) fun > T.then(node: ArgumentBuilder, block: T.() -> Unit): T = then(node).also(block) fun > T.thenExecute(block: suspend CommandContext.() -> Unit): T = executes { Firmament.coroutineScope.launch(MinecraftDispatcher) { block(it) } 1 }