diff --git a/src/main/java/at/hannibal2/skyhanni/api/EffectAPI.kt b/src/main/java/at/hannibal2/skyhanni/api/EffectAPI.kt new file mode 100644 index 000000000000..9b2789552e9a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/api/EffectAPI.kt @@ -0,0 +1,288 @@ +package at.hannibal2.skyhanni.api + +import at.hannibal2.skyhanni.api.event.HandleEvent +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.InventoryUpdatedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.TablistFooterUpdateEvent +import at.hannibal2.skyhanni.events.effects.EffectDurationChangeEvent +import at.hannibal2.skyhanni.events.effects.EffectDurationChangeType +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.ChatUtils +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.RegexUtils.firstMatcher +import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher +import at.hannibal2.skyhanni.utils.RegexUtils.matches +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.TimeUtils +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes + +@SkyHanniModule +object EffectAPI { + + // + /** + * REGEX-TEST: §cGod Potion§f: 4d + */ + private val godPotTabPattern by RepoPattern.pattern( + "stats.tabpatterns.godpot", + "(?:§.)*God Potion(?:§.)*: (?:§.)*(? + + private val profileStorage get() = ProfileStorageData.profileSpecific + + // TODO move the whole list into the repo + enum class NonGodPotEffect( + val tabListName: String, + val isMixin: Boolean = false, + val inventoryItemName: String = tabListName, + ) { + + SMOLDERING("§aSmoldering Polarization I"), + GLOWY("§2Mushed Glowy Tonic I"), + WISP("§bWisp's Ice-Flavored Water I"), + GOBLIN("§2King's Scent I"), + + INVISIBILITY("§8Invisibility I"), // when wearing sorrow armor + + REV("§cZombie Brain Mixin", true), + TARA("§6Spider Egg Mixin", true), + SVEN("§bWolf Fur Mixin", true), + VOID("§6Ender Portal Fumes", true), + BLAZE("§fGabagoey", true), + GLOWING_MUSH("§2Glowing Mush Mixin", true), + HOT_CHOCOLATE("§6Hot Chocolate Mixin", true), + + DEEP_TERROR("§4Deepterror", true), + + GREAT_SPOOK("§fGreat Spook I", inventoryItemName = "§fGreat Spook Potion"), + + HARVEST_HARBINGER("§6Harvest Harbinger V"), + + PEST_REPELLENT("§6Pest Repellent I§r"), + PEST_REPELLENT_MAX("§6Pest Repellent II"), + + CURSE_OF_GREED("§4Curse of Greed I"), + + COLD_RESISTANCE_4("§bCold Resistance IV"), + + POWDER_PUMPKIN("§fPowder Pumpkin I"), + FILET_O_FORTUNE("§fFilet O' Fortune I"), + CHILLED_PRISTINE_POTATO("§fChilled Pristine Potato I"), + } + + // Todo : cleanup and add support for poison candy I, and add support for splash / other formats + @SubscribeEvent + @Suppress("MaxLineLength") + fun onChat(event: LorenzChatEvent) { + if (!LorenzUtils.inSkyBlock) return + hotChocolateMixinConsumePattern.matchMatcher(event.message) { + val durationAdded = TimeUtils.getDuration(group("time")) + EffectDurationChangeEvent( + NonGodPotEffect.HOT_CHOCOLATE, + EffectDurationChangeType.ADD, + durationAdded, + ).post() + } + godPotConsumePattern.matchMatcher(event.message) { + val durationAdded = TimeUtils.getDuration(group("time")) + val existingValue = profileStorage?.godPotExpiryTime?.takeIfInitialized() ?: SimpleTimeMark.now() + profileStorage?.godPotExpiryTime = existingValue + durationAdded + } + + var effect: NonGodPotEffect? = null + var changeType: EffectDurationChangeType? = null + var duration: Duration? = null + + if (event.message == "§aYou ate a §r§aRe-heated Gummy Polar Bear§r§a!") { + effect = NonGodPotEffect.SMOLDERING + changeType = EffectDurationChangeType.SET + duration = 1.hours + } + + if (event.message == "§a§lBUFF! §fYou have gained §r§2Mushed Glowy Tonic I§r§f! Press TAB or type /effects to view your active effects!") { + effect = NonGodPotEffect.GLOWY + changeType = EffectDurationChangeType.SET + duration = 1.hours + } + + if (event.message == "§a§lBUFF! §fYou splashed yourself with §r§bWisp's Ice-Flavored Water I§r§f! Press TAB or type /effects to view your active effects!") { + effect = NonGodPotEffect.WISP + changeType = EffectDurationChangeType.SET + duration = 5.minutes + } + + if (event.message == "§eYou consumed a §r§fGreat Spook Potion§r§e!") { + effect = NonGodPotEffect.GREAT_SPOOK + changeType = EffectDurationChangeType.SET + duration = 24.hours + } + + if (event.message == "§a§lBUFF! §fYou have gained §r§6Harvest Harbinger V§r§f! Press TAB or type /effects to view your active effects!") { + effect = NonGodPotEffect.HARVEST_HARBINGER + changeType = EffectDurationChangeType.SET + duration = 25.minutes + } + + if (event.message == "§a§lYUM! §r§6Pests §r§7will now spawn §r§a2x §r§7less while you break crops for the next §r§a60m§r§7!") { + effect = NonGodPotEffect.PEST_REPELLENT + changeType = EffectDurationChangeType.SET + duration = 1.hours + } + + if (event.message == "§a§lYUM! §r§6Pests §r§7will now spawn §r§a4x §r§7less while you break crops for the next §r§a60m§r§7!") { + effect = NonGodPotEffect.PEST_REPELLENT_MAX + changeType = EffectDurationChangeType.SET + duration = 1.hours + } + + if (event.message == "§e[NPC] §6King Yolkar§f: §rThese eggs will help me stomach my pain.") { + effect = NonGodPotEffect.CURSE_OF_GREED + changeType = EffectDurationChangeType.SET + duration = 20.minutes + } + + if (event.message == "§cThe Goblin King's §r§afoul stench §r§chas dissipated!") { + effect = NonGodPotEffect.GOBLIN + changeType = EffectDurationChangeType.REMOVE + } + + effect?.let { + if (changeType == null) return@let + EffectDurationChangeEvent(it, changeType, duration).post() + } + } + + @HandleEvent + fun onTabUpdate(event: TablistFooterUpdateEvent) { + if (!LorenzUtils.inSkyBlock) return + for (line in event.footer.split("\n")) { + godPotTabPattern.matchMatcher(line) { + profileStorage?.godPotExpiryTime = SimpleTimeMark.now() + TimeUtils.getDuration(group("time")) + } + for (effect in NonGodPotEffect.entries) { + val tabListName = effect.tabListName + if ("$line§r".startsWith(tabListName)) { + val string = line.substring(tabListName.length) + try { + val duration = TimeUtils.getDuration(string.split("§f")[1]) + EffectDurationChangeEvent(effect, EffectDurationChangeType.SET, duration).post() + } catch (e: IndexOutOfBoundsException) { + ChatUtils.debug("Error while reading non god pot effects from tab list! line: '$line'") + } + } + } + } + } + + @SubscribeEvent + fun onInventoryUpdated(event: InventoryUpdatedEvent) { + if (!LorenzUtils.inSkyBlock || !event.isGodPotEffectsFilterSelect()) return + + val potionLore = event.inventoryItems[10]?.getLore() ?: run { + // No active god pot effects found, reset the expiry time + profileStorage?.godPotExpiryTime = SimpleTimeMark.farPast() + return + } + + val expiryDuration = potionRemainingLoreTimerPattern.firstMatcher(potionLore) { + TimeUtils.getDuration(group("time")) + } ?: return + + profileStorage?.godPotExpiryTime = SimpleTimeMark.now() + expiryDuration + } + + private fun InventoryUpdatedEvent.isGodPotEffectsFilterSelect(): Boolean = + effectsInventoryPattern.matches(this.inventoryName) && + this.inventoryItems.values.firstOrNull { + filterPattern.matches(it.displayName) + }?.getLore()?.any { + godPotEffectsFilterSelectPattern.matches(it) + } ?: false + + @SubscribeEvent + fun onInventoryOpen(event: InventoryFullyOpenedEvent) { + if (!LorenzUtils.inSkyBlock) return + if (!event.inventoryName.endsWith("Active Effects")) return + + for (stack in event.inventoryItems.values) { + val name = stack.name + for (effect in NonGodPotEffect.entries) { + if (!name.contains(effect.inventoryItemName)) continue + for (line in stack.getLore()) { + if (!line.contains("Remaining") || line == "§7Time Remaining: §aCompleted!" || line.contains("Remaining Uses")) continue + val duration = try { + TimeUtils.getDuration(line.split("§f")[1]) + } catch (e: IndexOutOfBoundsException) { + ErrorManager.logErrorWithData( + e, "Error while reading Non God-Potion effects from tab list", + "line" to line, + ) + continue + } + EffectDurationChangeEvent(effect, EffectDurationChangeType.SET, duration).post() + } + } + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/chocolatefactory/ChocolateFactoryConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/chocolatefactory/ChocolateFactoryConfig.java index a6c6847671ec..08613a859b04 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/chocolatefactory/ChocolateFactoryConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/chocolatefactory/ChocolateFactoryConfig.java @@ -201,6 +201,12 @@ public class ChocolateFactoryConfig { @FeatureToggle public boolean boosterCookieRequirement = false; + @Expose + @ConfigOption(name = "Hot Chocolate Mixin", desc = "Blocks running /cf without §9Hot Chocolate Mixin §7active.") + @ConfigEditorBoolean + @FeatureToggle + public boolean hotChocolateMixinRequirement = false; + @Expose @ConfigOption(name = "Stray Tracker", desc = "Track stray rabbits found in the Chocolate Factory menu.") @ConfigEditorBoolean 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 44e4fd6dfd65..1e502bce0fd9 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java +++ b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java @@ -239,6 +239,9 @@ public static class HitmanStatsStorage { @Expose public HitmanStatsStorage hitmanStats = new HitmanStatsStorage(); + + @Expose + public SimpleTimeMark hotChocolateMixinExpiry = SimpleTimeMarkFarPast(); } @Expose @@ -307,6 +310,9 @@ public static class BitsStorage { public SimpleTimeMark boosterCookieExpiryTime = null; } + @Expose + public SimpleTimeMark godPotExpiryTime = SimpleTimeMarkFarPast(); + @Expose public Map minions = new HashMap<>(); diff --git a/src/main/java/at/hannibal2/skyhanni/events/effects/EffectDurationChangeEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/effects/EffectDurationChangeEvent.kt new file mode 100644 index 000000000000..242087114224 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/effects/EffectDurationChangeEvent.kt @@ -0,0 +1,17 @@ +package at.hannibal2.skyhanni.events.effects + +import at.hannibal2.skyhanni.api.EffectAPI +import at.hannibal2.skyhanni.api.event.SkyHanniEvent +import kotlin.time.Duration + +class EffectDurationChangeEvent( + val effect: EffectAPI.NonGodPotEffect, + val durationChangeType: EffectDurationChangeType, + val duration: Duration? = null +) : SkyHanniEvent() + +enum class EffectDurationChangeType { + ADD, + REMOVE, + SET +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBlockOpen.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBlockOpen.kt index 4592d5262331..7061fb611aeb 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBlockOpen.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBlockOpen.kt @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.features.inventory.chocolatefactory import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.api.EffectAPI.NonGodPotEffect import at.hannibal2.skyhanni.api.event.HandleEvent import at.hannibal2.skyhanni.data.EntityMovementData import at.hannibal2.skyhanni.data.IslandGraphs @@ -8,6 +9,8 @@ import at.hannibal2.skyhanni.data.IslandType import at.hannibal2.skyhanni.data.ProfileStorageData import at.hannibal2.skyhanni.events.GuiContainerEvent import at.hannibal2.skyhanni.events.MessageSendToServerEvent +import at.hannibal2.skyhanni.events.effects.EffectDurationChangeEvent +import at.hannibal2.skyhanni.events.effects.EffectDurationChangeType import at.hannibal2.skyhanni.features.event.hoppity.MythicRabbitPetWarning import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils @@ -23,8 +26,9 @@ import kotlin.time.Duration.Companion.seconds @SkyHanniModule object ChocolateFactoryBlockOpen { private val config get() = SkyHanniMod.feature.inventory.chocolateFactory - private val profileStorage get() = ProfileStorageData.profileSpecific?.bits + private val profileStorage get() = ProfileStorageData.profileSpecific + // /** * REGEX-TEST: /cf * REGEX-TEST: /cf test @@ -45,9 +49,22 @@ object ChocolateFactoryBlockOpen { "inventory.chocolatefactory.openitem", "§6(?:Open )?Chocolate Factory", ) + // private var commandSentTimer = SimpleTimeMark.farPast() + @HandleEvent + fun onEffectUpdate(event: EffectDurationChangeEvent) { + if (event.effect != NonGodPotEffect.HOT_CHOCOLATE || event.duration == null) return + val chocolateFactory = profileStorage?.chocolateFactory ?: return + + chocolateFactory.hotChocolateMixinExpiry = when (event.durationChangeType) { + EffectDurationChangeType.ADD -> chocolateFactory.hotChocolateMixinExpiry + event.duration + EffectDurationChangeType.REMOVE -> SimpleTimeMark.farPast() + EffectDurationChangeType.SET -> SimpleTimeMark.now() + event.duration + } + } + @SubscribeEvent fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) { if (!LorenzUtils.inSkyBlock) return @@ -76,19 +93,20 @@ object ChocolateFactoryBlockOpen { SUCCESS, FAIL_NO_RABBIT, FAIL_NO_BOOSTER_COOKIE, + FAIL_NO_MIXIN, } private fun tryBlock(): TryBlockResult { - if (config.mythicRabbitRequirement && !MythicRabbitPetWarning.correctPet()) { + return if (config.mythicRabbitRequirement && !MythicRabbitPetWarning.correctPet()) { ChatUtils.clickToActionOrDisable( "§cBlocked opening the Chocolate Factory without a §dMythic Rabbit Pet §cequipped!", config::mythicRabbitRequirement, actionName = "open pets menu", action = { HypixelCommands.pet() }, ) - return TryBlockResult.FAIL_NO_RABBIT + TryBlockResult.FAIL_NO_RABBIT } else if (config.boosterCookieRequirement) { - profileStorage?.boosterCookieExpiryTime?.let { + profileStorage?.bits?.boosterCookieExpiryTime?.let { if (it.timeUntil() > 0.seconds) return TryBlockResult.SUCCESS ChatUtils.clickToActionOrDisable( "§cBlocked opening the Chocolate Factory without a §dBooster Cookie §cactive!", @@ -101,9 +119,30 @@ object ChocolateFactoryBlockOpen { } }, ) - return TryBlockResult.FAIL_NO_BOOSTER_COOKIE - } - } - return TryBlockResult.SUCCESS + TryBlockResult.FAIL_NO_BOOSTER_COOKIE + } ?: TryBlockResult.SUCCESS + } else if (config.hotChocolateMixinRequirement) { + val mixinExpiryTime = profileStorage?.chocolateFactory?.hotChocolateMixinExpiry ?: SimpleTimeMark.farPast() + val godPotExpiryTime = profileStorage?.godPotExpiryTime ?: SimpleTimeMark.farPast() + if (mixinExpiryTime.isInPast()) { + ChatUtils.clickToActionOrDisable( + "§cBlocked opening the Chocolate Factory without a §dHot Chocolate Mix §cactive! " + + "§7You may need to open §c/effects §7to refresh mixin status.", + config::hotChocolateMixinRequirement, + actionName = "search AH for mixin", + action = { HypixelCommands.auctionSearch("hot chocolate mixin") }, + ) + TryBlockResult.FAIL_NO_MIXIN + } else if (godPotExpiryTime.isInPast()) { + ChatUtils.clickToActionOrDisable( + "§cBlocked opening the Chocolate Factory without a §dGod Potion §cactive! " + + "§7You may need to open §c/effects §7and cycle the §aFilter §7to §bGod Potion Effects §7to refresh potion status.", + config::hotChocolateMixinRequirement, + actionName = "search AH for god potion", + action = { HypixelCommands.auctionSearch("god potion") }, + ) + TryBlockResult.FAIL_NO_MIXIN + } else TryBlockResult.SUCCESS + } else TryBlockResult.SUCCESS } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/NonGodPotEffectDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/NonGodPotEffectDisplay.kt deleted file mode 100644 index 5279bbc356d4..000000000000 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/NonGodPotEffectDisplay.kt +++ /dev/null @@ -1,293 +0,0 @@ -package at.hannibal2.skyhanni.features.misc - -import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.api.event.HandleEvent -import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator -import at.hannibal2.skyhanni.data.ProfileStorageData -import at.hannibal2.skyhanni.events.GuiRenderEvent -import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent -import at.hannibal2.skyhanni.events.LorenzChatEvent -import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent -import at.hannibal2.skyhanni.events.ProfileJoinEvent -import at.hannibal2.skyhanni.events.SecondPassedEvent -import at.hannibal2.skyhanni.events.minecraft.packet.PacketReceivedEvent -import at.hannibal2.skyhanni.features.dungeon.DungeonAPI -import at.hannibal2.skyhanni.features.rift.RiftAPI -import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule -import at.hannibal2.skyhanni.test.command.ErrorManager -import at.hannibal2.skyhanni.utils.ChatUtils -import at.hannibal2.skyhanni.utils.CollectionUtils.sorted -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.RegexUtils.matchMatcher -import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings -import at.hannibal2.skyhanni.utils.SoundUtils.playPlingSound -import at.hannibal2.skyhanni.utils.TimeUnit -import at.hannibal2.skyhanni.utils.TimeUtils -import at.hannibal2.skyhanni.utils.TimeUtils.format -import at.hannibal2.skyhanni.utils.TimeUtils.timerColor -import at.hannibal2.skyhanni.utils.Timer -import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern -import net.minecraft.network.play.server.S47PacketPlayerListHeaderFooter -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import kotlin.time.Duration.Companion.hours -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -@SkyHanniModule -object NonGodPotEffectDisplay { - - private val config get() = SkyHanniMod.feature.misc.potionEffect - private var checkFooter = false - private val effectDuration = mutableMapOf() - private var display = emptyList() - - // TODO move the whole list into the repo - enum class NonGodPotEffect( - val tabListName: String, - val isMixin: Boolean = false, - val inventoryItemName: String = tabListName, - ) { - - SMOLDERING("§aSmoldering Polarization I"), - GLOWY("§2Mushed Glowy Tonic I"), - WISP("§bWisp's Ice-Flavored Water I"), - GOBLIN("§2King's Scent I"), - - INVISIBILITY("§8Invisibility I"), // when wearing sorrow armor - - REV("§cZombie Brain Mixin", true), - TARA("§6Spider Egg Mixin", true), - SVEN("§bWolf Fur Mixin", true), - VOID("§6Ender Portal Fumes", true), - BLAZE("§fGabagoey", true), - GLOWING_MUSH("§2Glowing Mush Mixin", true), - HOT_CHOCOLATE("§6Hot Chocolate Mixin", true), - - DEEP_TERROR("§4Deepterror", true), - - GREAT_SPOOK("§fGreat Spook I", inventoryItemName = "§fGreat Spook Potion"), - - HARVEST_HARBINGER("§6Harvest Harbinger V"), - - PEST_REPELLENT("§6Pest Repellent I§r"), - PEST_REPELLENT_MAX("§6Pest Repellent II"), - - CURSE_OF_GREED("§4Curse of Greed I"), - - COLD_RESISTANCE_4("§bCold Resistance IV"), - - POWDER_PUMPKIN("§fPowder Pumpkin I"), - FILET_O_FORTUNE("§fFilet O' Fortune I"), - CHILLED_PRISTINE_POTATO("§fChilled Pristine Potato I"), - } - - private val effectsCountPattern by RepoPattern.pattern( - "misc.nongodpot.effects", - "§7You have §e(?\\d+) §7non-god effects\\.", - ) - private var totalEffectsCount = 0 - - @HandleEvent - fun onProfileJoin(event: ProfileJoinEvent) { - effectDuration.clear() - display = emptyList() - } - - // todo : cleanup and add support for poison candy I, and add support for splash / other formats - @SubscribeEvent - @Suppress("MaxLineLength") - fun onChat(event: LorenzChatEvent) { - if (event.message == "§aYou cleared all of your active effects!") { - effectDuration.clear() - update() - } - - if (event.message == "§aYou ate a §r§aRe-heated Gummy Polar Bear§r§a!") { - effectDuration[NonGodPotEffect.SMOLDERING] = Timer(1.hours) - update() - } - - if (event.message == "§a§lBUFF! §fYou have gained §r§2Mushed Glowy Tonic I§r§f! Press TAB or type /effects to view your active effects!") { - effectDuration[NonGodPotEffect.GLOWY] = Timer(1.hours) - update() - } - - if (event.message == "§a§lBUFF! §fYou splashed yourself with §r§bWisp's Ice-Flavored Water I§r§f! Press TAB or type /effects to view your active effects!") { - effectDuration[NonGodPotEffect.WISP] = Timer(5.minutes) - update() - } - - if (event.message == "§eYou consumed a §r§fGreat Spook Potion§r§e!") { - effectDuration[NonGodPotEffect.GREAT_SPOOK] = Timer(24.hours) - update() - } - - if (event.message == "§a§lBUFF! §fYou have gained §r§6Harvest Harbinger V§r§f! Press TAB or type /effects to view your active effects!") { - effectDuration[NonGodPotEffect.HARVEST_HARBINGER] = Timer(25.minutes) - update() - } - - if (event.message == "§a§lYUM! §r§6Pests §r§7will now spawn §r§a2x §r§7less while you break crops for the next §r§a60m§r§7!") { - effectDuration[NonGodPotEffect.PEST_REPELLENT] = Timer(1.hours) - } - - if (event.message == "§a§lYUM! §r§6Pests §r§7will now spawn §r§a4x §r§7less while you break crops for the next §r§a60m§r§7!") { - effectDuration[NonGodPotEffect.PEST_REPELLENT_MAX] = Timer(1.hours) - } - - if (event.message == "§e[NPC] §6King Yolkar§f: §rThese eggs will help me stomach my pain.") { - effectDuration[NonGodPotEffect.GOBLIN] = Timer(20.minutes) - update() - } - - if (event.message == "§cThe Goblin King's §r§afoul stench §r§chas dissipated!") { - effectDuration.remove(NonGodPotEffect.GOBLIN) - update() - } - } - - private fun update() { - if (effectDuration.values.removeIf { it.ended }) { - // to fetch the real amount of active pots - totalEffectsCount = 0 - checkFooter = true - } - - display = drawDisplay() - } - - private fun drawDisplay(): MutableList { - val newDisplay = mutableListOf() - for ((effect, time) in effectDuration.sorted()) { - if (time.ended) continue - if (effect == NonGodPotEffect.INVISIBILITY) continue - - if (effect.isMixin && !config.nonGodPotEffectShowMixins) continue - - val remaining = time.remaining.coerceAtLeast(0.seconds) - val format = remaining.format(TimeUnit.HOUR) - val color = remaining.timerColor() - - val displayName = effect.tabListName - newDisplay.add("$displayName $color$format") - } - val diff = totalEffectsCount - effectDuration.size - if (diff > 0) { - newDisplay.add("§eOpen the /effects inventory") - newDisplay.add("§eto show the missing $diff effects!") - checkFooter = true - } - return newDisplay - } - - @SubscribeEvent - fun onSecondPassed(event: SecondPassedEvent) { - if (!isEnabled()) return - if (!ProfileStorageData.loaded) return - - if (config.nonGodPotEffectDisplay) update() - - val effectWarning = config.expireWarning - val effectSound = config.expireSound - - if (!effectWarning && !effectSound) return - - effectDuration.sorted().forEach { (effect, time) -> - if (time.remaining.inWholeSeconds != config.expireWarnTime.toLong()) return - - if (effectWarning) LorenzUtils.sendTitle(effect.tabListName, 3.seconds) - if (effectSound) repeat(5) { playPlingSound() } - } - } - - @SubscribeEvent - fun onWorldChange(event: LorenzWorldChangeEvent) { - checkFooter = true - } - - @SubscribeEvent - fun onInventoryOpen(event: InventoryFullyOpenedEvent) { - if (!LorenzUtils.inSkyBlock) return - if (!event.inventoryName.endsWith("Active Effects")) return - - for (stack in event.inventoryItems.values) { - val name = stack.name - for (effect in NonGodPotEffect.entries) { - if (!name.contains(effect.inventoryItemName)) continue - for (line in stack.getLore()) { - if (!line.contains("Remaining") || line == "§7Time Remaining: §aCompleted!" || line.contains("Remaining Uses")) continue - val duration = try { - TimeUtils.getDuration(line.split("§f")[1]) - } catch (e: IndexOutOfBoundsException) { - ErrorManager.logErrorWithData( - e, "Error while reading Non God-Potion effects from tab list", - "line" to line, - ) - continue - } - effectDuration[effect] = Timer(duration) - update() - } - } - } - } - - // TODO use TablistFooterUpdateEvent instead - @HandleEvent(onlyOnSkyblock = true, priority = HandleEvent.LOW, receiveCancelled = true) - fun onPacketReceive(event: PacketReceivedEvent) { - val packet = event.packet - if (!checkFooter) return - if (packet is S47PacketPlayerListHeaderFooter) { - val formattedText = packet.footer.formattedText - val lines = formattedText.replace("§r", "").split("\n") - - if (!lines.any { it.contains("§a§lActive Effects") }) return - checkFooter = false - - var effectsCount = 0 - for (line in lines) { - for (effect in NonGodPotEffect.entries) { - val tabListName = effect.tabListName - if ("$line§r".startsWith(tabListName)) { - val string = line.substring(tabListName.length) - try { - val duration = TimeUtils.getDuration(string.split("§f")[1]) - effectDuration[effect] = Timer(duration) - update() - } catch (e: IndexOutOfBoundsException) { - ChatUtils.debug("Error while reading non god pot effects from tab list! line: '$line'") - } - } - } - effectsCountPattern.matchMatcher(line) { - val group = group("name") - effectsCount = group.toInt() - } - } - totalEffectsCount = effectsCount - } - } - - @SubscribeEvent - fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { - if (!isEnabled() || !config.nonGodPotEffectDisplay) return - if (RiftAPI.inRift()) return - - config.nonGodPotEffectPos.renderStrings( - display, - extraSpace = 3, - posLabel = "Non God Pot Effects", - ) - } - - @HandleEvent - fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { - event.move(3, "misc.nonGodPotEffectDisplay", "misc.potionEffect.nonGodPotEffectDisplay") - event.move(3, "misc.nonGodPotEffectShowMixins", "misc.potionEffect.nonGodPotEffectShowMixins") - event.move(3, "misc.nonGodPotEffectPos", "misc.potionEffect.nonGodPotEffectPos") - } - - private fun isEnabled() = LorenzUtils.inSkyBlock && !DungeonAPI.inDungeon() && !LorenzUtils.inKuudraFight -} diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/effects/NonGodPotEffectDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/effects/NonGodPotEffectDisplay.kt new file mode 100644 index 000000000000..e1877f63c84c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/effects/NonGodPotEffectDisplay.kt @@ -0,0 +1,180 @@ +package at.hannibal2.skyhanni.features.misc.effects + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.api.EffectAPI.NonGodPotEffect +import at.hannibal2.skyhanni.api.event.HandleEvent +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator +import at.hannibal2.skyhanni.data.ProfileStorageData +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent +import at.hannibal2.skyhanni.events.ProfileJoinEvent +import at.hannibal2.skyhanni.events.SecondPassedEvent +import at.hannibal2.skyhanni.events.TablistFooterUpdateEvent +import at.hannibal2.skyhanni.events.effects.EffectDurationChangeEvent +import at.hannibal2.skyhanni.events.effects.EffectDurationChangeType +import at.hannibal2.skyhanni.features.dungeon.DungeonAPI +import at.hannibal2.skyhanni.features.rift.RiftAPI +import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule +import at.hannibal2.skyhanni.utils.CollectionUtils.sorted +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher +import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings +import at.hannibal2.skyhanni.utils.SoundUtils.playPlingSound +import at.hannibal2.skyhanni.utils.TimeUnit +import at.hannibal2.skyhanni.utils.TimeUtils.format +import at.hannibal2.skyhanni.utils.TimeUtils.timerColor +import at.hannibal2.skyhanni.utils.Timer +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +@SkyHanniModule +object NonGodPotEffectDisplay { + + private val config get() = SkyHanniMod.feature.misc.potionEffect + private var checkFooter = false + private val effectDuration = mutableMapOf() + private var display = emptyList() + + private val effectsCountPattern by RepoPattern.pattern( + "misc.nongodpot.effects", + "§7You have §e(?\\d+) §7non-god effects\\.", + ) + private var totalEffectsCount = 0 + + @HandleEvent + fun onProfileJoin(event: ProfileJoinEvent) { + effectDuration.clear() + display = emptyList() + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (event.message == "§aYou cleared all of your active effects!") { + effectDuration.clear() + update() + } + } + + @HandleEvent + fun onEffectUpdate(event: EffectDurationChangeEvent) { + when (event.durationChangeType) { + EffectDurationChangeType.ADD -> { + event.duration?.let { + val existing = effectDuration[event.effect]?.duration ?: Duration.ZERO + effectDuration[event.effect] = Timer(existing + it) + } + } + + EffectDurationChangeType.SET -> { + event.duration?.let { + effectDuration[event.effect] = Timer(it) + } + } + + EffectDurationChangeType.REMOVE -> { + effectDuration.remove(event.effect) + } + } + update() + } + + private fun update() { + if (effectDuration.values.removeIf { it.ended }) { + // to fetch the real amount of active pots + totalEffectsCount = 0 + checkFooter = true + } + + display = drawDisplay() + } + + private fun drawDisplay(): MutableList { + val newDisplay = mutableListOf() + for ((effect, time) in effectDuration.sorted()) { + if (time.ended) continue + if (effect == NonGodPotEffect.INVISIBILITY) continue + + if (effect.isMixin && !config.nonGodPotEffectShowMixins) continue + + val remaining = time.remaining.coerceAtLeast(0.seconds) + val format = remaining.format(TimeUnit.HOUR) + val color = remaining.timerColor() + + val displayName = effect.tabListName + newDisplay.add("$displayName $color$format") + } + val diff = totalEffectsCount - effectDuration.size + if (diff > 0) { + newDisplay.add("§eOpen the /effects inventory") + newDisplay.add("§eto show the missing $diff effects!") + checkFooter = true + } + return newDisplay + } + + @SubscribeEvent + fun onSecondPassed(event: SecondPassedEvent) { + if (!isEnabled()) return + if (!ProfileStorageData.loaded) return + + if (config.nonGodPotEffectDisplay) update() + + val effectWarning = config.expireWarning + val effectSound = config.expireSound + + if (!effectWarning && !effectSound) return + + effectDuration.sorted().forEach { (effect, time) -> + if (time.remaining.inWholeSeconds != config.expireWarnTime.toLong()) return + + if (effectWarning) LorenzUtils.sendTitle(effect.tabListName, 3.seconds) + if (effectSound) repeat(5) { playPlingSound() } + } + } + + @SubscribeEvent + fun onWorldChange(event: LorenzWorldChangeEvent) { + checkFooter = true + } + + @HandleEvent + fun onTabUpdate(event: TablistFooterUpdateEvent) { + if (!LorenzUtils.inSkyBlock || !checkFooter) return + val lines = event.footer.split("\n") + if (!lines.any { it.contains("§a§lActive Effects") }) return + + checkFooter = false + var effectsCount = 0 + for (line in lines) { + effectsCountPattern.matchMatcher(line) { + val group = group("name") + effectsCount = group.toInt() + } + } + totalEffectsCount = effectsCount + } + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isEnabled() || !config.nonGodPotEffectDisplay) return + if (RiftAPI.inRift()) return + + config.nonGodPotEffectPos.renderStrings( + display, + extraSpace = 3, + posLabel = "Non God Pot Effects", + ) + } + + @HandleEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(3, "misc.nonGodPotEffectDisplay", "misc.potionEffect.nonGodPotEffectDisplay") + event.move(3, "misc.nonGodPotEffectShowMixins", "misc.potionEffect.nonGodPotEffectShowMixins") + event.move(3, "misc.nonGodPotEffectPos", "misc.potionEffect.nonGodPotEffectPos") + } + + private fun isEnabled() = LorenzUtils.inSkyBlock && !DungeonAPI.inDungeon() && !LorenzUtils.inKuudraFight +}