diff options
author | Linnea Gräf <nea@nea.moe> | 2025-06-22 22:45:16 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2025-06-22 22:45:16 +0200 |
commit | 1ab9094bde904b9671acf29c700e3c98d503c8fe (patch) | |
tree | ea0c2dc731a46a65a30748be01720c096ab3f83d | |
parent | fbc44e21393758ddf92397446d8e19f3f39a7317 (diff) | |
download | Firmament-1ab9094bde904b9671acf29c700e3c98d503c8fe.tar.gz Firmament-1ab9094bde904b9671acf29c700e3c98d503c8fe.tar.bz2 Firmament-1ab9094bde904b9671acf29c700e3c98d503c8fe.zip |
feat: Add npc location exporter
-rw-r--r-- | src/main/kotlin/features/debug/PowerUserTools.kt | 1 | ||||
-rw-r--r-- | src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt | 48 | ||||
-rw-r--r-- | src/main/kotlin/features/debug/itemeditor/ItemExporter.kt | 21 | ||||
-rw-r--r-- | src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt | 3 | ||||
-rw-r--r-- | src/main/kotlin/features/debug/itemeditor/PromptScreen.kt | 15 | ||||
-rw-r--r-- | src/main/kotlin/util/HoveredItemStack.kt | 2 | ||||
-rw-r--r-- | src/main/kotlin/util/MoulConfigUtils.kt | 39 | ||||
-rw-r--r-- | src/main/kotlin/util/async/input.kt | 108 |
8 files changed, 179 insertions, 58 deletions
diff --git a/src/main/kotlin/features/debug/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt index 03be15e..7c1df3f 100644 --- a/src/main/kotlin/features/debug/PowerUserTools.kt +++ b/src/main/kotlin/features/debug/PowerUserTools.kt @@ -58,6 +58,7 @@ object PowerUserTools : FirmamentFeature { val copyTitle by keyBindingWithDefaultUnbound("copy-title") val exportItemStackToRepo by keyBindingWithDefaultUnbound("export-item-stack") val exportUIRecipes by keyBindingWithDefaultUnbound("export-recipe") + val exportNpcLocation by keyBindingWithDefaultUnbound("export-npc-location") } override val config diff --git a/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt b/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt index 9306c84..588aba1 100644 --- a/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt +++ b/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt @@ -1,19 +1,27 @@ package moe.nea.firmament.features.debug.itemeditor +import kotlinx.coroutines.launch import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive +import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.entity.decoration.ArmorStandEntity +import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.HandledScreenKeyPressedEvent +import moe.nea.firmament.events.WorldKeyboardEvent import moe.nea.firmament.features.debug.PowerUserTools import moe.nea.firmament.repo.ItemNameLookup import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData import moe.nea.firmament.util.SHORT_NUMBER_FORMAT import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.async.waitForTextInput import moe.nea.firmament.util.ifDropLast import moe.nea.firmament.util.mc.ScreenUtil.getSlotByIndex import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt +import moe.nea.firmament.util.mc.setSkullOwner import moe.nea.firmament.util.parseShortNumber import moe.nea.firmament.util.red import moe.nea.firmament.util.removeColorCodes @@ -32,11 +40,47 @@ object ExportRecipe { val x = it % 3 val y = it / 3 - (xNames[x].toString() + yNames[y]) to x + y * 9 + 10 + (yNames[y].toString() + xNames[x].toString()) to x + y * 9 + 10 } val resultSlot = 25 @Subscribe + fun exportNpcLocation(event: WorldKeyboardEvent) { + if (!event.matches(PowerUserTools.TConfig.exportNpcLocation)) { + return + } + val entity = MC.instance.targetedEntity + if (entity == null) { + MC.sendChat(tr("firmament.repo.export.npc.noentity", "Could not find entity to export")) + return + } + Firmament.coroutineScope.launch { + val guessName = entity.world.getEntitiesByClass( + ArmorStandEntity::class.java, + entity.boundingBox.expand(0.1), + { !it.name.string.contains("CLICK") }) + .firstOrNull()?.customName?.string + ?: "" + val reply = waitForTextInput("$guessName (NPC)", "Export stub") + val id = generateName(reply) + ItemExporter.exportStub(id, reply) { + val playerEntity = entity as? ClientPlayerEntity + val textureUrl = playerEntity?.skinTextures?.textureUrl + if (textureUrl != null) + it.setSkullOwner(playerEntity.uuid, textureUrl) + } + ItemExporter.modifyJson(id) { + val mutJson = it.toMutableMap() + mutJson["island"] = JsonPrimitive(SBData.skyblockLocation?.locrawMode ?: "unknown") + mutJson["x"] = JsonPrimitive(entity.blockX) + mutJson["y"] = JsonPrimitive(entity.blockY) + mutJson["z"] = JsonPrimitive(entity.blockZ) + JsonObject(mutJson) + } + } + } + + @Subscribe fun onRecipeKeyBind(event: HandledScreenKeyPressedEvent) { if (!event.matches(PowerUserTools.TConfig.exportUIRecipes)) { return @@ -138,7 +182,7 @@ object ExportRecipe { } fun generateName(name: String): SkyblockId { - return SkyblockId(name.uppercase().replace(" ", "_")) + return SkyblockId(name.uppercase().replace(" ", "_").replace("(", "").replace(")", "")) } fun findStackableItemByName(name: String, fallbackToGenerated: Boolean = false): Pair<SkyblockId, Double>? { diff --git a/src/main/kotlin/features/debug/itemeditor/ItemExporter.kt b/src/main/kotlin/features/debug/itemeditor/ItemExporter.kt index fd411c9..d497ad2 100644 --- a/src/main/kotlin/features/debug/itemeditor/ItemExporter.kt +++ b/src/main/kotlin/features/debug/itemeditor/ItemExporter.kt @@ -65,13 +65,20 @@ object ItemExporter { exportItem(itemStack) } - fun appendRecipe(skyblockId: SkyblockId, recipe: JsonObject) { + fun modifyJson(skyblockId: SkyblockId, modify: (JsonObject) -> JsonObject) { val oldJson = Firmament.json.decodeFromString<JsonObject>(pathFor(skyblockId).readText()) - val mutableJson = oldJson.toMutableMap() - val recipes = ((mutableJson["recipes"] as JsonArray?) ?: listOf()).toMutableList() - recipes.add(recipe) - mutableJson["recipes"] = JsonArray(recipes) - pathFor(skyblockId).writeText(Firmament.twoSpaceJson.encodeToString(JsonObject(mutableJson))) + val newJson = modify(oldJson) + pathFor(skyblockId).writeText(Firmament.twoSpaceJson.encodeToString(JsonObject(newJson))) + } + + fun appendRecipe(skyblockId: SkyblockId, recipe: JsonObject) { + modifyJson(skyblockId) { oldJson -> + val mutableJson = oldJson.toMutableMap() + val recipes = ((mutableJson["recipes"] as JsonArray?) ?: listOf()).toMutableList() + recipes.add(recipe) + mutableJson["recipes"] = JsonArray(recipes) + JsonObject(mutableJson) + } } @Subscribe @@ -82,7 +89,7 @@ object ItemExporter { } } - fun exportStub(skyblockId: SkyblockId, title: String) { + fun exportStub(skyblockId: SkyblockId, title: String, extra: (ItemStack) -> Unit = {}) { exportItem(ItemStack(Items.PLAYER_HEAD).also { it.displayNameAccordingToNbt = Text.literal(title) it.loreAccordingToNbt = listOf(Text.literal("")) diff --git a/src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt b/src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt index fbb778a..7f7bfbd 100644 --- a/src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt +++ b/src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt @@ -35,6 +35,9 @@ import moe.nea.firmament.util.transformEachRecursively import moe.nea.firmament.util.unformattedString class LegacyItemExporter private constructor(var itemStack: ItemStack) { + init { + require(!itemStack.isEmpty) + } var lore = itemStack.loreAccordingToNbt var name = itemStack.displayNameAccordingToNbt val extraAttribs = itemStack.extraAttributes.copy() diff --git a/src/main/kotlin/features/debug/itemeditor/PromptScreen.kt b/src/main/kotlin/features/debug/itemeditor/PromptScreen.kt new file mode 100644 index 0000000..187b70b --- /dev/null +++ b/src/main/kotlin/features/debug/itemeditor/PromptScreen.kt @@ -0,0 +1,15 @@ +package moe.nea.firmament.features.debug.itemeditor + +import io.github.notenoughupdates.moulconfig.gui.CloseEventListener +import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper +import io.github.notenoughupdates.moulconfig.gui.GuiContext +import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent +import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent +import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter +import kotlin.reflect.KMutableProperty0 +import moe.nea.firmament.gui.FirmButtonComponent +import moe.nea.firmament.util.MoulConfigUtils + diff --git a/src/main/kotlin/util/HoveredItemStack.kt b/src/main/kotlin/util/HoveredItemStack.kt index a2e4ad2..526820a 100644 --- a/src/main/kotlin/util/HoveredItemStack.kt +++ b/src/main/kotlin/util/HoveredItemStack.kt @@ -24,4 +24,4 @@ class VanillaScreenProvider : HoveredItemStackProvider { val HandledScreen<*>.focusedItemStack: ItemStack? get() = HoveredItemStackProvider.allValidInstances - .firstNotNullOfOrNull { it.provideHoveredItemStack(this) } + .firstNotNullOfOrNull { it.provideHoveredItemStack(this)?.takeIf { !it.isEmpty } } diff --git a/src/main/kotlin/util/MoulConfigUtils.kt b/src/main/kotlin/util/MoulConfigUtils.kt index a9e3241..51ff340 100644 --- a/src/main/kotlin/util/MoulConfigUtils.kt +++ b/src/main/kotlin/util/MoulConfigUtils.kt @@ -9,7 +9,6 @@ import io.github.notenoughupdates.moulconfig.gui.GuiContext import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent import io.github.notenoughupdates.moulconfig.gui.MouseEvent -import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent import io.github.notenoughupdates.moulconfig.observer.GetSetter import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext import io.github.notenoughupdates.moulconfig.xml.ChildCount @@ -21,7 +20,6 @@ import java.io.File import java.util.function.Supplier import javax.xml.namespace.QName import me.shedaniel.math.Color -import org.jetbrains.annotations.Unmodifiable import org.w3c.dom.Element import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -41,13 +39,15 @@ object MoulConfigUtils { fun main(args: Array<out String>) { generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS) generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl) - File("wrapper.xsd").writeText(""" + File("wrapper.xsd").writeText( + """ <?xml version="1.0" encoding="UTF-8" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/> <xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/> </xs:schema> - """.trimIndent()) + """.trimIndent() + ) } val firmUrl = "http://firmament.nea.moe/moulconfig" @@ -96,9 +96,11 @@ object MoulConfigUtils { override fun createInstance(context: XMLContext<*>, element: Element): FirmHoverComponent { return FirmHoverComponent( context.getChildFragment(element), - context.getPropertyFromAttribute(element, - QName("lines"), - List::class.java) as Supplier<List<String>>, + context.getPropertyFromAttribute( + element, + QName("lines"), + List::class.java + ) as Supplier<List<String>>, context.getPropertyFromAttribute(element, QName("delay"), Duration::class.java, 0.6.seconds), ) } @@ -223,16 +225,21 @@ object MoulConfigUtils { generator.dumpToFile(file) } - fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen { - return object : GuiComponentWrapper(loadGui(name, bindTo)) { + fun wrapScreen(guiContext: GuiContext, parent: Screen?, onClose: () -> Unit = {}): Screen { + return object : GuiComponentWrapper(guiContext) { override fun close() { if (context.onBeforeClose() == CloseEventListener.CloseAction.NO_OBJECTIONS_TO_CLOSE) { client!!.setScreen(parent) + onClose() } } } } + fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen { + return wrapScreen(loadGui(name, bindTo), parent) + } + // TODO: move this utility into moulconfig (also rework guicontext into an interface so i can make this mesh better into vanilla) fun GuiContext.adopt(element: GuiComponent) = element.foldRecursive(Unit, { comp, unit -> comp.context = this }) @@ -288,12 +295,14 @@ object MoulConfigUtils { assert(drawContext?.isUntranslatedGuiDrawContext() != false) val context = drawContext?.let(::ModernRenderContext) ?: IMinecraft.instance.provideTopLevelRenderContext() - val immContext = GuiImmediateContext(context, - 0, 0, 0, 0, - mouseX, mouseY, - mouseX, mouseY, - mouseX.toFloat(), - mouseY.toFloat()) + val immContext = GuiImmediateContext( + context, + 0, 0, 0, 0, + mouseX, mouseY, + mouseX, mouseY, + mouseX.toFloat(), + mouseY.toFloat() + ) return immContext } diff --git a/src/main/kotlin/util/async/input.kt b/src/main/kotlin/util/async/input.kt index f22c595..2c546ba 100644 --- a/src/main/kotlin/util/async/input.kt +++ b/src/main/kotlin/util/async/input.kt @@ -1,47 +1,89 @@ - - package moe.nea.firmament.util.async +import io.github.notenoughupdates.moulconfig.gui.GuiContext +import io.github.notenoughupdates.moulconfig.gui.component.CenterComponent +import io.github.notenoughupdates.moulconfig.gui.component.ColumnComponent +import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextComponent +import io.github.notenoughupdates.moulconfig.gui.component.TextFieldComponent +import io.github.notenoughupdates.moulconfig.observer.GetSetter import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume +import net.minecraft.client.gui.screen.Screen import moe.nea.firmament.events.HandledScreenKeyPressedEvent +import moe.nea.firmament.gui.FirmButtonComponent import moe.nea.firmament.keybindings.IKeyBinding +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils +import moe.nea.firmament.util.ScreenUtil private object InputHandler { - data class KeyInputContinuation(val keybind: IKeyBinding, val onContinue: () -> Unit) - - private val activeContinuations = mutableListOf<KeyInputContinuation>() - - fun registerContinuation(keyInputContinuation: KeyInputContinuation): () -> Unit { - synchronized(InputHandler) { - activeContinuations.add(keyInputContinuation) - } - return { - synchronized(this) { - activeContinuations.remove(keyInputContinuation) - } - } - } - - init { - HandledScreenKeyPressedEvent.subscribe("Input:resumeAfterInput") { event -> - synchronized(InputHandler) { - val toRemove = activeContinuations.filter { - event.matches(it.keybind) - } - toRemove.forEach { it.onContinue() } - activeContinuations.removeAll(toRemove) - } - } - } + data class KeyInputContinuation(val keybind: IKeyBinding, val onContinue: () -> Unit) + + private val activeContinuations = mutableListOf<KeyInputContinuation>() + + fun registerContinuation(keyInputContinuation: KeyInputContinuation): () -> Unit { + synchronized(InputHandler) { + activeContinuations.add(keyInputContinuation) + } + return { + synchronized(this) { + activeContinuations.remove(keyInputContinuation) + } + } + } + + init { + HandledScreenKeyPressedEvent.subscribe("Input:resumeAfterInput") { event -> + synchronized(InputHandler) { + val toRemove = activeContinuations.filter { + event.matches(it.keybind) + } + toRemove.forEach { it.onContinue() } + activeContinuations.removeAll(toRemove) + } + } + } } suspend fun waitForInput(keybind: IKeyBinding): Unit = suspendCancellableCoroutine { cont -> - val unregister = - InputHandler.registerContinuation(InputHandler.KeyInputContinuation(keybind) { cont.resume(Unit) }) - cont.invokeOnCancellation { - unregister() - } + val unregister = + InputHandler.registerContinuation(InputHandler.KeyInputContinuation(keybind) { cont.resume(Unit) }) + cont.invokeOnCancellation { + unregister() + } } +fun createPromptScreenGuiComponent(suggestion: String, prompt: String, action: Runnable) = (run { + val text = GetSetter.floating(suggestion) + GuiContext( + CenterComponent( + PanelComponent( + ColumnComponent( + TextFieldComponent(text, 120), + FirmButtonComponent(TextComponent(prompt), action = action) + ) + ) + ) + ) to text +}) + +suspend fun waitForTextInput(suggestion: String, prompt: String) = + suspendCancellableCoroutine<String> { cont -> + lateinit var screen: Screen + lateinit var text: GetSetter<String> + val action = { + if (MC.screen === screen) + MC.screen = null + // TODO: should this exit + cont.resume(text.get()) + } + val (gui, text_) = createPromptScreenGuiComponent(suggestion, prompt, action) + text = text_ + screen = MoulConfigUtils.wrapScreen(gui, null, onClose = action) + ScreenUtil.setScreenLater(screen) + cont.invokeOnCancellation { + action() + } + } |