diff options
| author | David Cole <40234707+DavidArthurCole@users.noreply.github.com> | 2024-10-22 14:57:50 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-22 20:57:50 +0200 |
| commit | fa3ffae5674699eb13013cbf6102c8f53b017c89 (patch) | |
| tree | 3a35d68ff10680935d8a6e1da062ad7504c09be1 | |
| parent | 4e03682aaa5ef469362b4138bfb554845b75eab9 (diff) | |
| download | SkyHanni-fa3ffae5674699eb13013cbf6102c8f53b017c89.tar.gz SkyHanni-fa3ffae5674699eb13013cbf6102c8f53b017c89.tar.bz2 SkyHanni-fa3ffae5674699eb13013cbf6102c8f53b017c89.zip | |
Feature: Essence Shop & Carnival Shop Helper (#2423)
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
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 +} |
