package at.hannibal2.skyhanni.data.model import at.hannibal2.skyhanni.data.ProfileStorageData import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent import at.hannibal2.skyhanni.events.WidgetUpdateEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.DelayedRun import at.hannibal2.skyhanni.utils.ItemUtils.getLore import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.RegexUtils.groupOrNull import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher import at.hannibal2.skyhanni.utils.StringUtils.allLettersFirstUppercase import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraft.client.Minecraft import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import org.intellij.lang.annotations.Language import java.util.EnumMap import java.util.regex.Pattern import kotlin.math.roundToInt @Suppress("MaxLineLength") enum class SkyblockStat( val icon: String, @Language("RegExp") tabListPatternS: String, @Language("RegExp") menuPatternS: String, ) { DAMAGE("§c❁", "", ""), // Weapon only HEALTH("§c❤", " Health: §r§c❤(?.*)", " §c❤ Health §f(?.*)"), // TODO get from action bar DEFENSE("§a❈", " Defense: §r§a❈(?.*)", " §a❈ Defense §f(?.*)"), // TODO get from action bar STRENGTH("§c❁", " Strength: §r§c❁(?.*)", " §c❁ Strength §f(?.*)"), INTELLIGENCE("§b✎", " Intelligence: §r§b✎(?.*)", " §b✎ Intelligence §f(?.*)"), // TODO get from action bar CRIT_DAMAGE("§9☠", " Crit Damage: §r§9☠(?.*)", " §9☠ Crit Damage §f(?.*)"), CRIT_CHANCE("§9☣", " Crit Chance: §r§9☣(?.*)", " §9☣ Crit Chance §f(?.*)"), FEROCITY("§c⫽", " Ferocity: §r§c⫽(?.*)", " §c⫽ Ferocity §f(?.*)"), BONUS_ATTACK_SPEED("§e⚔", " Attack Speed: §r§e⚔(?.*)", " §e⚔ Bonus Attack Speed §f(?.*)"), ABILITY_DAMAGE("§c๑", " Ability Damage: §r§c๑(?.*)", " §c๑ Ability Damage §f(?.*)"), HEALTH_REGEN("§c❣", " Health Regen: §r§c❣(?.*)", " §c❣ Health Regen §f(?.*)"), VITALITY("§4♨", " Vitality: §r§4♨(?.*)", " §4♨ Vitality §f(?.*)"), MENDING("§a☄", " Mending: §r§a☄(?.*)", " §a☄ Mending §f(?.*)"), TRUE_DEFENCE("§7❂", " True Defense: §r§f❂(?.*)", " §f❂ True Defense §f(?.*)"), SWING_RANGE("§eⓈ", " Swing Range: §r§eⓈ(?.*)", " §eⓈ Swing Range §f(?.*)"), SPEED("§f✦", " Speed: §r§f✦(?.*)", " §f✦ Speed §f(?.*)"), // TODO add the way sba did get it (be careful with 500+ Speed) SEA_CREATURE_CHANCE("§3α", " Sea Creature Chance: §r§3α(?.*)", " §3α Sea Creature Chance §f(?.*)"), MAGIC_FIND("§b✯", " Magic Find: §r§b✯(?.*)", " §b✯ Magic Find §f(?.*)"), PET_LUCK("§d♣", " Pet Luck: §r§d♣(?.*)", " §d♣ Pet Luck §f(?.*)"), FISHING_SPEED("§b☂", " Fishing Speed: §r§b☂(?.*)", " §b☂ Fishing Speed §f(?.*)"), DOUBLE_HOOK_CHANCE("§9⚓", " Double Hook Chance: §r§9⚓(?.*)", ""), BONUS_PEST_CHANCE("§2ൠ", " (?:§r§7§m)?Bonus Pest Chance: (?:§r§2)?ൠ(?.*)", " (?:§7§m|§2)ൠ Bonus Pest Chance (?:§f)?(?.*)"), COMBAT_WISDOM("§3☯", " Combat Wisdom: §r§3☯(?.*)", " §3☯ Combat Wisdom §f(?.*)"), MINING_WISDOM("§3☯", " Mining Wisdom: §r§3☯(?.*)", " §3☯ Mining Wisdom §f(?.*)"), FARMING_WISDOM("§3☯", " Farming Wisdom: §r§3☯(?.*)", " §3☯ Farming Wisdom §f(?.*)"), FORAGING_WISDOM("§3☯", " Foraging Wisdom: §r§3☯(?.*)", " §3☯ Foraging Wisdom §f(?.*)"), FISHING_WISDOM("§3☯", " Fishing Wisdom: §r§3☯(?.*)", " §3☯ Fishing Wisdom §f(?.*)"), ENCHANTING_WISDOM("§3☯", " Enchanting Wisdom: §r§3☯(?.*)", " §3☯ Enchanting Wisdom §f(?.*)"), ALCHEMY_WISDOM("§3☯", " Alchemy Wisdom: §r§3☯(?.*)", " §3☯ Alchemy Wisdom §f(?.*)"), CARPENTRY_WISDOM("§3☯", " Carpentry Wisdom: §r§3☯(?.*)", " §3☯ Carpentry Wisdom §f(?.*)"), RUNECRAFTING_WISDOM("§3☯", " Runecrafting Wisdom: §r§3☯(?.*)", " §3☯ Runecrafting Wisdom §f(?.*)"), SOCIAL_WISDOM("§3☯", " Social Wisdom: §r§3☯(?.*)", " §3☯ Social Wisdom §f(?.*)"), TAMING_WISDOM("§3☯", " Taming Wisdom: §r§3☯(?.*)", " §3☯ Taming Wisdom §f(?.*)"), MINING_SPEED("§6⸕", " Mining Speed: §r§6⸕(?.*)", " §6⸕ Mining Speed §f(?.*)"), BREAKING_POWER("§2Ⓟ", "", " §2Ⓟ Breaking Power §f(?.*)"), PRISTINE("§5✧", " Pristine: §r§5✧(?.*)", " §5✧ Pristine §f(?.*)"), FORAGING_FORTUNE("§☘", " Foraging Fortune: §r§6☘(?.*)", " §6☘ Foraging Fortune §f(?.*)"), FARMING_FORTUNE("§6☘", " (?:§r§7§m)?Farming Fortune: (?:§r§6)?☘(?.*)", " (?:§7§m|§6)☘ Farming Fortune (?:§f)?(?.*)"), MINING_FORTUNE("§6☘", " Mining Fortune: §r§6☘(?.*)", " §6☘ Mining Fortune §f(?.*)"), FEAR("§5☠", " Fear: §r§5☠(?.*)", " §5☠ Fear §f(?.*)"), COLD_RESISTANCE("§b❄", " Cold Resistance: §r§b❄(?.*)", ""), WHEAT_FORTUNE("§7☘", "", " §7(?:§m)☘ Wheat Fortune (?.*)"), CARROT_FORTUNE("§7☘", "", " §7(?:§m)☘ Carrot Fortune (?.*)"), POTATO_FORTUNE("§7☘", "", " §7(?:§m)☘ Potato Fortune (?.*)"), PUMPKIN_FORTUNE("§7☘", "", " §7(?:§m)☘ Pumpkin Fortune (?.*)"), MELON_FORTUNE("§7☘", "", " §7(?:§m)☘ Melon Fortune (?.*)"), MUSHROOM_FORTUNE("§7☘", "", " §7(?:§m)☘ Mushroom Fortune (?.*)"), CACTUS_FORTUNE("§7☘", "", " §7(?:§m)☘ Cactus Fortune (?.*)"), NETHER_WART_FORTUNE("§7☘", "", " §7(?:§m)☘ Nether Wart Fortune (?.*)"), COCOA_BEANS_FORTUNE("§7☘", "", " §7(?:§m)☘ Cocoa Beans Fortune (?.*)"), SUGAR_CANE_FORTUNE("§7☘", "", " §7(?:§m)☘ Sugar Cane Fortune (?.*)"), MINING_SPREAD("§e▚", " (§r§7§m)?Mining Spread: (§r§e)?▚(?.*)", " (§7§m|§e)▚ Mining Spread (§f)?(?.*)"), GEMSTONE_SPREAD("§e▚", " (§r§7§m)?Mining Spread: (§r§e)?▚(?.*)", " (§7§m|§e)▚ Gemstone Spread (§f)?(?.*)"), ORE_FORTUNE("§6☘", " Ore Fortune: §r§6☘(?.*)", " §6☘ Ore Fortune §f103"), DWARVEN_METAL_FORTUNE("§6☘", " Dwarven Metal Fortune: §r§6☘(?.*)", " §6☘ Dwarven Metal Fortune §f(?.*)"), BLOCK_FORTUNE("§6☘", " Block Fortune: §r§6☘(?.*)", " §6☘ Block Fortune §f(?.*)"), GEMSTONE_FORTUNE("§6☘", " Gemstone Fortune: §r§6☘(?.*)", " §6☘ Gemstone Fortune §f(?.*)"), HEAT_RESISTANCE("§c♨", " Heat Resistance: §r§c♨(?.*)", " §c♨ Heat Resistance §f(?.*)"), UNKNOWN("§c?", "", "") ; var lastKnownValue: Double? get() = ProfileStorageData.profileSpecific?.stats?.get(this) set(value) { ProfileStorageData.profileSpecific?.stats?.set(this, value) } var lastSource: StatSourceType = StatSourceType.UNKNOWN val capitalizedName = name.lowercase().allLettersFirstUppercase() val iconWithName = "$icon $capitalizedName" val keyName = name.lowercase().replace('_', '.') val displayValue get() = lastKnownValue?.let { icon + it.roundToInt() } val tablistPattern by RepoPattern.pattern("stats.tablist.$keyName", tabListPatternS) val menuPattern by RepoPattern.pattern("stats.menu.$keyName", menuPatternS) fun asString(value: Int) = (if (value > 0) "+" else "") + value.toString() + " " + this.icon @SkyHanniModule companion object { val fontSizeOfLargestIcon by lazy { entries.maxOf { Minecraft.getMinecraft().fontRendererObj.getStringWidth(it.icon) } + 1 } fun getValueOrNull(string: String): SkyblockStat? = entries.firstOrNull { it.name == string } fun getValue(string: String): SkyblockStat = getValueOrNull(string) ?: UNKNOWN init { entries.forEach { it.tablistPattern it.menuPattern } } @SubscribeEvent fun onInventory(event: InventoryFullyOpenedEvent) { if (!LorenzUtils.inSkyBlock) return onSkyblockMenu(event) onStatsMenu(event) } private const val PLAYER_STATS_SLOT_INDEX = 13 private fun onSkyblockMenu(event: InventoryFullyOpenedEvent) { if (event.inventoryName != "SkyBlock Menu") return val list = event.inventoryItems[PLAYER_STATS_SLOT_INDEX]?.getLore() ?: return DelayedRun.runNextTick { // Delayed to not impact opening time assignEntry(list, StatSourceType.SKYBLOCK_MENU) { it.menuPattern } } } private val statsMenuRelevantSlotIndexes = listOf(15, 16, 24, 25) private fun onStatsMenu(event: InventoryFullyOpenedEvent) { if (event.inventoryName != "Your Equipment and Stats") return val list = statsMenuRelevantSlotIndexes.mapNotNull { event.inventoryItems[it]?.getLore() }.flatten() if (list.isEmpty()) return DelayedRun.runNextTick { // Delayed to not impact opening time assignEntry(list, StatSourceType.STATS_MENU) { it.menuPattern } } } @SubscribeEvent fun onTabList(event: WidgetUpdateEvent) { if (!event.isWidget(TabWidget.STATS, TabWidget.DUNGEON_SKILLS_AND_STATS)) return val type = if (event.isWidget(TabWidget.DUNGEON_SKILLS_AND_STATS)) StatSourceType.TABLIST_DUNGEON else StatSourceType.TABLIST assignEntry(event.lines, type) { it.tablistPattern } } private fun assignEntry(lines: List, type: StatSourceType, pattern: (SkyblockStat) -> Pattern) { for (line in lines) for (entry in entries) { val matchResult = pattern(entry).matchMatcher(line) { groupOrNull("value")?.replace("[,%]".toRegex(), "")?.toDouble() } ?: continue entry.lastKnownValue = matchResult entry.lastSource = type break // Exit the inner loop once a match is found } } } } class SkyblockStatList : EnumMap(SkyblockStat::class.java), Map { operator fun minus(other: SkyblockStatList): SkyblockStatList { return SkyblockStatList().apply { val keys = this.keys + other.keys for (key in keys) { this[key] = (this@SkyblockStatList[key] ?: 0.0) - (other[key] ?: 0.0) } } } companion object { fun mapOf(vararg list: Pair) = SkyblockStatList().apply { for ((key, value) in list) { this[key] = value } } } } enum class StatSourceType { UNKNOWN, SKYBLOCK_MENU, STATS_MENU, TABLIST, TABLIST_DUNGEON, }