aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-06-22 22:45:16 +0200
committerLinnea Gräf <nea@nea.moe>2025-06-22 22:45:16 +0200
commit1ab9094bde904b9671acf29c700e3c98d503c8fe (patch)
treeea0c2dc731a46a65a30748be01720c096ab3f83d
parentfbc44e21393758ddf92397446d8e19f3f39a7317 (diff)
downloadFirmament-1ab9094bde904b9671acf29c700e3c98d503c8fe.tar.gz
Firmament-1ab9094bde904b9671acf29c700e3c98d503c8fe.tar.bz2
Firmament-1ab9094bde904b9671acf29c700e3c98d503c8fe.zip
feat: Add npc location exporter
-rw-r--r--src/main/kotlin/features/debug/PowerUserTools.kt1
-rw-r--r--src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt48
-rw-r--r--src/main/kotlin/features/debug/itemeditor/ItemExporter.kt21
-rw-r--r--src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt3
-rw-r--r--src/main/kotlin/features/debug/itemeditor/PromptScreen.kt15
-rw-r--r--src/main/kotlin/util/HoveredItemStack.kt2
-rw-r--r--src/main/kotlin/util/MoulConfigUtils.kt39
-rw-r--r--src/main/kotlin/util/async/input.kt108
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()
+ }
+ }