aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-06-22 21:39:57 +0200
committerLinnea Gräf <nea@nea.moe>2025-06-22 21:39:57 +0200
commitc74930d6cb142cb8c3480a387645f8b4752c953a (patch)
treec240949e33312e71ee8799638838d022539234eb /src
parent89047619c62343b5f1fa26ef825595bcba02ad0a (diff)
downloadFirmament-c74930d6cb142cb8c3480a387645f8b4752c953a.tar.gz
Firmament-c74930d6cb142cb8c3480a387645f8b4752c953a.tar.bz2
Firmament-c74930d6cb142cb8c3480a387645f8b4752c953a.zip
feat: Add item shop recipe exporter
Diffstat (limited to 'src')
-rw-r--r--src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt193
-rw-r--r--src/main/kotlin/features/debug/itemeditor/ItemExporter.kt19
-rw-r--r--src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt1
-rw-r--r--src/main/kotlin/util/regex.kt7
-rw-r--r--src/main/kotlin/util/skyblock/SkyBlockItems.kt1
5 files changed, 199 insertions, 22 deletions
diff --git a/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt b/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt
index 25e5cbd..75ab943 100644
--- a/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt
+++ b/src/main/kotlin/features/debug/itemeditor/ExportRecipe.kt
@@ -1,14 +1,26 @@
package moe.nea.firmament.features.debug.itemeditor
+import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.features.debug.PowerUserTools
+import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
+import moe.nea.firmament.util.SkyblockId
+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.parseShortNumber
+import moe.nea.firmament.util.red
+import moe.nea.firmament.util.removeColorCodes
import moe.nea.firmament.util.skyBlockId
+import moe.nea.firmament.util.skyblock.SkyBlockItems
import moe.nea.firmament.util.tr
+import moe.nea.firmament.util.useMatch
object ExportRecipe {
@@ -29,29 +41,168 @@ object ExportRecipe {
if (!event.matches(PowerUserTools.TConfig.exportUIRecipes)) {
return
}
- if (!event.screen.title.string.endsWith(" Recipe")) {
- MC.sendChat(tr("firmament.repo.export.recipe.fail", "No Recipe found"))
+ val title = event.screen.title.string
+ val sellSlot = event.screen.getSlotByIndex(49, false)?.stack
+ if (title.endsWith(" Recipe")) {
+ slotIndices.forEach { (_, index) ->
+ event.screen.getSlotByIndex(index, false)?.stack?.let(ItemExporter::ensureExported)
+ }
+ val inputs = slotIndices.associate { (name, index) ->
+ val id = event.screen.getSlotByIndex(index, false)?.stack?.takeIf { !it.isEmpty() }?.let {
+ "${it.skyBlockId?.neuItem}:${it.count}"
+ } ?: ""
+ name to JsonPrimitive(id)
+ }
+ val output = event.screen.getSlotByIndex(resultSlot, false)?.stack!!
+ val overrideOutputId = output.skyBlockId!!.neuItem
+ val count = output.count
+ val recipe = JsonObject(
+ inputs + mapOf(
+ "type" to JsonPrimitive("crafting"),
+ "count" to JsonPrimitive(count),
+ "overrideOutputId" to JsonPrimitive(overrideOutputId)
+ )
+ )
+ ItemExporter.appendRecipe(output.skyBlockId!!, recipe)
+ MC.sendChat(tr("firmament.repo.export.recipe", "Recipe for ${output.skyBlockId} exported."))
return
+ } else if (sellSlot?.displayNameAccordingToNbt?.string == "Sell Item" || (sellSlot?.loreAccordingToNbt
+ ?: listOf()).any { it.string == "Click to buyback!" }
+ ) {
+ val shopId = SkyblockId(title.uppercase().replace(" ", "_") + "_NPC")
+ if (!ItemExporter.isExported(shopId)) {
+ // TODO: export location + skin of last clicked npc
+ ItemExporter.exportStub(shopId, "$title (NPC)")
+ }
+ for (index in (9..9 * 5)) {
+ val item = event.screen.getSlotByIndex(index, false)?.stack ?: continue
+ val skyblockId = item.skyBlockId ?: continue
+ val costLines = item.loreAccordingToNbt
+ .map { it.string.trim() }
+ .dropWhile { !it.startsWith("Cost") }
+ .dropWhile { it == "Cost" }
+ .takeWhile { it != "Click to trade!" }
+ .takeWhile { it != "Stock" }
+ .filter { !it.isBlank() }
+ .map { it.removePrefix("Cost: ") }
+
+
+ val costs = costLines.mapNotNull { lineText ->
+ val line = findStackableItemByName(lineText)
+ if (line == null) {
+ MC.sendChat(
+ tr(
+ "firmament.repo.itemshop.fail",
+ "Could not parse cost item ${lineText} for ${item.displayNameAccordingToNbt}"
+ ).red()
+ )
+ }
+ line
+ }
+
+
+ ItemExporter.appendRecipe(
+ shopId, JsonObject(
+ mapOf(
+ "type" to JsonPrimitive("npc_shop"),
+ "cost" to JsonArray(costs.map { JsonPrimitive("${it.first.neuItem}:${it.second}") }),
+ "result" to JsonPrimitive("${skyblockId.neuItem}:${item.count}"),
+ )
+ )
+ )
+ }
+ MC.sendChat(tr("firmament.repo.export.itemshop", "Item Shop export for ${title} complete."))
+ } else {
+ MC.sendChat(tr("firmament.repo.export.recipe.fail", "No Recipe found"))
}
- slotIndices.forEach { (_, index) ->
- event.screen.getSlotByIndex(index, false)?.stack?.let(ItemExporter::ensureExported)
- }
- val inputs = slotIndices.associate { (name, index) ->
- val id = event.screen.getSlotByIndex(index, false)?.stack?.takeIf { !it.isEmpty() }?.let {
- "${it.skyBlockId?.neuItem}:${it.count}"
- } ?: ""
- name to JsonPrimitive(id)
- }
- val output = event.screen.getSlotByIndex(resultSlot, false)?.stack!!
- val overrideOutputId = output.skyBlockId!!.neuItem
- val count = output.count
- val recipe = JsonObject(
- inputs + mapOf(
- "type" to JsonPrimitive("crafting"),
- "count" to JsonPrimitive(count),
- "overrideOutputId" to JsonPrimitive(overrideOutputId)
+ }
+
+ private val coinRegex = "(?<amount>$SHORT_NUMBER_FORMAT) Coins?".toPattern()
+ private val stackedItemRegex = "(?<name>.*) x(?<count>$SHORT_NUMBER_FORMAT)".toPattern()
+ private val reverseStackedItemRegex = "(?<count>$SHORT_NUMBER_FORMAT)x (?<name>.*)".toPattern()
+ private val essenceRegex = "(?<essence>.*) Essence x(?<count>$SHORT_NUMBER_FORMAT)".toPattern()
+ private val numberedItemRegex = "(?<count>$SHORT_NUMBER_FORMAT) (?<what>.*)".toPattern()
+
+ private val etherialRewardPattern = "\\+(?<amount>${SHORT_NUMBER_FORMAT})x? (?<what>.*)".toPattern()
+
+ fun findForName(name: String, fallbackToGenerated: Boolean = true): SkyblockId? {
+ var id = ItemNameLookup.guessItemByName(name, true)
+ if (id == null && fallbackToGenerated) {
+ id = generateName(name)
+ }
+ return id
+ }
+
+ fun skill(name: String): SkyblockId {
+ return SkyblockId("SKYBLOCK_SKILL_${name}")
+ }
+
+ fun generateName(name: String): SkyblockId {
+ return SkyblockId(name.uppercase().replace(" ", "_"))
+ }
+
+ fun findStackableItemByName(name: String, fallbackToGenerated: Boolean = false): Pair<SkyblockId, Double>? {
+ val properName = name.removeColorCodes().trim()
+ if (properName == "FREE" || properName == "This Chest is Free!") {
+ return Pair(SkyBlockItems.COINS, 0.0)
+ }
+ coinRegex.useMatch(properName) {
+ return Pair(SkyBlockItems.COINS, parseShortNumber(group("amount")))
+ }
+ etherialRewardPattern.useMatch(properName) {
+ val id = when (val id = group("what")) {
+ "Copper" -> SkyblockId("SKYBLOCK_COPPER")
+ "Bits" -> SkyblockId("SKYBLOCK_BIT")
+ "Garden Experience" -> SkyblockId("SKYBLOCK_SKILL_GARDEN")
+ "Farming XP" -> SkyblockId("SKYBLOCK_SKILL_FARMING")
+ "Gold Essence" -> SkyblockId("ESSENCE_GOLD")
+ "Gemstone Powder" -> SkyblockId("SKYBLOCK_POWDER_GEMSTONE")
+ "Mithril Powder" -> SkyblockId("SKYBLOCK_POWDER_MITHRIL")
+ "Pelts" -> SkyblockId("SKYBLOCK_PELT")
+ "Fine Flour" -> SkyblockId("FINE_FLOUR")
+ else -> {
+ id.ifDropLast(" Experience") {
+ skill(generateName(it).neuItem)
+ } ?: id.ifDropLast(" XP") {
+ skill(generateName(it).neuItem)
+ } ?: id.ifDropLast(" Powder") {
+ SkyblockId("SKYBLOCK_POWDER_${generateName(it).neuItem}")
+ } ?: id.ifDropLast(" Essence") {
+ SkyblockId("ESSENCE_${generateName(it).neuItem}")
+ } ?: generateName(id)
+ }
+ }
+ return Pair(id, parseShortNumber(group("amount")))
+ }
+ essenceRegex.useMatch(properName) {
+ return Pair(
+ SkyblockId("ESSENCE_${group("essence").uppercase()}"),
+ parseShortNumber(group("count"))
)
- )
- ItemExporter.appendRecipe(output.skyBlockId!!, recipe)
+ }
+ stackedItemRegex.useMatch(properName) {
+ val item = findForName(group("name"), fallbackToGenerated)
+ if (item != null) {
+ val count = parseShortNumber(group("count"))
+ return Pair(item, count)
+ }
+ }
+ reverseStackedItemRegex.useMatch(properName) {
+ val item = findForName(group("name"), fallbackToGenerated)
+ if (item != null) {
+ val count = parseShortNumber(group("count"))
+ return Pair(item, count)
+ }
+ }
+ numberedItemRegex.useMatch(properName) {
+ val item = findForName(group("what"), fallbackToGenerated)
+ if (item != null) {
+ val count = parseShortNumber(group("count"))
+ return Pair(item, count)
+ }
+ }
+
+ return findForName(properName, fallbackToGenerated)?.let { Pair(it, 1.0) }
}
+
}
diff --git a/src/main/kotlin/features/debug/itemeditor/ItemExporter.kt b/src/main/kotlin/features/debug/itemeditor/ItemExporter.kt
index 64e14f2..fd411c9 100644
--- a/src/main/kotlin/features/debug/itemeditor/ItemExporter.kt
+++ b/src/main/kotlin/features/debug/itemeditor/ItemExporter.kt
@@ -10,6 +10,7 @@ import kotlin.io.path.readText
import kotlin.io.path.relativeTo
import kotlin.io.path.writeText
import net.minecraft.item.ItemStack
+import net.minecraft.item.Items
import net.minecraft.text.Text
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
@@ -18,9 +19,13 @@ import moe.nea.firmament.features.debug.ExportedTestConstantMeta
import moe.nea.firmament.features.debug.PowerUserTools
import moe.nea.firmament.repo.RepoDownloadManager
import moe.nea.firmament.repo.RepoManager
+import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.focusedItemStack
import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.setSkyBlockId
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.tr
@@ -52,8 +57,11 @@ object ItemExporter {
fun pathFor(skyBlockId: SkyblockId) =
RepoManager.neuRepo.baseFolder.resolve("items/${skyBlockId.neuItem}.json")
+ fun isExported(skyblockId: SkyblockId) =
+ pathFor(skyblockId).exists()
+
fun ensureExported(itemStack: ItemStack) {
- if (!pathFor(itemStack.skyBlockId ?: return).exists())
+ if (!isExported(itemStack.skyBlockId ?: return))
exportItem(itemStack)
}
@@ -73,4 +81,13 @@ object ItemExporter {
PowerUserTools.lastCopiedStack = (itemStack to exportItem(itemStack))
}
}
+
+ fun exportStub(skyblockId: SkyblockId, title: String) {
+ exportItem(ItemStack(Items.PLAYER_HEAD).also {
+ it.displayNameAccordingToNbt = Text.literal(title)
+ it.loreAccordingToNbt = listOf(Text.literal(""))
+ it.setSkyBlockId(skyblockId)
+ })
+ MC.sendChat(tr("firmament.repo.export.stub", "Exported a stub item for $skyblockId"))
+ }
}
diff --git a/src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt b/src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt
index 1e9a4e9..fbb778a 100644
--- a/src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt
+++ b/src/main/kotlin/features/debug/itemeditor/LegacyItemExporter.kt
@@ -75,6 +75,7 @@ class LegacyItemExporter private constructor(var itemStack: ItemStack) {
deleteLineUntilNextSpace { it.startsWith("Held Item: ") }
deleteLineUntilNextSpace { it.startsWith("Progress to Level ") }
deleteLineUntilNextSpace { it.startsWith("MAX LEVEL") }
+ deleteLineUntilNextSpace { it.startsWith("Click to view recipe!") }
collapseWhitespaces()
name = name.transformEachRecursively {
diff --git a/src/main/kotlin/util/regex.kt b/src/main/kotlin/util/regex.kt
index f239810..be6bcfb 100644
--- a/src/main/kotlin/util/regex.kt
+++ b/src/main/kotlin/util/regex.kt
@@ -26,6 +26,13 @@ inline fun <T> Pattern.useMatch(string: String?, block: Matcher.() -> T): T? {
?.let(block)
}
+fun <T> String.ifDropLast(suffix: String, block: (String) -> T): T? {
+ if (endsWith(suffix)) {
+ return block(dropLast(suffix.length))
+ }
+ return null
+}
+
@Language("RegExp")
val TIME_PATTERN = "[0-9]+[ms]"
diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
index ca2b17b..74e1327 100644
--- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt
+++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt
@@ -3,6 +3,7 @@ package moe.nea.firmament.util.skyblock
import moe.nea.firmament.util.SkyblockId
object SkyBlockItems {
+ val COINS = SkyblockId("SKYBLOCK_COIN")
val ROTTEN_FLESH = SkyblockId("ROTTEN_FLESH")
val ENCHANTED_DIAMOND = SkyblockId("ENCHANTED_DIAMOND")
val DIAMOND = SkyblockId("DIAMOND")