diff options
author | nea <nea@nea.moe> | 2023-01-20 09:49:36 +0100 |
---|---|---|
committer | nea <nea@nea.moe> | 2023-01-31 21:20:18 +0100 |
commit | 9fabe241cfe613b836335aa918b1a2423bfde31b (patch) | |
tree | 8e6a7d463e9888e28d6db31f0eebd7162f22a3b9 | |
parent | 291860435d5487562b122aaddbe57c178a8734de (diff) | |
download | NotEnoughUpdates-9fabe241cfe613b836335aa918b1a2423bfde31b.tar.gz NotEnoughUpdates-9fabe241cfe613b836335aa918b1a2423bfde31b.tar.bz2 NotEnoughUpdates-9fabe241cfe613b836335aa918b1a2423bfde31b.zip |
brigadier wip
7 files changed, 506 insertions, 1 deletions
diff --git a/build.gradle.kts b/build.gradle.kts index e41aa513..4dc253a9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -139,6 +139,8 @@ dependencies { annotationProcessor("org.projectlombok:lombok:1.18.24") "oneconfigAnnotationProcessor"("org.projectlombok:lombok:1.18.24") + shadowImplementation("com.mojang:brigadier:1.0.18") + shadowImplementation("org.spongepowered:mixin:0.7.11-SNAPSHOT") { isTransitive = false // Dependencies of mixin are already bundled by minecraft } @@ -233,6 +235,7 @@ tasks.shadowJar { from(kotlinDependencyCollectionJar) dependsOn(kotlinDependencyCollectionJar) fun relocate(name: String) = relocate(name, "io.github.moulberry.notenoughupdates.deps.$name") + relocate("com.mojang.brigadier") } tasks.assemble.get().dependsOn(remapJar) diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java index cf266dca..3a2c39ca 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java @@ -19,6 +19,8 @@ package io.github.moulberry.notenoughupdates.commands.dev; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import io.github.moulberry.notenoughupdates.BuildFlags; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.commands.ClientCommandBase; @@ -49,9 +51,12 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; +import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument; + public class DevTestCommand extends ClientCommandBase { - private static final List<String> DEV_TESTERS = + public static final List<String> DEV_TESTERS = Arrays.asList( "d0e05de7-6067-454d-beae-c6d19d886191", // moulberry "66502b40-6ac1-4d33-950d-3df110297aab", // lucycoconut diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/EnumArgumentType.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/EnumArgumentType.kt new file mode 100644 index 00000000..14b6ed6e --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/EnumArgumentType.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * 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 <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util.brigadier + +import com.mojang.brigadier.LiteralMessage +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import java.util.concurrent.CompletableFuture + +class EnumArgumentType<T : Enum<T>>( + val values: List<T> +) : ArgumentType<T> { + companion object { + @JvmStatic + fun <T : Enum<T>> enum(values: Array<T>) = EnumArgumentType(values.toList()) + + inline fun <reified T : Enum<T>> enum() = enum(enumValues<T>()) + } + + override fun getExamples(): Collection<String> { + return values.map { it.name } + } + + override fun <S : Any?> listSuggestions( + context: CommandContext<S>, + builder: SuggestionsBuilder + ): CompletableFuture<Suggestions> { + + examples + .filter {builder.remaining.isBlank() || it.startsWith(builder.remaining, ignoreCase = true) } + .forEach { builder.suggest(it) } + return builder.buildFuture() + } + + private val invalidEnum = + SimpleCommandExceptionType(LiteralMessage("Expected one of: ${values.joinToString(", ")}")) + + override fun parse(reader: StringReader): T { + val enumName = reader.readString() + return values.find { enumName == it.name } + ?: throw invalidEnum.createWithContext(reader) + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/RestArgumentType.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/RestArgumentType.kt new file mode 100644 index 00000000..adfdae6a --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/RestArgumentType.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * 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 <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util.brigadier + +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType + +object RestArgumentType : ArgumentType<String> { + override fun parse(reader: StringReader): String { + val remaining = reader.remaining + reader.cursor += remaining.length + return remaining + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/Rome.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/Rome.kt new file mode 100644 index 00000000..7f8f3f74 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/Rome.kt @@ -0,0 +1,241 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util.brigadier + +import com.mojang.brigadier.CommandDispatcher +import com.mojang.brigadier.ParseResults +import com.mojang.brigadier.arguments.StringArgumentType.string +import com.mojang.brigadier.exceptions.CommandSyntaxException +import com.mojang.brigadier.suggestion.Suggestions +import io.github.moulberry.notenoughupdates.BuildFlags +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe +import io.github.moulberry.notenoughupdates.commands.dev.DevTestCommand.DEV_TESTERS +import io.github.moulberry.notenoughupdates.core.config.GuiPositionEditor +import io.github.moulberry.notenoughupdates.core.util.MiscUtils +import io.github.moulberry.notenoughupdates.miscfeatures.FishingHelper +import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.CustomBiomes +import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.LocationChangeEvent +import io.github.moulberry.notenoughupdates.miscgui.GuiPriceGraph +import io.github.moulberry.notenoughupdates.miscgui.minionhelper.MinionHelperManager +import io.github.moulberry.notenoughupdates.util.LRUCache +import io.github.moulberry.notenoughupdates.util.PronounDB +import io.github.moulberry.notenoughupdates.util.SBInfo +import io.github.moulberry.notenoughupdates.util.TabListUtils +import io.github.moulberry.notenoughupdates.util.brigadier.EnumArgumentType.Companion.enum +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.GuiScreen +import net.minecraft.command.CommandBase +import net.minecraft.command.ICommandSender +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.launchwrapper.Launch +import net.minecraft.util.BlockPos +import net.minecraft.util.ChatComponentText +import net.minecraft.util.EnumChatFormatting +import net.minecraft.util.EnumChatFormatting.GOLD +import net.minecraft.util.EnumChatFormatting.RED +import net.minecraft.util.EnumParticleTypes +import net.minecraftforge.client.ClientCommandHandler +import net.minecraftforge.common.MinecraftForge +import java.util.concurrent.CompletableFuture + +@NEUAutoSubscribe +object Rome { + val dispatcher = CommandDispatcher<DefaultSource>() + private val parseText = + LRUCache.memoize<Pair<ICommandSender, String>, ParseResults<DefaultSource>>({ (sender, text) -> + dispatcher.parse(text, sender) + }, 1) + + init { + dispatcher.register(literal("neudevtest2") { + requires { + DEV_TESTERS.contains((it as? EntityPlayer)?.uniqueID?.toString()) + || Launch.blackboard.get("fml.deobfuscatedEnvironment") as Boolean + } + thenLiteralExecute("profileinfo") { + val currentProfile = SBInfo.getInstance().currentProfile + val gamemode = SBInfo.getInstance().getGamemodeForProfile(currentProfile) + reply("${GOLD}You are on Profile $currentProfile with the mode $gamemode") + } + thenLiteralExecute("buildflags") { + reply("BuildFlags: \n" + + BuildFlags.getAllFlags().entries + .joinToString(("\n")) { (key, value) -> " + $key - $value" }) + } + thenLiteral("exteditor") { + thenExecute { + reply("Your external editor is: §Z${NotEnoughUpdates.INSTANCE.config.hidden.externalEditor}") + } + thenArgument("editor", string()) { newEditor -> + thenExecute { + NotEnoughUpdates.INSTANCE.config.hidden.externalEditor = this[newEditor] + reply("You changed your external editor to: §Z${this[newEditor]}") + } + } + } + thenLiteral("pricetest") { + thenExecute { + NotEnoughUpdates.INSTANCE.manager.auctionManager.updateBazaar() + } + thenArgument("item", string()) { item -> + thenExecute { + NotEnoughUpdates.INSTANCE.openGui = GuiPriceGraph(this[item]) + } + } + } + thenLiteralExecute("zone") { + val target = Minecraft.getMinecraft().objectMouseOver.blockPos + ?: Minecraft.getMinecraft().thePlayer.position + val zone = CustomBiomes.INSTANCE.getSpecialZone(target) + listOf( + ChatComponentText("Showing Zone Info for: $target"), + ChatComponentText("Zone: " + (zone?.name ?: "null")), + ChatComponentText("Location: " + SBInfo.getInstance().getLocation()), + ChatComponentText("Biome: " + CustomBiomes.INSTANCE.getCustomBiome(target)) + ).forEach { component -> + reply(component) + } + MinecraftForge.EVENT_BUS.post( + LocationChangeEvent( + SBInfo.getInstance().getLocation(), SBInfo + .getInstance() + .getLocation() + ) + ) + } + thenLiteralExecute("positiontest") { + NotEnoughUpdates.INSTANCE.openGui = GuiPositionEditor() + } + thenLiteral("pt") { + thenArgument("particle", enum<EnumParticleTypes>()) { particle -> + thenExecute { + FishingHelper.type = this[particle] + reply("Fishing particles set to ${FishingHelper.type}") + } + } + } + thenLiteralExecute("dev") { + NotEnoughUpdates.INSTANCE.config.hidden.dev = !NotEnoughUpdates.INSTANCE.config.hidden.dev + reply("§e[NEU] Dev mode " + if (NotEnoughUpdates.INSTANCE.config.hidden.dev) "§aenabled" else "§cdisabled") + } + thenLiteralExecute("saveconfig") { + NotEnoughUpdates.INSTANCE.saveConfig() + reply("Config saved") + } + thenLiteralExecute("searchmode") { + NotEnoughUpdates.INSTANCE.config.hidden.firstTimeSearchFocus = true + reply(EnumChatFormatting.AQUA.toString() + "I would never search") + } + thenLiteralExecute("bluehair") { + PronounDB.test() + } + thenLiteral("opengui") { + thenArgument("class", string()) { className -> + thenExecute { + try { + NotEnoughUpdates.INSTANCE.openGui = + Class.forName(this[className]).newInstance() as GuiScreen + reply("Opening gui: " + NotEnoughUpdates.INSTANCE.openGui) + } catch (e: Exception) { + e.printStackTrace() + reply("Failed to open this GUI.") + } + } + } + } + thenLiteralExecute("center") { + val x = Math.floor(Minecraft.getMinecraft().thePlayer.posX) + 0.5f + val z = Math.floor(Minecraft.getMinecraft().thePlayer.posZ) + 0.5f + Minecraft.getMinecraft().thePlayer.setPosition(x, Minecraft.getMinecraft().thePlayer.posY, z) + reply("Literal hacks") + } + thenLiteral("minion") { + thenArgument("args", RestArgumentType) { arg -> + thenExecute { + MinionHelperManager.getInstance().handleCommand(arrayOf("minion") + this[arg].split(" ")) + } + } + } + thenLiteralExecute("copytablist") { + val tabList = TabListUtils.getTabList().joinToString("\n", postfix = "\n") + MiscUtils.copyToClipboard(tabList) + reply("Copied tablist to clipboard!") + } + }) + updateHooks() + } + + fun updateHooks() = registerHooks(ClientCommandHandler.instance) + + fun registerHooks(handler: ClientCommandHandler) { + dispatcher.root.children.forEach { commandNode -> + if (commandNode.name in handler.commands) return@forEach + handler.registerCommand(object : CommandBase() { + override fun getCommandName(): String { + return commandNode.name + } + + override fun getCommandUsage(sender: ICommandSender): String { + return dispatcher.getAllUsage(commandNode, sender, true).joinToString("\n") + } + + fun getText(args: Array<out String>) = "${commandNode.name} ${args.joinToString(" ")}" + + override fun processCommand(sender: ICommandSender, args: Array<out String>) { + val results = parseText.apply(sender to getText(args).trim()) + try { + dispatcher.execute(results) + } catch (syntax: CommandSyntaxException) { + sender.addChatMessage(ChatComponentText("${RED}${syntax.message}")) + } + } + + var lastCompletionText: String? = null + var lastCompletion: CompletableFuture<Suggestions>? = null + + override fun addTabCompletionOptions( + sender: ICommandSender, + args: Array<out String>, + pos: BlockPos + ): List<String> { + val originalText = getText(args) + var lc: CompletableFuture<Suggestions>? = null + if (lastCompletionText == originalText) { + lc = lastCompletion + } + if (lc == null) { + lastCompletion?.cancel(true) + val results = parseText.apply(sender to originalText) + lc = dispatcher.getCompletionSuggestions(results) + } + lastCompletion = lc + lastCompletionText = originalText + val suggestions = lastCompletion?.getNow(null) ?: return emptyList() + return suggestions.list.map { it.text } + } + + override fun canCommandSenderUseCommand(sender: ICommandSender): Boolean { + return true + } + }) + } + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/dsl.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/dsl.kt new file mode 100644 index 00000000..b054f6d9 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/dsl.kt @@ -0,0 +1,133 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +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 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 + +fun literal( + name: String, + block: LiteralArgumentBuilder<DefaultSource>.() -> Unit +): LiteralArgumentBuilder<DefaultSource> = + LiteralArgumentBuilder.literal<DefaultSource>(name).also(block) + +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<T : Any>(val name: String, val argument: ArgumentType<T>) { + val argClass by lazy { + argument.javaClass + .iterate<Class<in ArgumentType<T>>> { + it.superclass + } + .flatMap { + it.genericInterfaces.toList() + } + .filterIsInstance<ParameterizedType>() + .find { it.rawType == ArgumentType::class.java }!! + .let { + normalizeGeneric(it.actualTypeArguments[0]) + } + } + + @JvmName("getWithThis") + fun <S> CommandContext<S>.get(): T = + get(this) + + + fun <S> get(ctx: CommandContext<S>): T { + return ctx.getArgument(name, argClass) as T + } +} + +fun <T : ICommandSender, C : CommandContext<T>> C.reply(component: IChatComponent) { + source.addChatMessage(component) +} + +fun <T : ICommandSender, C : CommandContext<T>> C.reply(text: String) { + source.addChatMessage(ChatComponentText(text)) +} + +operator fun <T : Any, C : CommandContext<*>> C.get(arg: TypeSafeArg<T>): T { + return arg.get(this) +} + + +fun <T : Any> argument( + name: String, + argument: ArgumentType<T>, + block: RequiredArgumentBuilder<DefaultSource, T>.(TypeSafeArg<T>) -> Unit +): RequiredArgumentBuilder<DefaultSource, T> = + RequiredArgumentBuilder.argument<DefaultSource, T>(name, argument).also { block(it, TypeSafeArg(name, argument)) } + +fun <T : ArgumentBuilder<DefaultSource, T>, AT : Any> T.thenArgument( + name: String, + argument: ArgumentType<AT>, + block: RequiredArgumentBuilder<DefaultSource, AT>.(TypeSafeArg<AT>) -> Unit +): T = then(argument(name, argument, block)) + + +fun <T : ArgumentBuilder<DefaultSource, T>> T.thenLiteral( + name: String, + block: LiteralArgumentBuilder<DefaultSource>.() -> Unit +): T = + then(literal(name, block)) + + +fun <T : ArgumentBuilder<DefaultSource, T>> T.thenLiteralExecute( + name: String, + block: CommandContext<DefaultSource>.() -> Unit +): T = + thenLiteral(name) { + thenExecute(block) + } + +fun <T : ArgumentBuilder<DefaultSource, T>> T.then(node: ArgumentBuilder<DefaultSource, *>, block: T.() -> Unit): T = + then(node).also(block) + +fun <T : ArgumentBuilder<DefaultSource, T>> T.thenExecute(block: CommandContext<DefaultSource>.() -> Unit): T = + executes { + block(it) + 1 + } + + diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/iterate.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/iterate.kt new file mode 100644 index 00000000..bcfe11aa --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/iterate.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * 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 <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util + +fun <T : Any> T.iterate(evolve: (T) -> T?): Sequence<T> = sequence { + var pointer: T? = this@iterate + while (pointer != null) { + yield(pointer) + pointer = evolve(pointer) + } +} |