diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 794a7a568803..79728810c93f 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -30,6 +30,7 @@ import at.hannibal2.skyhanni.data.GuiData import at.hannibal2.skyhanni.data.GuiEditManager import at.hannibal2.skyhanni.data.GuildAPI import at.hannibal2.skyhanni.data.HighlightOnHoverSlot +import at.hannibal2.skyhanni.data.HotmData import at.hannibal2.skyhanni.data.HypixelData import at.hannibal2.skyhanni.data.ItemAddManager import at.hannibal2.skyhanni.data.ItemClickData @@ -308,6 +309,7 @@ import at.hannibal2.skyhanni.features.mining.ColdOverlay import at.hannibal2.skyhanni.features.mining.DeepCavernsGuide import at.hannibal2.skyhanni.features.mining.GoldenGoblinHighlight import at.hannibal2.skyhanni.features.mining.HighlightMiningCommissionMobs +import at.hannibal2.skyhanni.features.mining.HotmFeatures import at.hannibal2.skyhanni.features.mining.KingTalismanHelper import at.hannibal2.skyhanni.features.mining.MiningCommissionsBlocksColor import at.hannibal2.skyhanni.features.mining.MiningNotifications @@ -571,6 +573,7 @@ class SkyHanniMod { loadModule(ChatUtils) loadModule(FixedRateTimerManager()) loadModule(ChromaManager) + loadModule(HotmData) loadModule(ContributorManager) loadModule(TabComplete) loadModule(HypixelBazaarFetcher) @@ -715,6 +718,7 @@ class SkyHanniMod { loadModule(StatsTuning()) loadModule(NonGodPotEffectDisplay()) loadModule(SoopyGuessBurrow()) + loadModule(HotmFeatures()) loadModule(DianaProfitTracker) loadModule(DianaFixChat()) loadModule(MythologicalCreatureTracker) diff --git a/src/main/java/at/hannibal2/skyhanni/api/HotmAPI.kt b/src/main/java/at/hannibal2/skyhanni/api/HotmAPI.kt new file mode 100644 index 000000000000..a9940670dab2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/api/HotmAPI.kt @@ -0,0 +1,117 @@ +package at.hannibal2.skyhanni.api + +import at.hannibal2.skyhanni.data.HotmData +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemCategory +import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getDrillUpgrades +import at.hannibal2.skyhanni.utils.StringUtils.firstLetterUppercase +import at.hannibal2.skyhanni.utils.TimeLimitedCache +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraft.item.ItemStack +import kotlin.time.Duration.Companion.seconds + +object HotmAPI { + + fun copyCurrentTree() = HotmData.storage?.deepCopy() + + val activeMiningAbility get() = HotmData.abilities.firstOrNull { it.enabled } + + private val blueGoblinEgg = "GOBLIN_OMELETTE_BLUE_CHEESE".asInternalName() + + private val blueEggCache = TimeLimitedCache(10.0.seconds) + val isBlueEggActive + get() = InventoryUtils.getItemInHand()?.let { + blueEggCache.getOrPut(it) { + it.getItemCategoryOrNull() == ItemCategory.DRILL && it.getDrillUpgrades() + ?.contains(blueGoblinEgg) == true + } + } == true + + enum class Powder() { + MITHRIL, + GEMSTONE, + GLACITE, + + ; + + val lowName = name.lowercase().firstLetterUppercase() + + val heartPattern by RepoPattern.pattern( + "inventory.${name.lowercase()}.heart", + "§7$lowName Powder: §a§.(?[\\d,]+)" + ) + val resetPattern by RepoPattern.pattern( + "inventory.${name.lowercase()}.reset", + "\\s+§8- §.(?[\\d,]+) $lowName Powder" + ) + + fun pattern(isHeart: Boolean) = if (isHeart) heartPattern else resetPattern + + fun getStorage() = ProfileStorageData.profileSpecific?.mining?.powder?.get(this) + + fun getCurrent() = getStorage()?.available ?: 0L + + fun setCurrent(value: Long) { + getStorage()?.available = value + } + + fun addCurrent(value: Long) { + setCurrent(getCurrent() + value) + } + + fun getTotal() = getStorage()?.total ?: 0L + + fun setTotal(value: Long) { + getStorage()?.total = value + } + + fun addTotal(value: Long) { + setTotal(getTotal() + value) + } + + /** Use when new powder gets collected*/ + fun gain(value: Long) { + addTotal(value) + addCurrent(value) + } + + fun reset() { + setCurrent(0) + setTotal(0) + } + } + + var skymall: SkymallPerk? = null + + var mineshaftMayhem: MayhemPerk? = null + + enum class SkymallPerk(chat: String, itemString: String) { + MINING_SPEED("Gain §r§a+100 §r§6⸕ Mining Speed§r§f.", "Gain §a+100 §6⸕ Mining Speed§7."), + MINING_FORTUNE("Gain §r§a+50 §r§6☘ Mining Fortune§r§f.", "Gain §a+50 §6☘ Mining Fortune§7."), + EXTRA_POWDER("Gain §r§a+15% §r§fmore Powder while mining.", "Gain §a+15% §7more Powder while mining."), + ABILITY_COOLDOWN("Reduce Pickaxe Ability cooldown by §r§a20%§r§f.", "Reduce Pickaxe Ability cooldown by"), + GOBLIN_CHANCE("§r§a10x §r§fchance to find Golden and Diamond Goblins.", "§a10x §7chance to find Golden and"), + TITANIUM("Gain §r§a5x §r§9Titanium §r§fdrops", "Gain §a+15% §7more Powder while mining.") + ; + + private val patternName = name.lowercase().replace("_", ".") + + val chatPattern by RepoPattern.pattern("mining.hotm.skymall.chat.$patternName", chat) + val itemPattern by RepoPattern.pattern("mining.hotm.skymall.item.$patternName", itemString) + } + + enum class MayhemPerk(chat: String) { + SCRAP_CHANCE("Your §r§9Suspicious Scrap §r§7chance was buffed by your §r§aMineshaft Mayhem §r§7perk!"), + MINING_FORTUNE("You received a §r§a§r§6☘ Mining Fortune §r§7buff from your §r§aMineshaft Mayhem §r§7perk!"), + MINING_SPEED("You received a §r§a§r§6⸕ Mining Speed §r§7buff from your §r§aMineshaft Mayhem §r§7perk!"), + COLD_RESISTANCE("You received a §r§a§r§b❄ Cold Resistance §r§7buff from your §r§aMineshaft Mayhem §r§7perk!"), + ABILITY_COOLDOWN("Your Pickaxe Ability cooldown was reduced §r§7from your §r§aMineshaft Mayhem §r§7perk!"); + + private val patternName = name.lowercase().replace("_", ".") + + val chatPattern by RepoPattern.pattern("mining.hotm.mayhem.chat.$patternName", chat) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/HotmConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/HotmConfig.java new file mode 100644 index 000000000000..cbe7d0be07ce --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/HotmConfig.java @@ -0,0 +1,27 @@ +package at.hannibal2.skyhanni.config.features.mining; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; + +public class HotmConfig { + + @Expose + @ConfigOption(name = "Enabled Highlight", desc = "Highlights enabled perks in the hotm tree green and disabled red. Not unlocked perks are highlighted gray.") + @ConfigEditorBoolean + @FeatureToggle + public boolean highlightEnabledPerks = true; + + @Expose + @ConfigOption(name = "Level Stack", desc = "Shows the level of a perk as item stacks.") + @ConfigEditorBoolean + @FeatureToggle + public boolean levelStackSize = true; + + @Expose + @ConfigOption(name = "Token Stack", desc = "Shows unused tokens on the heart.") + @ConfigEditorBoolean + @FeatureToggle + public boolean tokenStackSize = true; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java index 9c4af3882f17..05ad3fb2784e 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/MiningConfig.java @@ -13,6 +13,10 @@ public class MiningConfig { @Category(name = "Mining Event Tracker", desc = "Settings for the Mining Event Tracker") public MiningEventConfig miningEvent = new MiningEventConfig(); + @Expose + @Category(name = "HotM", desc = "Settings for Heart of the Mountain") + public HotmConfig hotm = new HotmConfig(); + @Expose @ConfigOption(name = "Powder Tracker", desc = "") @Accordion 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 747d5c116e6d..d73e707ab69c 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java +++ b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java @@ -1,8 +1,10 @@ package at.hannibal2.skyhanni.config.storage; +import at.hannibal2.skyhanni.api.HotmAPI; import at.hannibal2.skyhanni.api.SkillAPI; import at.hannibal2.skyhanni.data.IslandType; import at.hannibal2.skyhanni.data.MaxwellAPI; +import at.hannibal2.skyhanni.data.jsonobjects.local.HotmTree; import at.hannibal2.skyhanni.data.model.ComposterUpgrade; import at.hannibal2.skyhanni.features.combat.endernodetracker.EnderNodeTracker; import at.hannibal2.skyhanni.features.combat.ghostcounter.GhostData; @@ -490,6 +492,27 @@ public static class MiningConfig { @Expose public ExcavatorProfitTracker.Data fossilExcavatorProfitTracker = new ExcavatorProfitTracker.Data(); + + @Expose + public HotmTree hotmTree = new HotmTree(); + + @Expose + public Map powder = new HashMap<>(); + + public static class PowderStorage { + + @Expose + public Long available; + + @Expose + public Long total; + } + + @Expose + public int tokens; + + @Expose + public int availableTokens; } @Expose diff --git a/src/main/java/at/hannibal2/skyhanni/data/HotmData.kt b/src/main/java/at/hannibal2/skyhanni/data/HotmData.kt new file mode 100644 index 000000000000..f23795acdfaf --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/HotmData.kt @@ -0,0 +1,612 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.api.HotmAPI +import at.hannibal2.skyhanni.api.HotmAPI.MayhemPerk +import at.hannibal2.skyhanni.api.HotmAPI.SkymallPerk +import at.hannibal2.skyhanni.config.storage.ProfileSpecificStorage +import at.hannibal2.skyhanni.data.jsonobjects.local.HotmTree +import at.hannibal2.skyhanni.events.DebugDataCollectEvent +import at.hannibal2.skyhanni.events.InventoryCloseEvent +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.IslandChangeEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.ProfileJoinEvent +import at.hannibal2.skyhanni.events.ScoreboardChangeEvent +import at.hannibal2.skyhanni.features.gui.customscoreboard.ScoreboardPattern +import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.ConditionalUtils.transformIf +import at.hannibal2.skyhanni.utils.DelayedRun +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.ItemUtils.name +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.formatLong +import at.hannibal2.skyhanni.utils.RegexUtils.indexOfFirstMatch +import at.hannibal2.skyhanni.utils.RegexUtils.matchFirst +import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.StringUtils.allLettersFirstUppercase +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraft.inventory.Slot +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.math.ceil +import kotlin.math.floor +import kotlin.math.pow + +enum class HotmData( + guiName: String, + val maxLevel: Int, + val costFun: ((Int) -> (Double?)), + val rewardFun: ((Int) -> (Map)), +) { + + MINING_SPEED("Mining Speed", + 50, + { currentLevel -> (currentLevel + 2.0).pow(3) }, + { level -> mapOf(HotmReward.MINING_SPEED to level * 20.0) }), + MINING_FORTUNE("Mining Fortune", + 50, + { currentLevel -> (currentLevel + 2.0).pow(3.05) }, + { level -> mapOf(HotmReward.MINING_FORTUNE to level * 5.0) }), + QUICK_FORGE("Quick Forge", + 20, + { currentLevel -> (currentLevel + 2.0).pow(4) }, + { level -> mapOf(HotmReward.FORGE_TIME_DECREASE to 10.0 + (level * 0.5)) }), + TITANIUM_INSANIUM("Titanium Insanium", + 50, + { currentLevel -> (currentLevel + 2.0).pow(3.1) }, + { level -> mapOf(HotmReward.TITANIUM_CHANCE to 2.0 + (level * 0.1)) }), + DAILY_POWDER("Daily Powder", + 100, + { currentLevel -> 200.0 + (currentLevel * 18.0) }, + { level -> mapOf(HotmReward.DAILY_POWDER to (200.0 + ((level - 1.0) * 18.0)) * 2.0) }), + LUCK_OF_THE_CAVE("Luck of the Cave", + 45, + { currentLevel -> (currentLevel + 2.0).pow(3.07) }, + { level -> mapOf(HotmReward.EXTRA_CHANCE_TRIGGER_RARE_OCCURRENCES to 5.0 + level) }), + CRYSTALLIZED("Crystallized", 30, { currentLevel -> (currentLevel + 2.0).pow(3.4) }, { level -> + mapOf( + HotmReward.MINING_SPEED to 20.0 + ((level - 1.0) * 6.0), + HotmReward.MINING_FORTUNE to 20.0 + ((level - 1.0) * 5.0) + ) + }), + EFFICIENT_MINER("Efficient Miner", + 100, + { currentLevel -> (currentLevel + 2.0).pow(2.6) }, + { level -> mapOf(HotmReward.AVERAGE_BLOCK_BREAKS to (10.0 + (level * 0.4)) * (1.0 + (level * 0.05))) }), + ORBITER("Orbiter", + 80, + { currentLevel -> (currentLevel + 1.0) * 70.0 }, + { level -> mapOf(HotmReward.CHANCE_EXTRA_XP_ORBS to 0.2 + (level * 0.01)) }), + SEASONED_MINEMAN("Seasoned Mineman", + 100, + { currentLevel -> (currentLevel + 2.0).pow(2.3) }, + { level -> mapOf(HotmReward.MINING_WISDOM to 5.0 + (level * 0.1)) }), + MOLE("Mole", + 190, + { currentLevel -> (currentLevel + 2.0).pow(2.2) }, + { level -> mapOf(HotmReward.AVERAGE_BLOCK_BREAKS to 1.0 + ((level + 9.0) * 0.05 * ((level + 8) % 20))) }), + PROFESSIONAL("Professional", + 140, + { currentLevel -> (currentLevel + 2.0).pow(2.3) }, + { level -> mapOf(HotmReward.MINING_SPEED to 50.0 + (level * 5.0)) }), + LONESOME_MINER("Lonesome Miner", + 45, + { currentLevel -> (currentLevel + 2.0).pow(3.07) }, + { level -> mapOf(HotmReward.COMBAT_STAT_BOOST to 5.0 + ((level - 1.0) * 0.5)) }), + GREAT_EXPLORER("Great Explorer", 20, { currentLevel -> (currentLevel + 2.0).pow(4.0) }, { level -> + mapOf( + HotmReward.CHANCE_OF_TREASURE_CHEST to (0.2 * (0.2 + 0.04 * (level - 1.0))), + HotmReward.LOCKS_OF_TREASURE_CHEST to 1 + level * 0.2 + ) + }), + FORTUNATE("Fortunate", + 20, + { currentLevel -> (currentLevel + 1.0).pow(3.05) }, + { level -> mapOf(HotmReward.MINING_FORTUNE to 20.0 + (level * 4.0)) }), + POWDER_BUFF("Powder Buff", 50, { currentLevel -> (currentLevel + 1.0).pow(3.2) }, { level -> + mapOf( + HotmReward.MORE_MITHRIL_POWER to level.toDouble(), HotmReward.MORE_GEMSTONE_POWER to level.toDouble() + ) + }), + MINING_SPEED_II("Mining Speed II", + 50, + { currentLevel -> (currentLevel + 2.0).pow(3.2) }, + { level -> mapOf(HotmReward.MINING_SPEED to level * 40.0) }), + MINING_FORTUNE_II("Mining Fortune II", + 50, + { currentLevel -> (currentLevel + 2.0).pow(3.2) }, + { level -> mapOf(HotmReward.MINING_FORTUNE to level * 5.0) }), + + // Static + + MINING_MADNESS("Mining Madness", 1, { null }, { + mapOf( + HotmReward.MINING_SPEED to 50.0, HotmReward.MINING_FORTUNE to 50.0 + ) + }), + SKY_MALL("Sky Mall", 1, { null }, { emptyMap() }), + PRECISION_MINING("Precision Mining", 1, { null }, { mapOf(HotmReward.MINING_SPEED_BOOST to 30.0) }), + FRONT_LOADED("Front Loaded", 1, { null }, { + mapOf( + HotmReward.MINING_SPEED to 100.0, + HotmReward.MINING_FORTUNE to 100.0, + HotmReward.MORE_BASE_MITHRIL_POWER to 2.0, + HotmReward.MORE_BASE_GEMSTONE_POWER to 2.0 + ) + }), + STAR_POWDER("Star Powder", 1, { null }, { mapOf(HotmReward.MORE_MITHRIL_POWER to 300.0) }), + GOBLIN_KILLER("Goblin Killer", 1, { null }, { emptyMap() }), + + // Abilities + + PICKOBULUS("Pickobulus", 3, { null }, { level -> + mapOf( + HotmReward.ABILITY_RADIUS to ceil(level * 0.5) + 1.0, + HotmReward.ABILITY_COOLDOWN to 130.0 - 10.0 * level + ) + }), + MINING_SPEED_BOOST("Mining Speed Boost", 3, { null }, { level -> + mapOf( + HotmReward.ABILITY_DURATION to level + 1.0, HotmReward.ABILITY_COOLDOWN to 10.0 + 5.0 * level + ) + }), + VEIN_SEEKER("Vein Seeker", 3, { null }, { level -> + mapOf( + HotmReward.ABILITY_RADIUS to level + 1.0, + HotmReward.ABILITY_DURATION to 10.0 + 2.0 * level, + HotmReward.ABILITY_COOLDOWN to 60.0 + ) + }), + MANIAC_MINER("Maniac Miner", 3, { null }, { level -> + mapOf( + HotmReward.ABILITY_DURATION to 5.0 + level * 5.0, HotmReward.ABILITY_COOLDOWN to 60.0 - level + ) + }), + + PEAK_OF_THE_MOUNTAIN("Peak of the Mountain", 10, { null }, { emptyMap() }), + + // Mining V3 + DAILY_GRIND("Daily Grind", + 100, + { currentLevel -> 218.0 + (18.0 * (currentLevel - 2.0)) }, + { level -> mapOf(HotmReward.DAILY_POWDER to 50.0 * level) }), + DUST_COLLECTOR( + "Dust Collector", + 20, + { currentLevel -> (currentLevel + 1.0).pow(4) }, + { level -> mapOf(HotmReward.FOSSIL_DUST to 1.0 * level) }), + WARM_HEARTED("Warm Hearted", + 50, + { currentLevel -> floor((currentLevel + 1.0).pow(3.1)) }, + { level -> mapOf(HotmReward.COLD_RESISTANCE to 0.2 * level) }), + + STRONG_ARM("Strong Arm", + 100, + { currentLevel -> floor((currentLevel + 1.0).pow(2.3)) }, + { level -> mapOf(HotmReward.MINING_SPEED to 5.0 * level) }), + NO_STONE_UNTURNED("No Stone Unturned", + 50, + { currentLevel -> floor((currentLevel + 1.0).pow(3.05)) }, + { level -> mapOf(HotmReward.UNKNOWN to 0.5 * level) }), + + SUB_ZERO_MINING("SubZero Mining", + 100, + { currentLevel -> floor((currentLevel + 1.0).pow(2.3)) }, + { level -> mapOf(HotmReward.MINING_FORTUNE to 1.0 * level) }), + SURVEYOR("Surveyor", + 20, + { currentLevel -> (currentLevel + 1.0).pow(4) }, + { level -> mapOf(HotmReward.UNKNOWN to 0.75 * level) }), + EAGER_ADVENTURER("Eager Adventurer", + 100, + { currentLevel -> floor((currentLevel + 1.0).pow(2.3)) }, + { level -> mapOf(HotmReward.MINING_SPEED to 2.0 * level) }), + + DEAD_MANS_CHEST("Dead Man's Chest", + 50, + { currentLevel -> floor((currentLevel + 1.0).pow(3.2)) }, + { level -> mapOf(HotmReward.UNKNOWN to 1.0 * level) }), + + GIFTS_FROM_THE_DEPARTED("Gifts from the Departed", + 100, + { currentLevel -> floor((currentLevel + 1.0).pow(2.45)) }, + { level -> mapOf(HotmReward.UNKNOWN to 0.2 * level) }), + + EXCAVATOR( + "Excavator", + 50, + { currentLevel -> (currentLevel + 1.0).pow(3) }, + { level -> mapOf(HotmReward.UNKNOWN to 0.5 * level) }), + RAGS_TO_RICHES("Rags to Riches", + 50, + { currentLevel -> floor((currentLevel + 1.0).pow(3.05)) }, + { level -> mapOf(HotmReward.MINING_FORTUNE to 2.0 * level) }), + + KEEN_EYE("Keen Eye", 1, { null }, { emptyMap() }), + MINESHAFT_MAYHEM("Mineshaft Mayhem", 1, { null }, { emptyMap() }), + FROZEN_SOLID("Frozen Solid", 1, { null }, { emptyMap() }), + GEMSTONE_INFUSION("Gemstone Infusion", 1, { null }, { emptyMap() }), + HAZARDOUS_MINER("Hazardous Miner", 1, { null }, { emptyMap() }), + + ; + + private val guiNamePattern by patternGroup.pattern("perk.name.${name.lowercase().replace("_", "")}", "§.$guiName") + + val printName = name.allLettersFirstUppercase() + + val rawLevel: Int + get() = storage?.perks?.get(this.name)?.level ?: 0 + + var activeLevel: Int + get() = storage?.perks?.get(this.name)?.level?.plus(blueEgg()) ?: 0 + private set(value) { + storage?.perks?.computeIfAbsent(this.name) { HotmTree.HotmPerk() }?.level = value + } + + private fun blueEgg() = if (this != PEAK_OF_THE_MOUNTAIN && maxLevel != 1 && HotmAPI.isBlueEggActive) 1 else 0 + + var enabled: Boolean + get() = storage?.perks?.get(this.name)?.enabled ?: false + private set(value) { + storage?.perks?.computeIfAbsent(this.name) { HotmTree.HotmPerk() }?.enabled = value + } + + var isUnlocked: Boolean + get() = storage?.perks?.get(this.name)?.isUnlocked ?: false + private set(value) { + storage?.perks?.computeIfAbsent(this.name) { HotmTree.HotmPerk() }?.isUnlocked = value + } + + var slot: Slot? = null + private set + + fun getLevelUpCost() = costFun(rawLevel) + + fun getReward() = rewardFun(activeLevel) + + companion object { + + val storage get() = ProfileStorageData.profileSpecific?.mining?.hotmTree + + val abilities = + listOf(PICKOBULUS, MINING_SPEED_BOOST, VEIN_SEEKER, MANIAC_MINER, HAZARDOUS_MINER, GEMSTONE_INFUSION) + + private val inventoryPattern by patternGroup.pattern( + "inventory", "Heart of the Mountain" + ) + + private val levelPattern by patternGroup.pattern( + "perk.level", "§(?.)Level (?\\d+).*" + ) + + private val notUnlockedPattern by patternGroup.pattern( + "perk.notunlocked", "(§.)*Requires.*|.*Mountain!|(§.)*Click to unlock!|" + ) + + private val enabledPattern by patternGroup.pattern( + "perk.enable", "§a§lENABLED|(§.)*SELECTED" + ) + private val disabledPattern by patternGroup.pattern( + "perk.disabled", "§c§lDISABLED|§7§eClick to select!" + ) // unused for now since the assumption is when enabled isn't found it is disabled, but the value might be useful in the future or for debugging + + private val resetChatPattern by patternGroup.pattern( + "reset.chat", "§aReset your §r§5Heart of the Mountain§r§a! Your Perks and Abilities have been reset." + ) + + private val heartItemPattern by patternGroup.pattern( + "inventory.heart", "§5Heart of the Mountain" + ) + private val resetItemPattern by patternGroup.pattern( + "inventory.reset", "§cReset Heart of the Mountain" + ) + + private val heartTokensPattern by patternGroup.pattern( + "inventory.heart.token", "§7Token of the Mountain: §5(?\\d+)" + ) + + private val resetTokensPattern by patternGroup.pattern( + "inventory.reset.token", "\\s+§8- §5(?\\d+) Token of the Mountain" + ) + + private val skymallPattern by patternGroup.pattern( + "skymall", "(?:§eNew buff§r§r§r: §r§f|§8 ■ §7)(?.*)" + ) + + private val mayhemChatPattern by patternGroup.pattern( + "mayhem", "§b§lMAYHEM! §r§7(?.*)" + ) + + var inInventory = false + + var tokens: Int + get() = ProfileStorageData.profileSpecific?.mining?.tokens ?: 0 + private set(value) { + ProfileStorageData.profileSpecific?.mining?.tokens = value + } + + var availableTokens: Int + get() = ProfileStorageData.profileSpecific?.mining?.availableTokens ?: 0 + private set(value) { + ProfileStorageData.profileSpecific?.mining?.availableTokens = value + } + + var heartItem: Slot? = null + + init { + entries.forEach { it.guiNamePattern } + HotmAPI.Powder.entries.forEach { + it.heartPattern + it.resetPattern + } + HotmAPI.SkymallPerk.entries.forEach { + it.chatPattern + it.itemPattern + } + HotmAPI.MayhemPerk.entries.forEach { + it.chatPattern + } + } + + private fun resetTree() = entries.forEach { + it.activeLevel = 0 + it.enabled = false + it.isUnlocked = false + HotmAPI.Powder.entries.forEach { it.setCurrent(it.getTotal()) } + availableTokens = tokens + } + + private fun Slot.parse() { + val item = this.stack ?: return + + if (this.handlePowder()) return + + val entry = entries.firstOrNull { it.guiNamePattern.matches(item.name) } ?: return + entry.slot = this + + val lore = item.getLore().takeIf { it.isNotEmpty() } ?: return + + if (entry != PEAK_OF_THE_MOUNTAIN && notUnlockedPattern.matches(lore.last())) { + entry.activeLevel = 0 + entry.enabled = false + entry.isUnlocked = false + return + } + + entry.isUnlocked = true + + entry.activeLevel = levelPattern.matchMatcher(lore.first()) { + group("level").toInt().transformIf({ group("color") == "b" }, { this.minus(1) }) + } ?: entry.maxLevel + + if (entry.activeLevel > entry.maxLevel) { + throw IllegalStateException("Hotm Perk '${entry.name}' over max level") + } + + if (entry == PEAK_OF_THE_MOUNTAIN) { + entry.enabled = entry.activeLevel != 0 + return + } + entry.enabled = lore.any { enabledPattern.matches(it) } + + if (entry == SKY_MALL) handelSkyMall(lore) + } + + private fun Slot.handlePowder(): Boolean { + val item = this.stack ?: return false + + val isHeartItem = when { + heartItemPattern.matches(item.name) -> true + resetItemPattern.matches(item.name) -> false + else -> return false + } + + if (isHeartItem) { // Reset on the heart Item to remove duplication + tokens = 0 + availableTokens = 0 + HotmAPI.Powder.entries.forEach { it.reset() } + heartItem = this + } + + val lore = item.getLore() + + val tokenPattern = if (isHeartItem) heartTokensPattern else resetTokensPattern + + lore@ for (line in lore) { + + HotmAPI.Powder.entries.forEach { + it.pattern(isHeartItem).matchMatcher(line) { + val powder = group("powder").replace(",", "").toLong() + if (isHeartItem) { + it.setCurrent(powder) + } + it.addTotal(powder) + continue@lore + } + } + + tokenPattern.matchMatcher(line) { + val token = group("token").toInt() + if (isHeartItem) { + availableTokens = token + } + tokens += token + continue@lore + } + } + return true + } + + private val skyMallCurrentEffect by patternGroup.pattern( + "skymall.current", "§aYour Current Effect" + ) + + private fun handelSkyMall(lore: List) { + if (!SKY_MALL.enabled || !SKY_MALL.isUnlocked) HotmAPI.skymall = null + else { + val index = (lore.indexOfFirstMatch(skyMallCurrentEffect) ?: run { + ErrorManager.logErrorStateWithData( + "Could not read the skymall effect from the hotm tree", + "skyMallCurrentEffect didn't match", + "lore" to lore + ) + return + }) + 1 + skymallPattern.matchMatcher(lore[index]) { + val perk = group("perk") + HotmAPI.skymall = SkymallPerk.entries.firstOrNull { it.itemPattern.matches(perk) } ?: run { + ErrorManager.logErrorStateWithData( + "Could not read the skymall effect from the hotm tree", + "no itemPattern matched", + "lore" to lore, + "perk" to perk + ) + null + } + } + } + } + + @SubscribeEvent + fun onScoreboardUpdate(event: ScoreboardChangeEvent) { + if (!LorenzUtils.inSkyBlock) return + + event.newList.matchFirst(ScoreboardPattern.powderPattern) { + val type = HotmAPI.Powder.entries.firstOrNull { it.lowName == group("type") } ?: return + val amount = group("amount").formatLong() + val difference = amount - type.getCurrent() + + if (difference > 0) { + type.gain(difference) + ChatUtils.debug("Gained §a${difference.addSeparators()} §e${type.lowName} Powder") + } + } + } + + @SubscribeEvent + fun onInventoryClose(event: InventoryCloseEvent) { + if (!inInventory) return + inInventory = false + entries.forEach { it.slot = null } + heartItem = null + } + + @SubscribeEvent + fun onInventoryFullyOpen(event: InventoryFullyOpenedEvent) { + if (!LorenzUtils.inSkyBlock) return + inInventory = inventoryPattern.matches(event.inventoryName) + DelayedRun.runNextTick { + InventoryUtils.getItemsInOpenChest().forEach { it.parse() } + abilities.filter { it.isUnlocked }.forEach { + it.activeLevel = if (PEAK_OF_THE_MOUNTAIN.rawLevel >= 1) 2 else 1 + } + } + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!LorenzUtils.inSkyBlock) return + if (resetChatPattern.matches(event.message)) { + resetTree() + return + } + skymallPattern.matchMatcher(event.message) { + val perk = group("perk") + HotmAPI.skymall = SkymallPerk.entries.firstOrNull { it.chatPattern.matches(perk) } ?: run { + ErrorManager.logErrorStateWithData( + "Could not read the skymall effect from chat", + "no chatPattern matched", + "chat" to event.message, + "perk" to perk + ) + null + } + ChatUtils.debug("setting skymall to ${HotmAPI.skymall}") + return + } + DelayedRun.runNextTick { + mayhemChatPattern.matchMatcher(event.message) { + val perk = group("perk") + HotmAPI.mineshaftMayhem = MayhemPerk.entries.firstOrNull { it.chatPattern.matches(perk) } ?: run { + ErrorManager.logErrorStateWithData( + "Could not read the mayhem effect from chat", + "no chatPattern matched", + "chat" to event.message, + "perk" to perk + ) + null + } + ChatUtils.debug("setting mineshaftMayhem to ${HotmAPI.mineshaftMayhem}") + } + } + } + + @SubscribeEvent + fun onWorldSwitch(event: IslandChangeEvent) { + if (HotmAPI.mineshaftMayhem == null) return + HotmAPI.mineshaftMayhem = null + ChatUtils.debug("resetting mineshaftMayhem") + } + + @SubscribeEvent + fun onProfileSwitch(event: ProfileJoinEvent) { + HotmAPI.Powder.entries.forEach { + if (it.getStorage() == null) { + ProfileStorageData.profileSpecific?.mining?.powder?.put( + it, ProfileSpecificStorage.MiningConfig.PowderStorage() + ) + } + } + } + + @SubscribeEvent + fun onDebug(event: DebugDataCollectEvent) { + event.title("HotM") + event.addIrrelevant { + add("Tokens : $availableTokens/$tokens") + HotmAPI.Powder.entries.forEach { + add("${it.lowName} Powder: ${it.getCurrent()}/${it.getTotal()}") + } + add("Ability: ${HotmAPI.activeMiningAbility?.printName}") + add("Blue Egg: ${HotmAPI.isBlueEggActive}") + add("SkyMall: ${HotmAPI.skymall}") + add("Mineshaft Mayhem: ${HotmAPI.mineshaftMayhem}") + } + event.title("HotM - Tree") + event.addIrrelevant(entries.filter { it.isUnlocked }.map { + "${if (it.enabled) "✔" else "✖"} ${it.printName}: ${it.activeLevel}" + }) + } + } +} + +private val patternGroup = RepoPattern.group("mining.hotm") + +enum class HotmReward { + MINING_SPEED, + MINING_FORTUNE, + MINING_WISDOM, + FORGE_TIME_DECREASE, + TITANIUM_CHANCE, + DAILY_POWDER, + MORE_BASE_MITHRIL_POWER, + MORE_BASE_GEMSTONE_POWER, + MORE_MITHRIL_POWER, + MORE_GEMSTONE_POWER, + COMBAT_STAT_BOOST, + CHANCE_OF_TREASURE_CHEST, + LOCKS_OF_TREASURE_CHEST, + EXTRA_CHANCE_TRIGGER_RARE_OCCURRENCES, + AVERAGE_BLOCK_BREAKS, + CHANCE_EXTRA_XP_ORBS, + MINING_SPEED_BOOST, + ABILITY_DURATION, + ABILITY_RADIUS, + ABILITY_COOLDOWN, + FOSSIL_DUST, + UNKNOWN, + COLD_RESISTANCE +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/local/HotmTree.kt b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/local/HotmTree.kt new file mode 100644 index 000000000000..b9fba9ca0f83 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/local/HotmTree.kt @@ -0,0 +1,29 @@ +package at.hannibal2.skyhanni.data.jsonobjects.local; + +import at.hannibal2.skyhanni.utils.fromJson +import com.google.gson.Gson +import com.google.gson.annotations.Expose + +class HotmTree { + + @Expose + val perks = mutableMapOf(); + + fun deepCopy(): HotmTree { + val gson = Gson(); + val json = gson.toJson(this); + return gson.fromJson(json) + } + + class HotmPerk { + + @Expose + var level: Int = 0 + + @Expose + var enabled: Boolean = false + + @Expose + var isUnlocked: Boolean = false + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/events/DebugDataCollectEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/DebugDataCollectEvent.kt index 0162e72e4b2d..3b8a884a9e8f 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/DebugDataCollectEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/DebugDataCollectEvent.kt @@ -18,7 +18,7 @@ class DebugDataCollectEvent(private val list: MutableList, private val s fun addIrrelevant(text: String) = addIrrelevant(listOf(text)) - private fun addIrrelevant(text: List) { + fun addIrrelevant(text: List) { irrelevant = true addData(text) } diff --git a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardPattern.kt b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardPattern.kt index ec4cf0c6f4f3..2db0c37f7be2 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardPattern.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardPattern.kt @@ -173,9 +173,18 @@ object ScoreboardPattern { // mining private val miningSb = scoreboardGroup.group("mining") + + /** + * REGEX-TEST: §2᠅ §fMithril§f: §235,448 + * REGEX-TEST: §d᠅ §fGemstone§f: §d36,758 + * REGEX-TEST: §b᠅ §fGlacite§f: §b29,537 + * REGEX-TEST: §2᠅ §fMithril Powder§f: §235,448 + * REGEX-TEST: §d᠅ §fGemstone Powder§f: §d36,758 + * REGEX-TEST: §b᠅ §fGlacite Powder§f: §b29,537 + */ val powderPattern by miningSb.pattern( "powder", - "(§.)*᠅ §.(Gemstone|Mithril|Glacite)( Powder)?(§.)*:?.*$" + "(?:§.)*᠅ (?:§.)(?Gemstone|Mithril|Glacite)(?: Powder)?(?:§.)*:? (?:§.)*(?[\\d,.]*)" ) val windCompassPattern by miningSb.pattern( "windcompass", diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/HotmFeatures.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/HotmFeatures.kt new file mode 100644 index 000000000000..c594312a32d3 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/HotmFeatures.kt @@ -0,0 +1,53 @@ +package at.hannibal2.skyhanni.features.mining + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.HotmData +import at.hannibal2.skyhanni.events.GuiContainerEvent +import at.hannibal2.skyhanni.events.RenderItemTipEvent +import at.hannibal2.skyhanni.utils.LorenzColor +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RenderUtils.highlight +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class HotmFeatures { + + val config get() = SkyHanniMod.feature.mining.hotm + + fun isEnabled() = LorenzUtils.inSkyBlock && HotmData.inInventory + + @SubscribeEvent + fun onRender(event: GuiContainerEvent.BackgroundDrawnEvent) { + if (!isEnabled()) return + if (!config.highlightEnabledPerks) return + HotmData.entries.forEach { entry -> + val color = if (!entry.isUnlocked) LorenzColor.DARK_GRAY + else if (entry.enabled) LorenzColor.GREEN else LorenzColor.RED + entry.slot?.highlight(color) + } + } + + @SubscribeEvent + fun onRenderTip(event: RenderItemTipEvent) { + if (!isEnabled()) return + handleLevelStackSize(event) + handleTokenStackSize(event) + } + + private fun handleLevelStackSize(event: RenderItemTipEvent) { + if (!config.levelStackSize) return + HotmData.entries.firstOrNull() { + event.stack == it.slot?.stack + }?.let { + event.stackTip = if (it.activeLevel == 0 || it.activeLevel == it.maxLevel) "" else + "§e${it.activeLevel}" + it.activeLevel.toString() + } + } + + private fun handleTokenStackSize(event: RenderItemTipEvent) { + if (!config.tokenStackSize) return + if (event.stack != HotmData.heartItem?.stack) return + event.stackTip = HotmData.availableTokens.takeIf { it != 0 }?.let { "§b$it" } ?: "" + } + +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt index 8309a8f86466..d1a9922cbcad 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzColor.kt @@ -25,6 +25,17 @@ enum class LorenzColor(val chatColorCode: Char, private val color: Color, privat CHROMA('Z', Color(0, 0, 0, 0), "§ZChroma") // If chroma, go transparent instead of color code. ; + val next by lazy { + when (this) { + WHITE -> BLACK + CHROMA -> BLACK + else -> { + val index = entries.indexOf(this) + entries[index + 1] + } + } + } + fun getChatColor(): String = "§$chatColorCode" fun toColor(): Color = color diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RegexUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RegexUtils.kt index f12ecdef6f27..927b08d54cb7 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/RegexUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/RegexUtils.kt @@ -49,4 +49,11 @@ object RegexUtils { fun Matcher.groupOrNull(groupName: String): String? = runCatching { this.group(groupName) }.getOrNull() fun Matcher.hasGroup(groupName: String): Boolean = groupOrNull(groupName) != null + + fun List.indexOfFirstMatch(pattern: Pattern): Int? { + for ((index, line) in this.withIndex()) { + pattern.matcher(line).let { if (it.matches()) return index } + } + return null + } }