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
+}