From 2f6121bc209fb28e1d9f12fe2d10bfacfb12836d Mon Sep 17 00:00:00 2001 From: David Cole <40234707+DavidArthurCole@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:53:30 -0400 Subject: Feature: Corpse Tracker (#2306) --- .../features/mining/glacitemineshaft/CorpseAPI.kt | 87 +++++++++++++ .../mining/glacitemineshaft/CorpseTracker.kt | 142 +++++++++++++++++++++ .../features/mining/glacitemineshaft/CorpseType.kt | 15 +++ .../glacitemineshaft/MineshaftCorpseProfitPer.kt | 56 ++++++++ .../features/mining/mineshaft/CorpseAPI.kt | 87 ------------- .../features/mining/mineshaft/CorpseType.kt | 13 -- .../mining/mineshaft/MineshaftCorpseProfitPer.kt | 56 -------- 7 files changed, 300 insertions(+), 156 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseAPI.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseTracker.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseType.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/MineshaftCorpseProfitPer.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/CorpseAPI.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/CorpseType.kt delete mode 100644 src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/MineshaftCorpseProfitPer.kt (limited to 'src/main/java/at/hannibal2/skyhanni/features') diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseAPI.kt new file mode 100644 index 000000000..c8ee81288 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseAPI.kt @@ -0,0 +1,87 @@ +package at.hannibal2.skyhanni.features.mining.glacitemineshaft + +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.mining.CorpseLootedEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ItemUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@SkyHanniModule +object CorpseAPI { + + private val patternGroup = RepoPattern.group("mining.mineshaft") + private val chatPatternGroup = patternGroup.group("chat") + + /** + * REGEX-TEST: §r§b§l§r§9§lLAPIS §r§b§lCORPSE LOOT! + * REGEX-TEST: §r§b§l§r§7§lTUNGSTEN §r§b§lCORPSE LOOT! + * REGEX-TEST: §r§b§l§r§6§lUMBER §r§b§lCORPSE LOOT! + * REGEX-TEST: §r§b§l§r§f§lVANGUARD §r§b§lCORPSE LOOT! + */ + private val startPattern by chatPatternGroup.pattern( + "start", + " {2}§r§b§l§r§(?.)§l(?.*) §r§b§lCORPSE LOOT! ?" + ) + + /** + * REGEX-TEST: §a§l▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ + */ + private val endPattern by chatPatternGroup.pattern("end", "§a§l▬{64}") + + /** + * REGEX-TEST: §r§9☠ Fine Onyx Gemstone §r§8x2 + */ + private val itemPattern by chatPatternGroup.pattern("item", " {4}§r(?.+)") + + private var inLoot = false + private val loot = mutableListOf>() + + private var corpseType: CorpseType? = null + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!IslandType.MINESHAFT.isInIsland()) return + + val message = event.message + + startPattern.matchMatcher(message) { + inLoot = true + val name = group("name") + corpseType = CorpseType.valueOf(name) + return + } + + if (!inLoot) return + + if (endPattern.matches(message)) { + corpseType?.let { + CorpseLootedEvent(it, loot.toList()).postAndCatch() + } + corpseType = null + loot.clear() + inLoot = false + return + } + var pair = itemPattern.matchMatcher(message) { + /** + * TODO fix the bug that readItemAmount produces two different outputs: + * §r§fEnchanted Book -> §fEnchanted + * §fEnchanted Book §r§8x -> §fEnchanted Book + * + * also maybe this is no bug, as enchanted book is no real item? + */ + ItemUtils.readItemAmount(group("item")) + } ?: return + // Workaround: If it is an enchanted book, we assume it is a paleontologist I book + if (pair.first.let { it == "§fEnchanted" || it == "§fEnchanted Book" }) { +// pair = "Paleontologist I" to pair.second + pair = "§9Ice Cold I" to pair.second + } + loot.add(pair) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseTracker.kt new file mode 100644 index 000000000..77aabc1bd --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseTracker.kt @@ -0,0 +1,142 @@ +package at.hannibal2.skyhanni.features.mining.glacitemineshaft + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.IslandChangeEvent +import at.hannibal2.skyhanni.events.mining.CorpseLootedEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut +import at.hannibal2.skyhanni.utils.CollectionUtils.addSearchString +import at.hannibal2.skyhanni.utils.CollectionUtils.sumAllValues +import at.hannibal2.skyhanni.utils.ItemPriceUtils.getPrice +import at.hannibal2.skyhanni.utils.ItemUtils.itemName +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat +import at.hannibal2.skyhanni.utils.renderables.Renderable +import at.hannibal2.skyhanni.utils.renderables.Searchable +import at.hannibal2.skyhanni.utils.renderables.toSearchable +import at.hannibal2.skyhanni.utils.tracker.BucketedItemTrackerData +import at.hannibal2.skyhanni.utils.tracker.ItemTrackerData.TrackedItem +import at.hannibal2.skyhanni.utils.tracker.SkyHanniBucketedItemTracker +import com.google.gson.annotations.Expose +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.EnumMap + +@SkyHanniModule +object CorpseTracker { + private val config get() = SkyHanniMod.feature.mining.glaciteMineshaft.corpseTracker + + private val tracker = SkyHanniBucketedItemTracker( + "Corpse Tracker", + { BucketData() }, + { it.mining.mineshaft.corpseProfitTracker }, + { drawDisplay(it) } + ) + + class BucketData : BucketedItemTrackerData() { + override fun resetItems() { + corpsesLooted = EnumMap(CorpseType::class.java) + } + + override fun getDescription(timesGained: Long): List { + val divisor = 1.coerceAtLeast(getSelectedBucket()?.let { corpsesLooted[it]?.toInt() } ?: corpsesLooted.sumAllValues().toInt()) + val percentage = timesGained.toDouble() / divisor + val dropRate = LorenzUtils.formatPercentage(percentage.coerceAtMost(1.0)) + return listOf( + "§7Dropped §e${timesGained.addSeparators()} §7times.", + "§7Your drop rate: §c$dropRate.", + ) + } + + override fun getCoinName(bucket: CorpseType?, item: TrackedItem) = "" + override fun getCoinDescription(bucket: CorpseType?, item: TrackedItem): List = listOf("") + + @Expose + var corpsesLooted: MutableMap = EnumMap(CorpseType::class.java) + + fun getCorpseCount(): Long = getSelectedBucket()?.let { corpsesLooted[it] } ?: corpsesLooted.values.sum() + } + + private fun addLootedCorpse(type: CorpseType) = tracker.modify { it.corpsesLooted.addOrPut(type, 1) } + + @SubscribeEvent + fun onCorpseLoot(event: CorpseLootedEvent) { + addLootedCorpse(event.corpseType) + for ((itemName, amount) in event.loot) { + NEUInternalName.fromItemNameOrNull(itemName)?.let { item -> + tracker.modify { + it.addItem(event.corpseType, item, amount) + } + } + } + } + + private fun drawDisplay(bucketData: BucketData): List = buildList { + addSearchString("§b§lMineshaft Corpse Profit Tracker") + tracker.addBucketSelector(this, bucketData, "Corpse Type") + + if (bucketData.getCorpseCount() == 0L) return@buildList + + var profit = tracker.drawItems(bucketData, { true }, this) + val applicableKeys: List = bucketData.getSelectedBucket()?.let { listOf(it) } + ?: enumValues().toList().filter { bucketData.corpsesLooted[it] != null } + var totalKeyCost = 0.0 + var totalKeyCount = 0 + val keyCostStrings = buildList { + applicableKeys.forEach { keyData -> + keyData.key?.let { key -> + val keyName = key.itemName + val price = key.getPrice() + val count = bucketData.corpsesLooted[keyData] ?: 0 + val totalPrice = price * count + if (totalPrice > 0) { + profit -= totalPrice + totalKeyCost += totalPrice + totalKeyCount += count.toInt() + add("§7${count}x $keyName§7: §c-${totalPrice.shortFormat()}") + } + } + } + } + + if (totalKeyCount > 0) { + val specificKeyFormat = if (applicableKeys.count() == 1) applicableKeys.first().key!!.itemName else "§eCorpse Keys" + val keyFormat = "§7${totalKeyCount}x $specificKeyFormat§7: §c-${totalKeyCost.shortFormat()}" + add( + if (applicableKeys.count() == 1) Renderable.string(keyFormat).toSearchable() + else Renderable.hoverTips( + keyFormat, + keyCostStrings, + ).toSearchable(), + ) + } + + add(tracker.addTotalProfit(profit, bucketData.getCorpseCount(), "loot")) + + tracker.addPriceFromButton(this) + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent) { + if (!isEnabled()) return + tracker.renderDisplay(config.position) + } + + @SubscribeEvent + fun onIslandChange(event: IslandChangeEvent) { + if (event.newIsland == IslandType.MINESHAFT || event.newIsland == IslandType.DWARVEN_MINES) { + tracker.firstUpdate() + } + } + + fun resetCommand() { + tracker.resetCommand() + } + + fun isEnabled() = + config.enabled && IslandType.DWARVEN_MINES.isInIsland() && (!config.onlyInMineshaft || IslandType.MINESHAFT.isInIsland()) +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseType.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseType.kt new file mode 100644 index 000000000..42567d5f3 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/CorpseType.kt @@ -0,0 +1,15 @@ +package at.hannibal2.skyhanni.features.mining.glacitemineshaft + +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName + +enum class CorpseType(val displayName: String, private val keyName: String? = null) { + LAPIS("§9Lapis"), + TUNGSTEN("§7Tungsten", "TUNGSTEN_KEY"), + UMBER("§6Umber", "UMBER_KEY"), + VANGUARD("§fVanguard", "SKELETON_KEY"), + ; + + val key by lazy { keyName?.asInternalName() } + + override fun toString(): String = displayName +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/MineshaftCorpseProfitPer.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/MineshaftCorpseProfitPer.kt new file mode 100644 index 000000000..eed5f2b9d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/glacitemineshaft/MineshaftCorpseProfitPer.kt @@ -0,0 +1,56 @@ +package at.hannibal2.skyhanni.features.mining.glacitemineshaft + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.mining.CorpseLootedEvent +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.CollectionUtils.sortedDesc +import at.hannibal2.skyhanni.utils.ItemUtils.itemName +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NEUItems.getPrice +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +@SkyHanniModule +object MineshaftCorpseProfitPer { + private val config get() = SkyHanniMod.feature.mining.mineshaft + + @SubscribeEvent + fun onFossilExcavation(event: CorpseLootedEvent) { + if (!config.profitPerCorpseLoot) return + val loot = event.loot + + var totalProfit = 0.0 + val map = mutableMapOf() + for ((name, amount) in loot) { + if (name == "§bGlacite Powder") continue + NEUInternalName.fromItemNameOrNull(name)?.let { + val pricePer = it.getPrice() + if (pricePer == -1.0) continue + val profit = amount * pricePer + val text = "§eFound $name §8${amount.addSeparators()}x §7(§6${profit.shortFormat()}§7)" + map[text] = profit + totalProfit += profit + } + } + + val corpseType = event.corpseType + val name = corpseType.displayName + + corpseType.key?.let { + val keyName = it.itemName + val price = it.getPrice() + + map["$keyName: §c-${price.shortFormat()}"] = -price + totalProfit -= price + } + + val hover = map.sortedDesc().keys.toMutableList() + val profitPrefix = if (totalProfit < 0) "§c" else "§6" + val totalMessage = "Profit for $name Corpse§e: $profitPrefix${totalProfit.shortFormat()}" + hover.add("") + hover.add("§e$totalMessage") + ChatUtils.hoverableChat(totalMessage, hover) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/CorpseAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/CorpseAPI.kt deleted file mode 100644 index e49ca462f..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/CorpseAPI.kt +++ /dev/null @@ -1,87 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.mineshaft - -import at.hannibal2.skyhanni.data.IslandType -import at.hannibal2.skyhanni.events.LorenzChatEvent -import at.hannibal2.skyhanni.events.mining.CorpseLootedEvent -import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule -import at.hannibal2.skyhanni.utils.ItemUtils -import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland -import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher -import at.hannibal2.skyhanni.utils.RegexUtils.matches -import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent - -@SkyHanniModule -object CorpseAPI { - - private val patternGroup = RepoPattern.group("mining.mineshaft") - private val chatPatternGroup = patternGroup.group("chat") - - /** - * REGEX-TEST: §r§b§l§r§9§lLAPIS §r§b§lCORPSE LOOT! - * REGEX-TEST: §r§b§l§r§7§lTUNGSTEN §r§b§lCORPSE LOOT! - * REGEX-TEST: §r§b§l§r§6§lUMBER §r§b§lCORPSE LOOT! - * REGEX-TEST: §r§b§l§r§f§lVANGUARD §r§b§lCORPSE LOOT! - */ - private val startPattern by chatPatternGroup.pattern( - "start", - " {2}§r§b§l§r§(?.)§l(?.*) §r§b§lCORPSE LOOT! ?" - ) - - /** - * REGEX-TEST: §a§l▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ - */ - private val endPattern by chatPatternGroup.pattern("end", "§a§l▬{64}") - - /** - * REGEX-TEST: §r§9☠ Fine Onyx Gemstone §r§8x2 - */ - private val itemPattern by chatPatternGroup.pattern("item", " {4}§r(?.+)") - - private var inLoot = false - private val loot = mutableListOf>() - - private var corpseType: CorpseType? = null - - @SubscribeEvent - fun onChat(event: LorenzChatEvent) { - if (!IslandType.MINESHAFT.isInIsland()) return - - val message = event.message - - startPattern.matchMatcher(message) { - inLoot = true - val name = group("name") - corpseType = CorpseType.valueOf(name) - return - } - - if (!inLoot) return - - if (endPattern.matches(message)) { - corpseType?.let { - CorpseLootedEvent(it, loot.toList()).postAndCatch() - } - corpseType = null - loot.clear() - inLoot = false - return - } - var pair = itemPattern.matchMatcher(message) { - /** - * TODO fix the bug that readItemAmount produces two different outputs: - * §r§fEnchanted Book -> §fEnchanted - * §fEnchanted Book §r§8x -> §fEnchanted Book - * - * also maybe this is no bug, as enchanted book is no real item? - */ - ItemUtils.readItemAmount(group("item")) - } ?: return - // Workaround: If it is an enchanted book, we assume it is a paleontologist I book - if (pair.first.let { it == "§fEnchanted" || it == "§fEnchanted Book" }) { -// pair = "Paleontologist I" to pair.second - pair = "§9Ice Cold I" to pair.second - } - loot.add(pair) - } -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/CorpseType.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/CorpseType.kt deleted file mode 100644 index 7ddfca867..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/CorpseType.kt +++ /dev/null @@ -1,13 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.mineshaft - -import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName - -enum class CorpseType(val displayName: String, private val keyName: String? = null) { - LAPIS("§9Lapis"), - TUNGSTEN("§7Tungsten", "TUNGSTEN_KEY"), - UMBER("§6Umber", "UMBER_KEY"), - VANGUARD("§fVanguard", "SKELETON_KEY"), - ; - - val key by lazy { keyName?.asInternalName() } -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/MineshaftCorpseProfitPer.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/MineshaftCorpseProfitPer.kt deleted file mode 100644 index 102391b87..000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/mineshaft/MineshaftCorpseProfitPer.kt +++ /dev/null @@ -1,56 +0,0 @@ -package at.hannibal2.skyhanni.features.mining.mineshaft - -import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.events.mining.CorpseLootedEvent -import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule -import at.hannibal2.skyhanni.utils.ChatUtils -import at.hannibal2.skyhanni.utils.CollectionUtils.sortedDesc -import at.hannibal2.skyhanni.utils.ItemUtils.itemName -import at.hannibal2.skyhanni.utils.NEUInternalName -import at.hannibal2.skyhanni.utils.NEUItems.getPrice -import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators -import at.hannibal2.skyhanni.utils.NumberUtil.shortFormat -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent - -@SkyHanniModule -object MineshaftCorpseProfitPer { - private val config get() = SkyHanniMod.feature.mining.mineshaft - - @SubscribeEvent - fun onFossilExcavation(event: CorpseLootedEvent) { - if (!config.profitPerCorpseLoot) return - val loot = event.loot - - var totalProfit = 0.0 - val map = mutableMapOf() - for ((name, amount) in loot) { - if (name == "§bGlacite Powder") continue - NEUInternalName.fromItemNameOrNull(name)?.let { - val pricePer = it.getPrice() - if (pricePer == -1.0) continue - val profit = amount * pricePer - val text = "§eFound $name §8${amount.addSeparators()}x §7(§6${profit.shortFormat()}§7)" - map[text] = profit - totalProfit += profit - } - } - - val corpseType = event.corpseType - val name = corpseType.displayName - - corpseType.key?.let { - val keyName = it.itemName - val price = it.getPrice() - - map["$keyName: §c-${price.shortFormat()}"] = -price - totalProfit -= price - } - - val hover = map.sortedDesc().keys.toMutableList() - val profitPrefix = if (totalProfit < 0) "§c" else "§6" - val totalMessage = "Profit for $name Corpse§e: $profitPrefix${totalProfit.shortFormat()}" - hover.add("") - hover.add("§e$totalMessage") - ChatUtils.hoverableChat(totalMessage, hover) - } -} -- cgit