/*
* Copyright (C) 2023 Linnea Gräf
*
* This file is part of NotEnoughUpdates.
*
* NotEnoughUpdates is free software: you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* NotEnoughUpdates is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with NotEnoughUpdates. If not, see .
*/
package io.github.moulberry.notenoughupdates.util.brigadier
import com.mojang.brigadier.arguments.ArgumentType
import com.mojang.brigadier.builder.ArgumentBuilder
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.builder.RequiredArgumentBuilder
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.tree.ArgumentCommandNode
import com.mojang.brigadier.tree.CommandNode
import com.mojang.brigadier.tree.LiteralCommandNode
import io.github.moulberry.notenoughupdates.commands.dev.DevTestCommand
import io.github.moulberry.notenoughupdates.util.iterate
import net.minecraft.command.ICommandSender
import net.minecraft.util.ChatComponentText
import net.minecraft.util.IChatComponent
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
typealias DefaultSource = ICommandSender
private fun normalizeGeneric(argument: Type): Class<*> {
return if (argument is Class<*>) {
argument
} else if (argument is TypeVariable<*>) {
normalizeGeneric(argument.bounds[0])
} else if (argument 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 {
return ctx.getArgument(name, argClass) as T
}
}
fun > C.reply(component: IChatComponent) {
source.addChatMessage(ChatComponentText("§e[NEU] ").appendSibling(component))
}
@JvmOverloads
fun > C.reply(text: String, block: ChatComponentText.() -> Unit = {}) {
source.addChatMessage(ChatComponentText(text.split("\n").joinToString("\n") { "§e[NEU] $it" }).also(block))
}
operator fun > C.get(arg: TypeSafeArg): T {
return arg.get(this)
}
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
): ArgumentCommandNode = argument(name, argument, block).build().also(::then)
fun , AT : Any> T.thenArgumentExecute(
name: String,
argument: ArgumentType,
block: CommandContext.(TypeSafeArg) -> Unit
): ArgumentCommandNode = thenArgument(name, argument) {
thenExecute {
block(it)
}
}
fun literal(
name: String,
block: LiteralArgumentBuilder.() -> Unit = {}
): LiteralArgumentBuilder =
LiteralArgumentBuilder.literal(name).also(block)
fun > T.thenLiteral(
name: String,
block: LiteralArgumentBuilder.() -> Unit
): LiteralCommandNode =
then(literal(name), block) as LiteralCommandNode
fun > T.thenLiteralExecute(
name: String,
block: CommandContext.() -> Unit
): LiteralCommandNode =
thenLiteral(name) {
thenExecute(block)
}
fun > T.thenRedirect(node: CommandNode): T {
node.children.forEach {
this.then(it)
}
forward(node.redirect, node.redirectModifier, node.isFork)
executes(node.command)
return this
}
fun , U : ArgumentBuilder> T.then(
node: U,
block: U.() -> Unit
): CommandNode =
node.also(block).build().also(::then)
fun > T.thenExecute(block: CommandContext.() -> Unit): T =
executes {
block(it)
1
}
fun > T.requiresDev(): T {
requires { DevTestCommand.isDeveloper(it) }
return this
}
fun NEUBrigadierHook.withHelp(helpText: String): NEUBrigadierHook {
commandNode.withHelp(helpText)
return this
}
fun > T.withHelp(helpText: String): T {
BrigadierRoot.setHelpForNode(this, helpText)
return this
}
fun > T.suggestsList(list: List) {
suggestsList { list }
}
fun > T.suggestsList(list: () -> List) {
suggests { context, builder ->
list().filter { it.startsWith(builder.remaining, ignoreCase = true) }
.forEach { builder.suggest(it) }
builder.buildFuture()
}
}