aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java7
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuCarnivalShops.kt12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuEssenceShopJson.kt8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/CarnivalShopHelper.kt272
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/EssenceShopHelper.kt274
7 files changed, 583 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java
index cad2b7ea3..9364b0c04 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/carnival/CarnivalConfig.java
@@ -36,4 +36,10 @@ public class CarnivalConfig {
@ConfigEditorBoolean
@FeatureToggle
public boolean doubleClickToStart = true;
+
+ @Expose
+ @ConfigOption(name = "Token Shop Helper", desc = "Show extra information about remaining upgrades in Event Shops.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean tokenShopHelper = true;
}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java
index 608fac71c..8ff178fc0 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.java
@@ -310,6 +310,12 @@ public class InventoryConfig {
public boolean hexAsColorInLore = true;
@Expose
+ @ConfigOption(name = "Essence Shop Helper", desc = "Show extra information about remaining upgrades in essence shops.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean essenceShopHelper = true;
+
+ @Expose
@ConfigOption(name = "Snake Game Keybinds", desc = "Use WASD-Keys to move around in the Abiphone snake game.")
@ConfigEditorBoolean
@FeatureToggle
@@ -320,4 +326,5 @@ public class InventoryConfig {
@ConfigEditorBoolean
@FeatureToggle
public boolean highlightActiveBeaconEffect = true;
+
}
diff --git a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
index 5a5e884e0..b9d76c55f 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
@@ -203,6 +203,10 @@ public class ProfileSpecificStorage {
@Expose
public Map<CarnivalGoal, Boolean> goals = new HashMap<>();
+
+ @Expose
+ // shop name -> (item name, tier)
+ public Map<String, Map<String, Integer>> carnivalShopProgress = new HashMap<>();
}
@Expose
diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuCarnivalShops.kt b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuCarnivalShops.kt
new file mode 100644
index 000000000..9c0fd6016
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuCarnivalShops.kt
@@ -0,0 +1,12 @@
+package at.hannibal2.skyhanni.data.jsonobjects.repo.neu
+
+import com.google.gson.annotations.Expose
+
+data class NeuMiscJson(
+ @Expose val carnivalTokenShops: Map<String, Map<String, NeuCarnivalTokenCostJson>>
+)
+
+data class NeuCarnivalTokenCostJson(
+ @Expose val name: String,
+ @Expose val costs: List<Int>
+)
diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuEssenceShopJson.kt b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuEssenceShopJson.kt
new file mode 100644
index 000000000..c3fd3244e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/neu/NeuEssenceShopJson.kt
@@ -0,0 +1,8 @@
+package at.hannibal2.skyhanni.data.jsonobjects.repo.neu
+
+import com.google.gson.annotations.Expose
+
+data class NeuEssenceShopJson(
+ @Expose val name: String,
+ @Expose val costs: List<Int>
+)
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/CarnivalShopHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/CarnivalShopHelper.kt
new file mode 100644
index 000000000..51264a860
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/CarnivalShopHelper.kt
@@ -0,0 +1,272 @@
+package at.hannibal2.skyhanni.features.inventory
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.ProfileStorageData
+import at.hannibal2.skyhanni.data.jsonobjects.repo.neu.NeuCarnivalTokenCostJson
+import at.hannibal2.skyhanni.data.jsonobjects.repo.neu.NeuMiscJson
+import at.hannibal2.skyhanni.events.InventoryCloseEvent
+import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent
+import at.hannibal2.skyhanni.events.InventoryOpenEvent
+import at.hannibal2.skyhanni.events.InventoryUpdatedEvent
+import at.hannibal2.skyhanni.events.NeuRepositoryReloadEvent
+import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent
+import at.hannibal2.skyhanni.features.inventory.EssenceShopHelper.essenceUpgradePattern
+import at.hannibal2.skyhanni.features.inventory.EssenceShopHelper.maxedUpgradeLorePattern
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.ItemUtils.createItemStack
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName
+import at.hannibal2.skyhanni.utils.NEUItems.getItemStack
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.NumberUtil.formatInt
+import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal
+import at.hannibal2.skyhanni.utils.RegexUtils.firstMatcher
+import at.hannibal2.skyhanni.utils.RegexUtils.groupOrNull
+import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
+import at.hannibal2.skyhanni.utils.RegexUtils.matches
+import net.minecraft.item.ItemStack
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+@SkyHanniModule
+object CarnivalShopHelper {
+
+ // Where the informational item stack will be placed in the GUI
+ private const val CUSTOM_STACK_LOCATION = 8
+ private val NAME_TAG_ITEM by lazy { "NAME_TAG".asInternalName().getItemStack().item }
+
+ private var repoEventShops = mutableListOf<EventShop>()
+ private var currentProgress: EventShopProgress? = null
+ private var currentEventType: String = ""
+ private var tokensOwned: Int = 0
+ private var tokensNeeded: Int = 0
+ private var overviewInfoItemStack: ItemStack? = null
+ private var shopSpecificInfoItemStack: ItemStack? = null
+
+ /**
+ * REGEX-TEST: Your Tokens: §a1,234,567
+ * REGEX-TEST: Your Tokens: §a0
+ */
+ private val currentTokenCountPattern by patternGroup.pattern(
+ "carnival.tokens.current",
+ ".*§7Your Tokens: §a(?<tokens>[\\d,]*)",
+ )
+
+ /**
+ * REGEX-TEST: §8Souvenir Shop
+ * REGEX-TEST: §8Carnival Perks
+ */
+ private val overviewInventoryNamesPattern by patternGroup.pattern(
+ "carnival.overviewinventories",
+ "(?:§.)*(?:Souvenir Shop|Carnival Perks)",
+ )
+
+ data class EventShop(val shopName: String, val upgrades: List<NeuCarnivalTokenCostJson>)
+ data class EventShopUpgradeStatus(
+ val upgradeName: String,
+ val currentLevel: Int,
+ val maxLevel: Int,
+ val remainingCosts: MutableList<Int>,
+ )
+
+ data class EventShopProgress(val shopName: String, val purchasedUpgrades: Map<String, Int>) {
+ private val eventShop = repoEventShops.find { it.shopName.equals(shopName, ignoreCase = true) }
+ val remainingUpgrades: MutableList<EventShopUpgradeStatus> = eventShop?.upgrades?.map {
+ val purchasedAmount = purchasedUpgrades[it.name] ?: 0
+ EventShopUpgradeStatus(
+ it.name,
+ currentLevel = purchasedAmount,
+ maxLevel = it.costs.count(),
+ remainingCosts = it.costs.drop(purchasedAmount).toMutableList(),
+ )
+ }.orEmpty().toMutableList()
+ val remainingUpgradeSum = remainingUpgrades.sumOf { it.remainingCosts.sum() }
+ val nonRepoUpgrades = purchasedUpgrades.filter { purchasedUpgrade ->
+ eventShop?.upgrades?.none { it.name.equals(purchasedUpgrade.key, ignoreCase = true) } == true
+ }
+ }
+
+ @SubscribeEvent
+ fun replaceItem(event: ReplaceItemEvent) {
+ if (!isEnabled() || repoEventShops.isEmpty() || event.slot != CUSTOM_STACK_LOCATION) return
+ tryReplaceShopSpecificStack(event)
+ tryReplaceOverviewStack(event)
+ }
+
+ private fun ReplaceItemEvent.isUnknownShop() = repoEventShops.none {
+ it.shopName.equals(this.inventory.name, ignoreCase = true)
+ }
+
+ private fun tryReplaceShopSpecificStack(event: ReplaceItemEvent) {
+ if (currentProgress == null || event.isUnknownShop()) return
+ shopSpecificInfoItemStack.let { event.replace(it) }
+ }
+
+ private fun tryReplaceOverviewStack(event: ReplaceItemEvent) {
+ if (!overviewInventoryNamesPattern.matches(event.inventory.name)) return
+ overviewInfoItemStack.let { event.replace(it) }
+ }
+
+ @SubscribeEvent
+ fun onNeuRepoReload(event: NeuRepositoryReloadEvent) {
+ val repoTokenShops = event.readConstant<NeuMiscJson>("carnivalshops").carnivalTokenShops
+ repoEventShops = repoTokenShops.map { (key, value) ->
+ EventShop(key.replace("_", " "), value.values.toMutableList())
+ }.toMutableList()
+ checkSavedProgress()
+ regenerateOverviewItemStack()
+ }
+
+ @SubscribeEvent
+ fun onInventoryClose(event: InventoryCloseEvent) {
+ currentProgress = null
+ currentEventType = ""
+ tokensOwned = 0
+ tokensNeeded = 0
+ }
+
+ @SubscribeEvent
+ fun onInventoryOpen(event: InventoryFullyOpenedEvent) {
+ processInventoryEvent(event)
+ }
+
+ @SubscribeEvent
+ fun onInventoryUpdated(event: InventoryUpdatedEvent) {
+ processInventoryEvent(event)
+ }
+
+ private fun checkSavedProgress() {
+ val storage = ProfileStorageData.profileSpecific?.carnival?.carnivalShopProgress ?: return
+ for (key in storage.keys) {
+ if (!repoEventShops.any { shop -> shop.shopName.equals(key, ignoreCase = true) }) {
+ storage.remove(key)
+ }
+ }
+ }
+
+ private fun saveProgress() {
+ val storage = ProfileStorageData.profileSpecific?.carnival?.carnivalShopProgress ?: return
+ val progress = currentProgress ?: return
+ storage[progress.shopName] = progress.purchasedUpgrades
+ }
+
+ private fun MutableList<String>.addNeededRemainingTokens(cost: Int, extraFormatting: String? = null) {
+ this.add("")
+ this.add("§7Total Tokens Needed: §8${cost.addSeparators()}${extraFormatting.orEmpty()}")
+ val tokensNeeded = cost - tokensOwned
+ if (tokensOwned > 0) this.add("§7Tokens Owned: §8${tokensOwned.addSeparators()}")
+ if (tokensNeeded > 0) this.add("§7Additional Tokens Needed: §8${tokensNeeded.addSeparators()}${extraFormatting.orEmpty()}")
+ else this.addAll(listOf("", "§eYou have enough tokens"))
+ }
+
+ private fun regenerateOverviewItemStack() {
+ if (repoEventShops.isEmpty()) return
+ val storage = ProfileStorageData.profileSpecific?.carnival?.carnivalShopProgress ?: return
+ val lore = buildList {
+ add("§8(From SkyHanni)")
+ add("")
+ var sumTokensNeeded = 0
+ var foundShops = 0
+ for (repoShop in repoEventShops) {
+ val purchasedUpgrades = storage[repoShop.shopName] ?: run {
+ add("§7${repoShop.shopName}: §copen shop to load...")
+ continue
+ }
+ foundShops++
+ val shopProgress = EventShopProgress(repoShop.shopName, purchasedUpgrades)
+ when (shopProgress.remainingUpgradeSum) {
+ 0 -> add("§a${repoShop.shopName}§7: §aall upgrades purchased!")
+ else -> add("§7${repoShop.shopName}: §8${shopProgress.remainingUpgradeSum.addSeparators()} tokens needed")
+ }
+ sumTokensNeeded += shopProgress.remainingUpgradeSum
+ }
+ val extraFormatting = if (foundShops != repoEventShops.size) "*" else ""
+ sumTokensNeeded.takeIf { it > 0 }?.let { addNeededRemainingTokens(it, extraFormatting) }
+ }
+ overviewInfoItemStack = createItemStack(
+ NAME_TAG_ITEM,
+ "§bRemaining Event Shop Token Upgrades",
+ lore,
+ )
+ }
+
+ private fun regenerateShopSpecificItemStack() {
+ val progress = currentProgress ?: return
+ val lore = buildList {
+ add("§8(From SkyHanni)")
+ add("")
+ val remaining = progress.remainingUpgrades.filter { it.remainingCosts.isNotEmpty() }
+ if (remaining.isEmpty()) {
+ add("§a§lAll upgrades purchased!")
+ } else {
+ add("")
+ remaining.forEach {
+ add(
+ " §a${it.upgradeName} §b${it.currentLevel} §7-> §b${it.maxLevel}§7: §8${
+ it.remainingCosts.sum().addSeparators()
+ }",
+ )
+ }
+ val upgradeTotal = progress.remainingUpgradeSum
+ tokensNeeded = upgradeTotal - tokensOwned
+ upgradeTotal.takeIf { it > 0 }?.let { addNeededRemainingTokens(it) }
+ }
+
+ if (progress.nonRepoUpgrades.any()) {
+ add("")
+ add("§cFound upgrades not in repo§c§l:")
+ progress.nonRepoUpgrades.forEach { add(" §4${it.key}") }
+ }
+ }
+ shopSpecificInfoItemStack = createItemStack(
+ NAME_TAG_ITEM,
+ "§bRemaining $currentEventType Token Upgrades",
+ lore,
+ )
+ }
+
+ private fun processInventoryEvent(event: InventoryOpenEvent) {
+ if (!isEnabled() || repoEventShops.isEmpty()) return
+ processTokenShopFooter(event)
+ val matchingShop = repoEventShops.find { it.shopName.equals(event.inventoryName, ignoreCase = true) } ?: return
+ currentEventType = matchingShop.shopName
+ processEventShopUpgrades(event.inventoryItems)
+ regenerateShopSpecificItemStack()
+ regenerateOverviewItemStack()
+ saveProgress()
+ }
+
+ private fun processTokenShopFooter(event: InventoryOpenEvent) {
+ val tokenFooterStack = event.inventoryItems[32]
+ if (tokenFooterStack === null || tokenFooterStack.displayName != "§eCarnival Tokens") return
+ currentTokenCountPattern.firstMatcher(tokenFooterStack.getLore()) {
+ tokensOwned = groupOrNull("tokens")?.formatInt() ?: 0
+ }
+ }
+
+ private fun processEventShopUpgrades(inventoryItems: Map<Int, ItemStack>) {
+ /**
+ * All upgrades will appear in slots 11 -> 15
+ *
+ * Filter out items outside of these bounds
+ */
+ val upgradeStacks = inventoryItems.filter { it.key in 11..15 && it.value.item != null }
+ // TODO remove duplicate code fragment with EssenceShopHelper
+ val purchasedUpgrades: MutableMap<String, Int> = buildMap {
+ for (value in upgradeStacks.values) {
+ // Right now Carnival and Essence Upgrade patterns are 'in-sync'
+ // This may change in the future, and this would then need its own pattern
+ essenceUpgradePattern.matchMatcher(value.displayName) {
+ val upgradeName = groupOrNull("upgrade") ?: return
+ val nextUpgradeRoman = groupOrNull("tier") ?: return
+ val nextUpgrade = nextUpgradeRoman.romanToDecimal()
+ val isMaxed = value.getLore().any { loreLine -> maxedUpgradeLorePattern.matches(loreLine) }
+ put(upgradeName, if (isMaxed) nextUpgrade else nextUpgrade - 1)
+ }
+ }
+ }.toMutableMap()
+ currentProgress = EventShopProgress(currentEventType, purchasedUpgrades)
+ }
+
+ private fun isEnabled() = LorenzUtils.inSkyBlock && SkyHanniMod.feature.event.carnival.tokenShopHelper
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/EssenceShopHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/EssenceShopHelper.kt
new file mode 100644
index 000000000..450e013da
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/EssenceShopHelper.kt
@@ -0,0 +1,274 @@
+package at.hannibal2.skyhanni.features.inventory
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.data.jsonobjects.repo.neu.NeuEssenceShopJson
+import at.hannibal2.skyhanni.events.GuiContainerEvent
+import at.hannibal2.skyhanni.events.InventoryCloseEvent
+import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent
+import at.hannibal2.skyhanni.events.InventoryOpenEvent
+import at.hannibal2.skyhanni.events.InventoryUpdatedEvent
+import at.hannibal2.skyhanni.events.NeuRepositoryReloadEvent
+import at.hannibal2.skyhanni.events.render.gui.ReplaceItemEvent
+import at.hannibal2.skyhanni.features.inventory.bazaar.BazaarApi
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.test.command.ErrorManager
+import at.hannibal2.skyhanni.utils.ItemPriceSource
+import at.hannibal2.skyhanni.utils.ItemPriceUtils.getPrice
+import at.hannibal2.skyhanni.utils.ItemUtils.createItemStack
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.NEUInternalName
+import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName
+import at.hannibal2.skyhanni.utils.NEUItems.getItemStack
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.NumberUtil.formatInt
+import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimal
+import at.hannibal2.skyhanni.utils.RegexUtils.firstMatcher
+import at.hannibal2.skyhanni.utils.RegexUtils.groupOrNull
+import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
+import at.hannibal2.skyhanni.utils.RegexUtils.matches
+import at.hannibal2.skyhanni.utils.SimpleTimeMark
+import net.minecraft.item.ItemStack
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import kotlin.time.Duration.Companion.seconds
+
+@SkyHanniModule
+object EssenceShopHelper {
+
+ // Where the informational item stack will be placed in the GUI
+ private const val CUSTOM_STACK_LOCATION = 8
+ private val GOLD_NUGGET_ITEM by lazy { "GOLD_NUGGET".asInternalName().getItemStack().item }
+
+ private var essenceShops = mutableListOf<EssenceShop>()
+ private var currentProgress: EssenceShopProgress? = null
+ private var currentEssenceType: String = ""
+ private var currentEssenceItem: NEUInternalName? = null
+ private var essenceOwned: Int = 0
+ private var essenceNeeded: Int = 0
+ private var lastClick = SimpleTimeMark.farPast()
+ private var infoItemStack: ItemStack? = null
+
+ /**
+ * REGEX-TEST: Gold Essence Shop
+ * REGEX-TEST: Wither Essence Shop
+ */
+ private val essenceShopPattern by patternGroup.pattern(
+ "essence.shop",
+ "(?:§.)*(?<essence>.*) Essence Shop",
+ )
+
+ /**
+ * REGEX-TEST: §7Your Undead Essence: §d12,664
+ * REGEX-TEST: §7Your Wither Essence: §d2,275
+ */
+ private val currentEssenceCountPattern by patternGroup.pattern(
+ "essence.current",
+ ".*§7Your (?<essence>.*) Essence: §.(?<count>[\\d,]*)",
+ )
+
+ /**
+ * REGEX-TEST: §a§lUNLOCKED
+ */
+ val maxedUpgradeLorePattern by patternGroup.pattern(
+ "essence.maxedupgrade",
+ ".*§a§lUNLOCKED",
+ )
+
+ /**
+ * REGEX-TEST: §aHigh Roller I
+ * REGEX-TEST: §aForbidden Speed III
+ * REGEX-TEST: §aReturn to Sender X
+ */
+ val essenceUpgradePattern by patternGroup.pattern(
+ "essence.upgrade",
+ "§.(?<upgrade>.*) (?<tier>[IVXLCDM]*)",
+ )
+
+ data class EssenceShop(val shopName: String, val upgrades: List<NeuEssenceShopJson>)
+ data class EssenceShopUpgradeStatus(
+ val upgradeName: String,
+ val currentLevel: Int,
+ val maxLevel: Int,
+ val remainingCosts: MutableList<Int>,
+ )
+
+ data class EssenceShopProgress(val essenceName: String, val purchasedUpgrades: Map<String, Int>) {
+ private val essenceShop = essenceShops.find { it.shopName.equals(essenceName, ignoreCase = true) }
+ val remainingUpgrades: MutableList<EssenceShopUpgradeStatus> = essenceShop?.upgrades?.map {
+ val purchasedAmount = purchasedUpgrades[it.name] ?: 0
+ EssenceShopUpgradeStatus(
+ it.name,
+ currentLevel = purchasedAmount,
+ maxLevel = it.costs.count(),
+ remainingCosts = it.costs.drop(purchasedAmount).toMutableList(),
+ )
+ }?.toMutableList() ?: mutableListOf()
+ val nonRepoUpgrades = purchasedUpgrades.filter { purchasedUpgrade ->
+ essenceShop?.upgrades?.none { it.name.equals(purchasedUpgrade.key, ignoreCase = true) } == true
+ }
+ }
+
+ @SubscribeEvent
+ fun replaceItem(event: ReplaceItemEvent) {
+ if (!isEnabled() || essenceShops.isEmpty() || currentProgress == null || event.slot != CUSTOM_STACK_LOCATION) return
+ if (!essenceShopPattern.matches(event.inventory.name)) return
+ infoItemStack.let { event.replace(it) }
+ }
+
+ @SubscribeEvent
+ fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) {
+ if (currentProgress == null || event.slotId != CUSTOM_STACK_LOCATION) return
+ val currentEssenceItem = currentEssenceItem ?: return
+ event.cancel()
+ if (lastClick.passedSince() > 0.3.seconds) {
+ BazaarApi.searchForBazaarItem(currentEssenceItem, essenceNeeded)
+ lastClick = SimpleTimeMark.now()
+ }
+ }
+
+ @SubscribeEvent
+ fun onNeuRepoReload(event: NeuRepositoryReloadEvent) {
+ val repoEssenceShops = event.readConstant<Map<String, Map<String, NeuEssenceShopJson>>>("essenceshops")
+ essenceShops = repoEssenceShops.map { (key, value) ->
+ EssenceShop(key, value.values.toMutableList())
+ }.toMutableList()
+ }
+
+ @SubscribeEvent
+ fun onInventoryClose(event: InventoryCloseEvent) {
+ currentProgress = null
+ currentEssenceType = ""
+ currentEssenceItem = null
+ essenceOwned = 0
+ essenceNeeded = 0
+ }
+
+ @SubscribeEvent
+ fun onInventoryOpen(event: InventoryFullyOpenedEvent) {
+ processInventoryEvent(event)
+ }
+
+ @SubscribeEvent
+ fun onInventoryUpdated(event: InventoryUpdatedEvent) {
+ processInventoryEvent(event)
+ }
+
+ private fun regenerateItemStack() {
+ val progress = currentProgress ?: return
+ val lore = buildList {
+ add("§8(From SkyHanni)")
+ add("")
+ val remaining = progress.remainingUpgrades.filter { it.remainingCosts.isNotEmpty() }
+ if (remaining.isEmpty()) {
+ add("§a§lAll upgrades purchased!")
+ } else {
+ remaining.forEach {
+ add(
+ " §a${it.upgradeName} §b${it.currentLevel} §7-> §b${it.maxLevel}§7: §8${
+ it.remainingCosts.sum().addSeparators()
+ }",
+ )
+ }
+ add("")
+
+ val upgradeTotal = remaining.sumOf { it.remainingCosts.sum() }
+ add("§7Sum Essence Needed: §8${upgradeTotal.addSeparators()}")
+ essenceNeeded = upgradeTotal - essenceOwned
+ if (essenceOwned > 0) add("§7Essence Owned: §8${essenceOwned.addSeparators()}")
+ if (essenceNeeded > 0) {
+ add("§7Additional Essence Needed: §8${essenceNeeded.addSeparators()}")
+ val essenceItem = "ESSENCE_${currentEssenceType.uppercase()}".asInternalName()
+
+ val bzInstantPrice = essenceItem.getPrice(ItemPriceSource.BAZAAR_INSTANT_BUY)
+ val totalInstantPrice = bzInstantPrice * essenceNeeded
+ add(" §7BZ Instant Buy: §6${totalInstantPrice.addSeparators()}")
+
+ val bzOrderPrice = essenceItem.getPrice(ItemPriceSource.BAZAAR_INSTANT_SELL)
+ val totalOrderPrice = bzOrderPrice * essenceNeeded
+ add(" §7BZ Buy Order: §6${totalOrderPrice.addSeparators()}")
+
+ add("")
+ add("§eClick to open Bazaar!")
+ } else addAll(listOf("", "§eYou have enough essence"))
+ }
+
+ if (progress.nonRepoUpgrades.any()) {
+ add("")
+ add("§cFound upgrades not in repo§c§l:")
+ progress.nonRepoUpgrades.forEach { add(" §4${it.key}") }
+ }
+ }
+ infoItemStack = createItemStack(
+ GOLD_NUGGET_ITEM,
+ "§bRemaining $currentEssenceType Essence Upgrades",
+ lore,
+ )
+ }
+
+ private fun processInventoryEvent(event: InventoryOpenEvent) {
+ if (!isEnabled() || essenceShops.isEmpty()) return
+ essenceShopPattern.matchMatcher(event.inventoryName) {
+ currentEssenceType = groupOrNull("essence") ?: return
+ val essenceName = "ESSENCE_${currentEssenceType.uppercase()}"
+ currentEssenceItem = essenceName.asInternalName()
+ essenceShops.find { it.shopName == essenceName } ?: return
+ processEssenceShopUpgrades(essenceName, event.inventoryItems)
+ processEssenceShopHeader(event)
+ regenerateItemStack()
+ }
+ }
+
+ private fun processEssenceShopHeader(event: InventoryOpenEvent) {
+ val essenceHeaderStack = event.inventoryItems[4]
+ if (essenceHeaderStack == null || !essenceShopPattern.matches(essenceHeaderStack.displayName)) {
+ ErrorManager.logErrorWithData(
+ NoSuchElementException(""),
+ "Could not read current Essence Count from inventory",
+ extraData = listOf(
+ "inventoryName" to event.inventoryName,
+ "essenceHeaderStack" to essenceHeaderStack?.displayName.orEmpty(),
+ "populatedInventorySize" to event.inventoryItems.filter { it.value.hasDisplayName() }.size,
+ "eventType" to event.javaClass.simpleName,
+ ).toTypedArray(),
+ )
+ return
+ }
+ currentEssenceCountPattern.firstMatcher(essenceHeaderStack.getLore()) {
+ essenceOwned = groupOrNull("count")?.formatInt() ?: 0
+ }
+ }
+
+ private fun processEssenceShopUpgrades(essenceName: String, inventoryItems: Map<Int, ItemStack>) {
+ /**
+ * Essence Upgrade Bounds
+ * Undead -> 10 to 20
+ * Wither -> 10 to 16
+ * Dragon -> 19 to 33
+ * Spider -> 19 to 25
+ * Crimson -> 20 to 33
+ * Ice -> 21 to 32
+ * Gold -> 19 to 25
+ * Diamond -> 19 to 25
+ *
+ * Filter out items that fall outside the bounds of 10 - 33
+ */
+ val upgradeStacks = inventoryItems.filter { it.key in 10..33 && it.value.item != null }
+ // TODO remove duplicate code fragment with CarnivalShopHelper
+ val purchasedUpgrades: MutableMap<String, Int> = buildMap {
+ for (value in upgradeStacks.values) {
+ // Right now Carnival and Essence Upgrade patterns are 'in-sync'
+ // This may change in the future, and this would then need its own pattern
+ essenceUpgradePattern.matchMatcher(value.displayName) {
+ val upgradeName = groupOrNull("upgrade") ?: return
+ val nextUpgradeRoman = groupOrNull("tier") ?: return
+ val nextUpgrade = nextUpgradeRoman.romanToDecimal()
+ val isMaxed = value.getLore().any { loreLine -> maxedUpgradeLorePattern.matches(loreLine) }
+ put(upgradeName, if (isMaxed) nextUpgrade else nextUpgrade - 1)
+ }
+ }
+ }.toMutableMap()
+ currentProgress = EssenceShopProgress(essenceName, purchasedUpgrades)
+ }
+
+ private fun isEnabled() = LorenzUtils.inSkyBlock && SkyHanniMod.feature.inventory.essenceShopHelper
+}