aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Cole <40234707+DavidArthurCole@users.noreply.github.com>2024-08-25 20:42:33 -0400
committerGitHub <noreply@github.com>2024-08-26 02:42:33 +0200
commit290676ba3954d653bc1cbc0630d2cde90a3249f4 (patch)
treeaad9f4d117b5e1b3ebb50817c87b6ab7b63ec656
parentfe0770027bd09c22c3399ee3abf02c530ebc49ec (diff)
downloadskyhanni-290676ba3954d653bc1cbc0630d2cde90a3249f4.tar.gz
skyhanni-290676ba3954d653bc1cbc0630d2cde90a3249f4.tar.bz2
skyhanni-290676ba3954d653bc1cbc0630d2cde90a3249f4.zip
Feature + Fix: Hoppity Event Summarization (#2311)
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
-rw-r--r--.idea/dictionaries/default_user.xml3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java1
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEggsConfig.java (renamed from src/main/java/at/hannibal2/skyhanni/config/features/event/HoppityEggsConfig.java)8
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEventSummaryConfig.java73
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java36
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/hoppity/RabbitFoundEvent.kt14
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityAPI.kt175
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggDisplayManager.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggType.kt15
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsCompactChat.kt33
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsManager.kt40
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEventSummary.kt277
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityNpc.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryAPI.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBarnManager.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryStrayTracker.kt12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt7
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/SkyBlockTime.kt8
21 files changed, 689 insertions, 39 deletions
diff --git a/.idea/dictionaries/default_user.xml b/.idea/dictionaries/default_user.xml
index b166e60d3..db27217cf 100644
--- a/.idea/dictionaries/default_user.xml
+++ b/.idea/dictionaries/default_user.xml
@@ -100,6 +100,7 @@
<w>hideparticles</w>
<w>hoppity</w>
<w>hoppity's</w>
+ <w>hoppitys</w>
<w>horsezooka</w>
<w>hotbar</w>
<w>hotm</w>
@@ -274,4 +275,4 @@
<w>yolkar</w>
</words>
</dictionary>
-</component>
+</component> \ No newline at end of file
diff --git a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
index 89d7bb9bb..50accf32c 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
+++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
@@ -30,6 +30,7 @@ import at.hannibal2.skyhanni.features.event.diana.InquisitorWaypointShare
import at.hannibal2.skyhanni.features.event.diana.MythologicalCreatureTracker
import at.hannibal2.skyhanni.features.event.hoppity.HoppityCollectionStats
import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggLocations
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEventSummary
import at.hannibal2.skyhanni.features.event.jerry.frozentreasure.FrozenTreasureTracker
import at.hannibal2.skyhanni.features.fishing.tracker.FishingProfitTracker
import at.hannibal2.skyhanni.features.fishing.tracker.SeaCreatureTracker
@@ -337,6 +338,10 @@ object Commands {
"shtpinfested",
"Teleports you to the nearest infested plot",
) { PestFinder.teleportNearestInfestedPlot() }
+ registerCommand(
+ "shhoppitystats",
+ "Look up stats for a Hoppity's Event (by SkyBlock year).\nRun standalone for a list of years that have stats."
+ ) { HoppityEventSummary.sendStatsMessage(it) }
}
private fun usersBugFix() {
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java
index d6a0eb064..56a17dfb9 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DebugConfig.java
@@ -157,6 +157,11 @@ public class DebugConfig {
@ConfigEditorBoolean
public boolean neverFunnyTime = false;
+ @Expose
+ @ConfigOption(name = "Always Hoppity's", desc = "Always act as if Hoppity's Hunt is active.")
+ @ConfigEditorBoolean
+ public boolean alwaysHoppitys = false;
+
// Does not have a config element!
@Expose
public Position trackSoundPosition = new Position(0, 0);
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java
index 251ce51ba..57d3e0699 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/EventConfig.java
@@ -2,6 +2,7 @@ package at.hannibal2.skyhanni.config.features.event;
import at.hannibal2.skyhanni.config.features.event.bingo.BingoConfig;
import at.hannibal2.skyhanni.config.features.event.diana.DianaConfig;
+import at.hannibal2.skyhanni.config.features.event.hoppity.HoppityEggsConfig;
import at.hannibal2.skyhanni.config.features.event.waypoints.LobbyWaypointsConfig;
import at.hannibal2.skyhanni.config.features.event.winter.WinterConfig;
import com.google.gson.annotations.Expose;
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/HoppityEggsConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEggsConfig.java
index 386aa7021..50a5731a5 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/features/event/HoppityEggsConfig.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEggsConfig.java
@@ -1,8 +1,9 @@
-package at.hannibal2.skyhanni.config.features.event;
+package at.hannibal2.skyhanni.config.features.event.hoppity;
import at.hannibal2.skyhanni.config.FeatureToggle;
import at.hannibal2.skyhanni.config.core.config.Position;
import com.google.gson.annotations.Expose;
+import io.github.notenoughupdates.moulconfig.annotations.Accordion;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorColour;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDropdown;
@@ -14,6 +15,11 @@ import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
public class HoppityEggsConfig {
@Expose
+ @ConfigOption(name = "Event Summary", desc = "")
+ @Accordion
+ public HoppityEventSummaryConfig eventSummary = new HoppityEventSummaryConfig();
+
+ @Expose
@ConfigOption(name = "Hoppity Waypoints", desc = "Toggle guess waypoints for Hoppity's Hunt.")
@ConfigEditorBoolean
@FeatureToggle
diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEventSummaryConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEventSummaryConfig.java
new file mode 100644
index 000000000..618b15f7f
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEventSummaryConfig.java
@@ -0,0 +1,73 @@
+package at.hannibal2.skyhanni.config.features.event.hoppity;
+
+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.ConfigEditorDraggableList;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorInfoText;
+import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class HoppityEventSummaryConfig {
+
+ @Expose
+ @ConfigOption(name = "Enabled", desc = "Show a summary of your Hoppity Hunt stats when the event is over.")
+ @ConfigEditorBoolean
+ @FeatureToggle
+ public boolean enabled = true;
+
+ @Expose
+ @ConfigOption(
+ name = "Viewing Stats",
+ desc = "View current and past event stats at any time using §b/shhoppitystats§7."
+ )
+ @ConfigEditorInfoText
+ public String commandInfo;
+
+ @Expose
+ @ConfigOption(
+ name = "Stats List",
+ desc = "Drag text to change what displays in the summary card."
+ )
+ @ConfigEditorDraggableList
+ public List<HoppityStat> statDisplayList = new ArrayList<>(Arrays.asList(
+ HoppityStat.MEAL_EGGS_FOUND,
+ HoppityStat.HOPPITY_RABBITS_BOUGHT,
+ HoppityStat.SIDE_DISH_EGGS,
+ HoppityStat.MILESTONE_RABBITS,
+ HoppityStat.EMPTY_1,
+ HoppityStat.NEW_RABBITS,
+ HoppityStat.EMPTY_2,
+ HoppityStat.DUPLICATE_RABBITS
+ ));
+
+ public enum HoppityStat {
+ MEAL_EGGS_FOUND("§7You found §b45§7/§a47 §6Chocolate Meal Eggs§7."),
+ HOPPITY_RABBITS_BOUGHT("§7You bought §b7 §fRabbits §7from §aHoppity§7."),
+ SIDE_DISH_EGGS("§7You found §b4 §6§lSide Dish §r§6Eggs §7in the §6Chocolate Factory§7."),
+ MILESTONE_RABBITS("§7You claimed §b2 §6§lMilestone Rabbits§7."),
+ EMPTY_1(""),
+ NEW_RABBITS("§7Unique Rabbits: §b7\n §f1 §7- §a1 §7- §91 §7- §51 §7- §61 §7- §d1 §7- §b1"),
+ EMPTY_2(""),
+ DUPLICATE_RABBITS("§7Duplicate Rabbits: §c10\n §f4 §7- §a3 §7- §92 §7- §51 §7- §60 §7- §d0 §7- §b0\n §6+250,000,000 Chocolate"),
+ EMPTY_3(""),
+ STRAY_RABBITS("§7Stray Rabbits: §f20\n §f10 §7- §a6 §7- §93 §7- §51 §7- §60 §7- §d0 §7- §b0\n §6+8,000,000 Chocolate\n §c* §c§oRequires Stray Tracker being enabled to work."),
+ EMPTY_4(""),
+ TIME_IN_CF("§7You spent §b4h 36m §7in the §6Chocolate Factory§7."),
+ ;
+
+ private final String display;
+
+ HoppityStat(String display) {
+ this.display = display;
+ }
+
+ @Override
+ public String toString() {
+ return display;
+ }
+ }
+}
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 39ce4402b..51be4daf1 100644
--- a/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
+++ b/src/main/java/at/hannibal2/skyhanni/config/storage/ProfileSpecificStorage.java
@@ -13,6 +13,7 @@ import at.hannibal2.skyhanni.features.dungeon.DungeonFloor;
import at.hannibal2.skyhanni.features.event.diana.DianaProfitTracker;
import at.hannibal2.skyhanni.features.event.diana.MythologicalCreatureTracker;
import at.hannibal2.skyhanni.features.event.hoppity.HoppityCollectionStats;
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggType;
import at.hannibal2.skyhanni.features.event.jerry.frozentreasure.FrozenTreasureTracker;
import at.hannibal2.skyhanni.features.fame.UpgradeReminder;
import at.hannibal2.skyhanni.features.fishing.tracker.FishingProfitTracker;
@@ -39,6 +40,7 @@ import at.hannibal2.skyhanni.features.rift.area.westvillage.kloon.KloonTerminal;
import at.hannibal2.skyhanni.features.skillprogress.SkillType;
import at.hannibal2.skyhanni.features.slayer.SlayerProfitTracker;
import at.hannibal2.skyhanni.utils.GenericWrapper;
+import at.hannibal2.skyhanni.utils.LorenzRarity;
import at.hannibal2.skyhanni.utils.LorenzVec;
import at.hannibal2.skyhanni.utils.NEUInternalName;
import at.hannibal2.skyhanni.utils.SimpleTimeMark;
@@ -666,4 +668,38 @@ public class ProfileSpecificStorage {
@Expose
public UpgradeReminder.CommunityShopUpgrade communityShopProfileUpgrade = null;
+
+ @Expose
+ public Map<Integer, HoppityEventStats> hoppityEventStats = new HashMap<>();
+
+ public static class HoppityEventStats {
+ @Expose
+ public Map<HoppityEggType, Integer> mealsFound = new HashMap<>();
+
+ @Expose
+ public Map<LorenzRarity, RabbitData> rabbitsFound = new HashMap<>();
+
+ public static class RabbitData {
+ @Expose
+ public int uniques = 0;
+
+ @Expose
+ public int dupes = 0;
+
+ @Expose
+ public int strays = 0;
+ }
+
+ @Expose
+ public long dupeChocolateGained = 0;
+
+ @Expose
+ public long strayChocolateGained = 0;
+
+ @Expose
+ public long millisInCf = 0;
+
+ @Expose
+ public boolean summarized = false;
+ }
}
diff --git a/src/main/java/at/hannibal2/skyhanni/events/hoppity/RabbitFoundEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/hoppity/RabbitFoundEvent.kt
new file mode 100644
index 000000000..865f5a690
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/events/hoppity/RabbitFoundEvent.kt
@@ -0,0 +1,14 @@
+package at.hannibal2.skyhanni.events.hoppity
+
+import at.hannibal2.skyhanni.api.event.SkyHanniEvent
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggType
+
+class RabbitFoundEvent(
+ val eggType: HoppityEggType,
+ val duplicate: Boolean,
+ val rabbitName: String,
+ val chocGained: Long = 0,
+) : SkyHanniEvent() {
+ override fun toString(): String =
+ "§fType§7: ${eggType.coloredName}\n§fDuplicate§7: §b$duplicate\n§fRabbit§7: $rabbitName\n§fChoc Gained§7: §6$chocGained"
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityAPI.kt
new file mode 100644
index 000000000..5c63fe3a8
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityAPI.kt
@@ -0,0 +1,175 @@
+package at.hannibal2.skyhanni.features.event.hoppity
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.events.GuiContainerEvent
+import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.events.hoppity.RabbitFoundEvent
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggsManager.getEggType
+import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryAPI
+import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
+import at.hannibal2.skyhanni.utils.InventoryUtils
+import at.hannibal2.skyhanni.utils.ItemUtils.getLore
+import at.hannibal2.skyhanni.utils.ItemUtils.itemName
+import at.hannibal2.skyhanni.utils.LorenzRarity
+import at.hannibal2.skyhanni.utils.LorenzRarity.DIVINE
+import at.hannibal2.skyhanni.utils.RegexUtils.firstMatcher
+import at.hannibal2.skyhanni.utils.RegexUtils.groupOrNull
+import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
+import at.hannibal2.skyhanni.utils.SkyblockSeason
+import net.minecraft.util.ChatComponentText
+import net.minecraftforge.fml.common.eventhandler.EventPriority
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+@SkyHanniModule
+object HoppityAPI {
+
+ private var hoppityEggChat = mutableListOf<String>()
+ private var duplicate = false
+ private var lastRarity = ""
+ private var lastName = ""
+ private var newRabbit = false
+ private var lastChatMeal: HoppityEggType? = null
+ private var lastDuplicateAmount: Long? = null
+ private var rabbitBought = false
+
+ val hoppityRarities by lazy { LorenzRarity.entries.filter { it <= DIVINE } }
+
+ private fun resetChatData() {
+ this.hoppityEggChat = mutableListOf()
+ this.duplicate = false
+ this.newRabbit = false
+ this.lastRarity = ""
+ this.lastName = ""
+ this.lastChatMeal = null
+ this.lastDuplicateAmount = null
+ this.rabbitBought = false
+ }
+
+ fun isHoppityEvent() = (SkyblockSeason.currentSeason == SkyblockSeason.SPRING || SkyHanniMod.feature.dev.debug.alwaysHoppitys)
+
+ fun rarityByRabbit(rabbit: String): LorenzRarity? = hoppityRarities.firstOrNull { it.chatColorCode == rabbit.substring(0, 2) }
+
+ /**
+ * REGEX-TEST: §f1st Chocolate Milestone
+ * REGEX-TEST: §915th Chocolate Milestone
+ * REGEX-TEST: §622nd Chocolate Milestone
+ */
+ private val milestoneNamePattern by ChocolateFactoryAPI.patternGroup.pattern(
+ "rabbit.milestone",
+ "(?:§.)*?(?<milestone>\\d{1,2})[a-z]{2} Chocolate Milestone",
+ )
+
+ /**
+ * REGEX-TEST: §7Reach §6300B Chocolate §7all-time to
+ * REGEX-TEST: §7Reach §61k Chocolate §7all-time to unlock
+ */
+ private val allTimeLorePattern by ChocolateFactoryAPI.patternGroup.pattern(
+ "milestone.alltime",
+ "§7Reach §6(?<amount>[\\d.MBk]*) Chocolate §7all-time.*",
+ )
+
+ /**
+ * REGEX-TEST: §7Spend §6150B Chocolate §7in the
+ * REGEX-TEST: §7Spend §62M Chocolate §7in the §6Chocolate
+ */
+ private val shopLorePattern by ChocolateFactoryAPI.patternGroup.pattern(
+ "milestone.shop",
+ "§7Spend §6(?<amount>[\\d.MBk]*) Chocolate §7in.*",
+ )
+
+ fun fireSideDishMessage() {
+ LorenzChatEvent(
+ "§d§lHOPPITY'S HUNT §r§dYou found a §r§6§lSide Dish §r§6Egg §r§din the Chocolate Factory§r§d!",
+ ChatComponentText(""),
+ ).postAndCatch()
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGH)
+ fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) {
+ val index = event.slot?.slotIndex ?: return
+ if (index == -999) return
+
+ val clickedStack = InventoryUtils.getItemsInOpenChest()
+ .find { it.slotNumber == event.slot.slotNumber && it.hasStack }
+ ?.stack ?: return
+ val nameText = (if (clickedStack.hasDisplayName()) clickedStack.displayName else clickedStack.itemName)
+
+ milestoneNamePattern.matchMatcher(nameText) {
+ val itemLore = clickedStack.getLore()
+ if (!itemLore.any { it == "§eClick to claim!" }) return
+
+ // Will never match both all time and shop patterns together
+ allTimeLorePattern.firstMatcher(clickedStack.getLore()) {
+ LorenzChatEvent(
+ "§d§lHOPPITY'S HUNT §r§dYou claimed a §r§6§lChocolate Milestone Rabbit §r§din the Chocolate Factory§r§d!",
+ ChatComponentText(""),
+ ).postAndCatch()
+ }
+
+ shopLorePattern.firstMatcher(clickedStack.getLore()) {
+ LorenzChatEvent(
+ "§d§lHOPPITY'S HUNT §r§dYou claimed a §r§6§lShop Milestone Rabbit §r§din the Chocolate Factory§r§d!",
+ ChatComponentText(""),
+ ).postAndCatch()
+ }
+ }
+ }
+
+ // Dumbed down version of the Compact Chat for Hoppity's,
+ // with the additional native context of side dishes
+ fun handleChat(event: LorenzChatEvent) {
+ HoppityEggsManager.eggFoundPatterns.forEach {
+ it.matchMatcher(event.message) {
+ resetChatData()
+ lastChatMeal = getEggType(event)
+ attemptFire(event)
+ }
+ }
+
+ HoppityEggsManager.eggBoughtPattern.matchMatcher(event.message) {
+ if (group("rabbitname").equals(lastName)) {
+ rabbitBought = true
+ lastChatMeal = HoppityEggType.BOUGHT
+ attemptFire(event)
+ }
+ }
+
+ HoppityEggsManager.rabbitFoundPattern.matchMatcher(event.message) {
+ // The only cases where "You found ..." will come in with more than 1 message,
+ // or empty for hoppityEggChat, is where the rabbit was purchased from hoppity,
+ // In the case of buying, we want to reset variables to a clean state during this capture,
+ // as the important capture for the purchased message is the final message in
+ // the chain; "You found [rabbit]" -> "Dupe/New Rabbit" -> "You bought [rabbit]"
+ if ((hoppityEggChat.isEmpty() || hoppityEggChat.size > 1)) {
+ resetChatData()
+ }
+
+ lastName = group("name")
+ lastRarity = group("rarity")
+ attemptFire(event)
+ }
+
+ HoppityEggsManager.newRabbitFound.matchMatcher(event.message) {
+ newRabbit = true
+ groupOrNull("other")?.let {
+ attemptFire(event)
+ return
+ }
+ attemptFire(event)
+ }
+ }
+
+ fun attemptFire(event: LorenzChatEvent, lastDuplicateAmount: Long? = null) {
+ lastDuplicateAmount?.let {
+ this.lastDuplicateAmount = it
+ }
+ hoppityEggChat.add(event.message)
+ if (lastDuplicateAmount != null) {
+ this.duplicate = true
+ }
+ val lastChatMeal = lastChatMeal ?: return
+ if (hoppityEggChat.size == 3) {
+ RabbitFoundEvent(lastChatMeal, duplicate, lastName, lastDuplicateAmount ?: 0).post()
+ }
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggDisplayManager.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggDisplayManager.kt
index f09aa4eac..fe908e58f 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggDisplayManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggDisplayManager.kt
@@ -76,7 +76,7 @@ object HoppityEggDisplayManager {
if (ReminderUtils.isBusy() && !config.showWhileBusy) return emptyList()
val displayList =
- HoppityEggType.entries.map { "§7 - ${it.formattedName} ${it.timeUntil().format()}" }.toMutableList()
+ HoppityEggType.resettingEntries.map { "§7 - ${it.formattedName} ${it.timeUntil().format()}" }.toMutableList()
displayList.add(0, "§bUnclaimed Eggs:")
if (config.showCollectedLocationCount && LorenzUtils.inSkyBlock) {
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt
index dcf56aac6..d15d43d81 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggLocator.kt
@@ -9,7 +9,6 @@ import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent
import at.hannibal2.skyhanni.events.ReceiveParticleEvent
import at.hannibal2.skyhanni.features.fame.ReminderUtils
import at.hannibal2.skyhanni.features.garden.GardenAPI
-import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryAPI
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
import at.hannibal2.skyhanni.utils.ColorUtils.toChromaColor
import at.hannibal2.skyhanni.utils.InventoryUtils
@@ -278,7 +277,7 @@ object HoppityEggLocator {
type == EnumParticleTypes.ENCHANTMENT_TABLE && speed == -2.0f && count == 10
fun isEnabled() = LorenzUtils.inSkyBlock && config.waypoints && !GardenAPI.inGarden() &&
- !ReminderUtils.isBusy(true) && ChocolateFactoryAPI.isHoppityEvent()
+ !ReminderUtils.isBusy(true) && HoppityAPI.isHoppityEvent()
private val ItemStack.isLocatorItem get() = getInternalName() == locatorItem
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggType.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggType.kt
index 2846262e6..23e00cf88 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggType.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggType.kt
@@ -14,9 +14,14 @@ enum class HoppityEggType(
BREAKFAST("Breakfast", "§6", 7),
LUNCH("Lunch", "§9", 14),
DINNER("Dinner", "§a", 21),
+ SIDE_DISH("Side Dish", "§6§l", -1),
+ BOUGHT("Bought", "§a", -1),
+ CHOCOLATE_SHOP_MILESTONE("Shop Milestone", "§6", -1),
+ CHOCOLATE_FACTORY_MILESTONE("Chocolate Milestone", "§6", -1)
;
fun timeUntil(): Duration {
+ if (resetsAt == -1) return Duration.INFINITE
val now = SkyBlockTime.now()
if (now.hour >= resetsAt) {
return now.copy(day = now.day + 1, hour = resetsAt, minute = 0, second = 0)
@@ -38,7 +43,9 @@ enum class HoppityEggType(
val coloredName get() = "$mealColor$mealName"
companion object {
- fun allFound() = entries.forEach { it.markClaimed() }
+ val resettingEntries = entries.filter { it.resetsAt != -1 }
+
+ fun allFound() = resettingEntries.forEach { it.markClaimed() }
fun getMealByName(mealName: String) = entries.find { it.mealName == mealName }
@@ -47,7 +54,7 @@ enum class HoppityEggType(
val currentSbDay = currentSbTime.day
val currentSbHour = currentSbTime.hour
- for (eggType in entries) {
+ for (eggType in resettingEntries) {
if (currentSbHour < eggType.resetsAt || eggType.lastResetDay == currentSbDay) continue
eggType.markSpawned()
eggType.lastResetDay = currentSbDay
@@ -60,11 +67,11 @@ enum class HoppityEggType(
}
fun eggsRemaining(): Boolean {
- return entries.any { !it.claimed }
+ return resettingEntries.any { !it.claimed }
}
fun allEggsRemaining(): Boolean {
- return entries.all { !it.claimed }
+ return resettingEntries.all { !it.claimed }
}
}
}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsCompactChat.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsCompactChat.kt
index 35f87d7da..6bc1c8dfc 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsCompactChat.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsCompactChat.kt
@@ -1,7 +1,11 @@
package at.hannibal2.skyhanni.features.event.hoppity
-import at.hannibal2.skyhanni.config.features.event.HoppityEggsConfig
+import at.hannibal2.skyhanni.config.features.event.hoppity.HoppityEggsConfig
import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggType.CHOCOLATE_FACTORY_MILESTONE
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggType.CHOCOLATE_SHOP_MILESTONE
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggType.SIDE_DISH
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggsManager.eggFoundPatterns
import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggsManager.getEggType
import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryAPI
import at.hannibal2.skyhanni.utils.ChatUtils
@@ -67,7 +71,10 @@ object HoppityEggsCompactChat {
private fun createCompactMessage(): String {
val mealName = lastChatMeal?.coloredName ?: ""
- val mealNameFormatted = if (rabbitBought) "§aBought Rabbit" else "$mealName Egg"
+ val mealNameFormatted = if (rabbitBought) "§aBought Rabbit"
+ else if (lastChatMeal == SIDE_DISH) "§6§lSide Dish §r§6Egg"
+ else if (lastChatMeal == CHOCOLATE_SHOP_MILESTONE || lastChatMeal == CHOCOLATE_FACTORY_MILESTONE) "§6§lMilestone Rabbit"
+ else "$mealName Egg"
val rarityConfig = HoppityEggsManager.config.rarityInCompact
return if (duplicate) {
@@ -86,10 +93,12 @@ object HoppityEggsCompactChat {
}
fun handleChat(event: LorenzChatEvent) {
- HoppityEggsManager.eggFoundPattern.matchMatcher(event.message) {
- resetCompactData()
- lastChatMeal = getEggType(event)
- compactChat(event)
+ eggFoundPatterns.forEach {
+ it.matchMatcher(event.message) {
+ resetCompactData()
+ lastChatMeal = getEggType(event)
+ compactChat(event)
+ }
}
HoppityEggsManager.eggBoughtPattern.matchMatcher(event.message) {
@@ -100,12 +109,12 @@ object HoppityEggsCompactChat {
}
HoppityEggsManager.rabbitFoundPattern.matchMatcher(event.message) {
- // The only case where "You found ..." will come in with more than 1 message,
- // or empty for hoppityEggChat, is where the rabbit was purchased from hoppity
- // in this case, we want to reset variables to a clean state during this capture,
+ // The only cases where "You found ..." will come in with more than 1 message,
+ // or empty for hoppityEggChat, is where the rabbit was purchased from hoppity,
+ // In the case of buying, we want to reset variables to a clean state during this capture,
// as the important capture for the purchased message is the final message in
// the chain; "You found [rabbit]" -> "Dupe/New Rabbit" -> "You bought [rabbit]"
- if (hoppityEggChat.isEmpty() || hoppityEggChat.size > 1) {
+ if ((hoppityEggChat.isEmpty() || hoppityEggChat.size > 1)) {
resetCompactData()
}
@@ -130,7 +139,9 @@ object HoppityEggsCompactChat {
}
}
- fun clickableCompact(onClick: () -> Unit): Boolean = if (hoppityEggChat.isNotEmpty()) {
+ fun clickableCompact(onClick: () -> Unit): Boolean = if (hoppityEggChat.isNotEmpty() && !rabbitBought && lastChatMeal != null &&
+ HoppityEggType.resettingEntries.contains(lastChatMeal)
+ ) {
val hover = hoppityEggChat.joinToString("\n") + " \n§eClick here to share the location of this chocolate egg with the server!"
hoppityEggChat.clear()
ChatUtils.clickableChat(
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsManager.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsManager.kt
index 93ae797e7..fcf4b17f0 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEggsManager.kt
@@ -25,6 +25,7 @@ import at.hannibal2.skyhanni.utils.StringUtils.removeColor
import at.hannibal2.skyhanni.utils.TimeUtils.format
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import java.util.regex.Matcher
+import java.util.regex.Pattern
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
@@ -37,13 +38,37 @@ object HoppityEggsManager {
* REGEX-TEST: §d§lHOPPITY'S HUNT §r§dYou found a §r§9Chocolate Lunch Egg §r§don a ledge next to the stairs up§r§d!
* REGEX-TEST: §d§lHOPPITY'S HUNT §r§dYou found a §r§aChocolate Dinner Egg §r§dbehind Emissary Sisko§r§d!
* REGEX-TEST: §d§lHOPPITY'S HUNT §r§dYou found a §r§9Chocolate Lunch Egg §r§dnear the Diamond Essence Shop§r§d!
+ * REGEX-TEST: §d§lHOPPITY'S HUNT §r§dYou found a §r§6§lSide Dish §r§6Egg §r§din the Chocolate Factory§r§d!
*/
- val eggFoundPattern by ChocolateFactoryAPI.patternGroup.pattern(
+ private val eggFoundPattern by ChocolateFactoryAPI.patternGroup.pattern(
"egg.found",
"§d§lHOPPITY'S HUNT §r§dYou found a §r§.Chocolate (?<meal>\\w+) Egg §r§d(?<note>.*)§r§d!",
)
/**
+ * REGEX-TEST: §d§lHOPPITY'S HUNT §r§dYou found a §r§6§lSide Dish §r§6Egg §r§din the Chocolate Factory§r§d!
+ */
+ private val sideDishEggFoundPattern by ChocolateFactoryAPI.patternGroup.pattern(
+ "sidedish.found",
+ "§d§lHOPPITY'S HUNT §r§dYou found a §r§6§l(?<meal>.*) §r§6Egg §r§din the Chocolate Factory§r§d!",
+ )
+
+ /**
+ * REGEX-TEST: §d§lHOPPITY'S HUNT §r§dYou claimed a §r§6§lShop Milestone Rabbit §r§din the Chocolate Factory§r§d!
+ * REGEX-TEST: §d§lHOPPITY'S HUNT §r§dYou claimed a §r§6§lChocolate Milestone Rabbit §r§din the Chocolate Factory§r§d!
+ */
+ private val milestoneRabbitFoundPattern by ChocolateFactoryAPI.patternGroup.pattern(
+ "milestone.claimed",
+ "§d§lHOPPITY'S HUNT §r§dYou claimed a §r§6§l(?<meal>[\\w ]+) Rabbit §r§din the Chocolate Factory§r§d!",
+ )
+
+ val eggFoundPatterns: List<Pattern> = listOf(
+ eggFoundPattern,
+ sideDishEggFoundPattern,
+ milestoneRabbitFoundPattern,
+ )
+
+ /**
* REGEX-TEST: §aYou bought §r§9Casanova §r§afor §r§6970,000 Coins§r§a!
* REGEX-TEST: §aYou bought §r§fHeidie §r§afor §r§6194,000 Coins§r§a!
*/
@@ -74,7 +99,7 @@ object HoppityEggsManager {
val duplicateRabbitFound by ChocolateFactoryAPI.patternGroup.pattern(
"rabbit.duplicate",
- "§7§lDUPLICATE RABBIT! §6\\+(?<amount>[\\d,]+) Chocolate"
+ "§7§lDUPLICATE RABBIT! §6\\+(?<amount>[\\d,]+) Chocolate",
)
private val noEggsLeftPattern by ChocolateFactoryAPI.patternGroup.pattern(
@@ -123,8 +148,9 @@ object HoppityEggsManager {
}
HoppityEggsCompactChat.handleChat(event)
+ HoppityAPI.handleChat(event)
- if (!ChocolateFactoryAPI.isHoppityEvent()) return
+ if (!HoppityAPI.isHoppityEvent()) return
eggFoundPattern.matchMatcher(event.message) {
HoppityEggLocations.saveNearestEgg()
@@ -141,7 +167,7 @@ object HoppityEggsManager {
HoppityEggType.allFound()
if (config.timeInChat) {
- val nextEgg = HoppityEggType.entries.minByOrNull { it.timeUntil() } ?: return
+ val nextEgg = HoppityEggType.resettingEntries.minByOrNull { it.timeUntil() } ?: return
ChatUtils.chat("§eNext egg available in §b${nextEgg.timeUntil().format()}§e.")
event.blockedReason = "hoppity_egg"
}
@@ -151,7 +177,7 @@ object HoppityEggsManager {
eggAlreadyCollectedPattern.matchMatcher(event.message) {
getEggType(event).markClaimed()
if (config.timeInChat) {
- val nextEgg = HoppityEggType.entries.minByOrNull { it.timeUntil() } ?: return
+ val nextEgg = HoppityEggType.resettingEntries.minByOrNull { it.timeUntil() } ?: return
ChatUtils.chat("§eNext egg available in §b${nextEgg.timeUntil().format()}§e.")
event.blockedReason = "hoppity_egg"
}
@@ -222,7 +248,7 @@ object HoppityEggsManager {
if (lastWarnTime.passedSince() < 1.minutes) return
lastWarnTime = now()
- val amount = HoppityEggType.entries.size
+ val amount = HoppityEggType.resettingEntries.size
val message = "All $amount Hoppity Eggs are ready to be found!"
if (config.warpUnclaimedEggs) {
if (LorenzUtils.inSkyBlock) {
@@ -255,5 +281,5 @@ object HoppityEggsManager {
}
fun isActive() = (LorenzUtils.inSkyBlock || (LorenzUtils.onHypixel && config.showOutsideSkyblock)) &&
- ChocolateFactoryAPI.isHoppityEvent()
+ HoppityAPI.isHoppityEvent()
}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEventSummary.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEventSummary.kt
new file mode 100644
index 000000000..f334e407e
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEventSummary.kt
@@ -0,0 +1,277 @@
+package at.hannibal2.skyhanni.features.event.hoppity
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.api.event.HandleEvent
+import at.hannibal2.skyhanni.config.features.event.hoppity.HoppityEventSummaryConfig.HoppityStat
+import at.hannibal2.skyhanni.config.storage.ProfileSpecificStorage.HoppityEventStats
+import at.hannibal2.skyhanni.config.storage.ProfileSpecificStorage.HoppityEventStats.RabbitData
+import at.hannibal2.skyhanni.data.ProfileStorageData
+import at.hannibal2.skyhanni.events.ProfileJoinEvent
+import at.hannibal2.skyhanni.events.SecondPassedEvent
+import at.hannibal2.skyhanni.events.hoppity.RabbitFoundEvent
+import at.hannibal2.skyhanni.features.inventory.chocolatefactory.ChocolateFactoryAPI
+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.addOrPut
+import at.hannibal2.skyhanni.utils.CollectionUtils.sumAllValues
+import at.hannibal2.skyhanni.utils.LorenzRarity
+import at.hannibal2.skyhanni.utils.LorenzUtils
+import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators
+import at.hannibal2.skyhanni.utils.SkyBlockTime
+import at.hannibal2.skyhanni.utils.SkyBlockTime.Companion.SKYBLOCK_DAY_MILLIS
+import at.hannibal2.skyhanni.utils.SkyBlockTime.Companion.SKYBLOCK_HOUR_MILLIS
+import at.hannibal2.skyhanni.utils.SkyblockSeason
+import at.hannibal2.skyhanni.utils.StringUtils
+import at.hannibal2.skyhanni.utils.TimeUtils.format
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import kotlin.time.Duration.Companion.milliseconds
+
+@SkyHanniModule
+object HoppityEventSummary {
+ private val config get() = SkyHanniMod.feature.event.hoppityEggs
+ private val lineHeader = " ".repeat(4)
+
+ private var firstInCf = 0L
+ private var inCfNow = false
+
+ @HandleEvent
+ fun onRabbitFound(event: RabbitFoundEvent) {
+ if (!HoppityAPI.isHoppityEvent()) return
+ val stats = getYearStats().first ?: return
+
+ stats.mealsFound.addOrPut(event.eggType, 1)
+ val rarity = HoppityAPI.rarityByRabbit(event.rabbitName) ?: return
+ val rarityMap = stats.rabbitsFound.getOrPut(rarity) { RabbitData() }
+ if (event.duplicate) rarityMap.dupes++
+ else rarityMap.uniques++
+ if (event.chocGained > 0) stats.dupeChocolateGained += event.chocGained
+ }
+
+ @SubscribeEvent
+ fun onSecondPassed(event: SecondPassedEvent) {
+ if (!LorenzUtils.inSkyBlock) return
+ checkEnded()
+ if (!HoppityAPI.isHoppityEvent()) return
+ checkInit()
+ checkAddCfTime()
+ }
+
+ @SubscribeEvent
+ fun onProfileJoin(event: ProfileJoinEvent) {
+ checkEnded()
+ }
+
+ private fun checkInit() {
+ val statStorage = ProfileStorageData.profileSpecific?.hoppityEventStats ?: return
+ val currentYear = SkyBlockTime.now().year
+ if (statStorage.containsKey(currentYear)) return
+ statStorage[currentYear] = HoppityEventStats()
+ }
+
+ private fun getYearStats(year: Int? = null): Pair<HoppityEventStats?, Int> {
+ val queryYear = year ?: SkyBlockTime.now().year
+ val storage = ProfileStorageData.profileSpecific?.hoppityEventStats ?: return Pair(null, queryYear)
+ if (!storage.containsKey(queryYear)) return Pair(null, queryYear)
+ return Pair(storage[queryYear], queryYear)
+ }
+
+ private fun checkAddCfTime() {
+ if (ChocolateFactoryAPI.inChocolateFactory && !this.inCfNow) {
+ this.inCfNow = true
+ firstInCf = SkyBlockTime.now().toMillis()
+ } else if (!ChocolateFactoryAPI.inChocolateFactory && this.inCfNow) {
+ val stats = getYearStats().first ?: return
+ stats.millisInCf += (SkyBlockTime.now().toMillis() - firstInCf)
+ this.inCfNow = false
+ firstInCf = 0
+ }
+ }
+
+ private fun checkEnded() {
+ val (stats, year) = getYearStats()
+ if (stats == null || stats.summarized) return
+
+ val currentYear = SkyBlockTime.now().year
+ val currentSeason = SkyblockSeason.currentSeason
+ val isSpring = currentSeason == SkyblockSeason.SPRING
+
+ if (year < currentYear || (year == currentYear && !isSpring) && config.eventSummary.enabled) {
+ sendStatsMessage(stats, year)
+ (ProfileStorageData.profileSpecific?.hoppityEventStats?.get(year)?.also { it.summarized = true }
+ ?: ErrorManager.skyHanniError("Could not save summarization state in Hoppity Event Summarization."))
+ }
+ }
+
+ // First event was year 346 -> #1, 20th event was year 365, etc.
+ private fun getHoppityEventNumber(skyblockYear: Int): Int = (skyblockYear - 345)
+
+ fun addStrayCaught(rarity: LorenzRarity, chocGained: Long) {
+ if (!HoppityAPI.isHoppityEvent()) return
+ val stats = getYearStats().first ?: return
+ val rarityMap = stats.rabbitsFound.getOrPut(rarity) { RabbitData() }
+ rarityMap.strays++
+ stats.strayChocolateGained += chocGained
+ }
+
+ private fun StringBuilder.appendHeadedLine(line: String) {
+ appendLine("$lineHeader$line")
+ }
+
+ private fun StringBuilder.addExtraChocFormatLine(chocGained: Long) {
+ if (chocGained <= 0) return
+ appendHeadedLine(
+ buildString {
+ append(" §6+${chocGained.addSeparators()} Chocolate")
+ if (SkyHanniMod.feature.inventory.chocolateFactory.showDuplicateTime) {
+ val timeFormatted = ChocolateFactoryAPI.timeUntilNeed(chocGained).format(maxUnits = 2)
+ append(" §7(§a+§b$timeFormatted§7)")
+ }
+ },
+ )
+ }
+
+ private val summaryOperationList by lazy {
+ buildMap<HoppityStat, (sb: StringBuilder, stats: HoppityEventStats, year: Int) -> Unit> {
+ put(HoppityStat.MEAL_EGGS_FOUND) { sb, stats, year ->
+ stats.getEggsFoundFormat(year).takeIf { it != null }?.let {
+ sb.appendHeadedLine(it)
+ }
+ }
+
+ put(HoppityStat.HOPPITY_RABBITS_BOUGHT) { sb, stats, _ ->
+ stats.mealsFound[HoppityEggType.BOUGHT]?.let {
+ sb.appendHeadedLine("§7You bought §b$it §f${StringUtils.pluralize(it, "Rabbit")} §7from §aHoppity§7.")
+ }
+ }
+
+ put(HoppityStat.SIDE_DISH_EGGS) { sb, stats, _ ->
+ stats.mealsFound[HoppityEggType.SIDE_DISH]?.let {
+ sb.appendHeadedLine(
+ "§7You found §b$it §6§lSide Dish §r§6${StringUtils.pluralize(it, "Egg")}§7 " +
+ "§7in the §6Chocolate Factory§7.",
+ )
+ }
+ }
+
+ put(HoppityStat.MILESTONE_RABBITS) { sb, stats, _ ->
+ ((stats.mealsFound[HoppityEggType.CHOCOLATE_FACTORY_MILESTONE] ?: 0) +
+ (stats.mealsFound[HoppityEggType.CHOCOLATE_SHOP_MILESTONE] ?: 0)).takeIf { it > 0 }?.let {
+ sb.appendHeadedLine("§7You claimed §b$it §6§lMilestone §r§6${StringUtils.pluralize(it, "Rabbit")}§7.")
+ }
+ }
+
+ put(HoppityStat.NEW_RABBITS) { sb, stats, _ ->
+ getRabbitsFormat(stats.rabbitsFound.mapValues { m -> m.value.uniques }, "Unique").forEach {
+ sb.appendHeadedLine(it)
+ }
+ }
+
+ put(HoppityStat.DUPLICATE_RABBITS) { sb, stats, _ ->
+ getRabbitsFormat(stats.rabbitsFound.mapValues { m -> m.value.dupes }, "Duplicate").forEach {
+ sb.appendHeadedLine(it)
+ }
+ sb.addExtraChocFormatLine(stats.dupeChocolateGained)
+ }
+
+ put(HoppityStat.STRAY_RABBITS) { sb, stats, _ ->
+ getRabbitsFormat(stats.rabbitsFound.mapValues { m -> m.value.strays }, "Stray").forEach {
+ sb.appendHeadedLine(it)
+ }
+ sb.addExtraChocFormatLine(stats.strayChocolateGained)
+ }
+
+ put(HoppityStat.TIME_IN_CF) { sb, stats, _ ->
+ sb.appendHeadedLine("§7You spent §b${stats.millisInCf.milliseconds.format(maxUnits = 2)} §7in the §6Chocolate Factory§7.")
+ }
+
+ put(HoppityStat.EMPTY_1) { sb, _, _ -> sb.appendLine() }
+ put(HoppityStat.EMPTY_2) { sb, _, _ -> sb.appendLine() }
+ put(HoppityStat.EMPTY_3) { sb, _, _ -> sb.appendLine() }
+ put(HoppityStat.EMPTY_4) { sb, _, _ -> sb.appendLine() }
+ }
+ }
+
+ fun sendStatsMessage(it: Array<String>) {
+ val statsStorage = ProfileStorageData.profileSpecific?.hoppityEventStats ?: return
+ val currentYear = SkyBlockTime.now().year
+ val statsYearList = statsStorage.keys.takeIf { it.isNotEmpty() } ?: mutableListOf()
+ val statsYearFormatList = statsStorage.keys.takeIf { it.isNotEmpty() }?.map {
+ "§b$it${if (it == currentYear) " §a(Current)§r" else ""}"
+ }?.toMutableList() ?: mutableListOf()
+
+ val parsedInt: Int? = if (it.size == 1) it[0].toIntOrNull() else null
+
+ val availableYearsFormat = "§eStats are available for the following years:§af\n${statsYearFormatList.joinToString("§e,") { it }}"
+
+ if (parsedInt == null) {
+ if (HoppityAPI.isHoppityEvent()) {
+ val stats = getYearStats(currentYear).first ?: return
+ sendStatsMessage(stats, currentYear)
+ } else ChatUtils.chat(availableYearsFormat)
+ } else if (!statsYearList.contains(parsedInt)) {
+ ChatUtils.chat("Could not find stats for year §b$parsedInt§e.\n$availableYearsFormat")
+ } else {
+ val stats = getYearStats(parsedInt).first ?: return
+ sendStatsMessage(stats, parsedInt)
+ }
+ }
+
+ private fun sendStatsMessage(stats: HoppityEventStats, eventYear: Int?) {
+ if (eventYear == null) return
+ val summaryBuilder: StringBuilder = StringBuilder()
+ summaryBuilder.appendLine("§d§l${"▬".repeat(64)}")
+
+ // Header
+ summaryBuilder.appendLine("${" ".repeat(26)}§d§lHoppity's Hunt #${getHoppityEventNumber(eventYear)} Stats")
+ summaryBuilder.appendLine()
+
+ // Various stats from config
+ val statsBuilder: StringBuilder = StringBuilder()
+ config.eventSummary.statDisplayList.forEach {
+ summaryOperationList[it]?.invoke(statsBuilder, stats, eventYear)
+ }
+ if (statsBuilder.toString().replace("\n", "").isEmpty()) {
+ statsBuilder.appendHeadedLine("§c§lNothing to show!\n§c§oGo find some eggs!")
+ }
+
+ // Append stats
+ summaryBuilder.append(statsBuilder)
+
+ // Footer
+ summaryBuilder.append("§d§l${"▬".repeat(64)}")
+
+ ChatUtils.chat(summaryBuilder.toString(), prefix = false)
+ }
+
+ private fun HoppityEventStats.getEggsFoundFormat(year: Int): String? =
+ mealsFound.filterKeys { it in HoppityEggType.resettingEntries }.sumAllValues().toInt().takeIf { it > 0 }?.let {
+ val milliDifference = SkyBlockTime.now().toMillis() - SkyBlockTime.fromSbYear(year).toMillis()
+ val pastEvent = milliDifference > SkyBlockTime.SKYBLOCK_SEASON_MILLIS
+ // Calculate total eggs from complete days and incomplete day periods
+ val previousEggs = if (pastEvent) 279 else (milliDifference / SKYBLOCK_DAY_MILLIS).toInt() * 3
+ val currentEggs = when {
+ pastEvent -> 0
+ // Add eggs for the current day based on time of day
+ milliDifference % SKYBLOCK_DAY_MILLIS >= SKYBLOCK_HOUR_MILLIS * 21 -> 3 // Dinner egg, 9 PM
+ milliDifference % SKYBLOCK_DAY_MILLIS >= SKYBLOCK_HOUR_MILLIS * 14 -> 2 // Lunch egg, 2 PM
+ milliDifference % SKYBLOCK_DAY_MILLIS >= SKYBLOCK_HOUR_MILLIS * 7 -> 1 // Breakfast egg, 7 AM
+ else -> 0
+ }
+ val spawnedMealsEggs = previousEggs + currentEggs
+ "§7You found §b$it§7/§a$spawnedMealsEggs §6Chocolate Meal ${StringUtils.pluralize(it, "Egg")}§7."
+ }
+
+
+ private fun getRabbitsFormat(rarityMap: Map<LorenzRarity, Int>, name: String): List<String> {
+ val rabbitsSum = rarityMap.values.sum()
+ if (rabbitsSum == 0) return emptyList()
+
+ return mutableListOf(
+ "§7$name Rabbits: §f$rabbitsSum",
+ HoppityAPI.hoppityRarities.joinToString(" §7-") {
+ " ${it.chatColorCode}${rarityMap[it] ?: 0}"
+ },
+ )
+ }
+}
diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityNpc.kt b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityNpc.kt
index 877e161ef..217e93bb4 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityNpc.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityNpc.kt
@@ -50,7 +50,7 @@ object HoppityNpc {
if (!isReminderEnabled()) return
if (ReminderUtils.isBusy()) return
if (hoppityYearOpened == SkyBlockTime.now().year) return
- if (!ChocolateFactoryAPI.isHoppityEvent()) return
+ if (!HoppityAPI.isHoppityEvent()) return
if (lastReminderSent.passedSince() <= 2.minutes) return
ChatUtils.clickableChat(
@@ -59,7 +59,7 @@ object HoppityNpc {
disableReminder()
ChatUtils.chat("§eHoppity's Shop reminder disabled.")
},
- oneTimeClick = true
+ oneTimeClick = true,
)
lastReminderSent = SimpleTimeMark.now()
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryAPI.kt
index 57b572fe4..59a119bf5 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryAPI.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryAPI.kt
@@ -192,9 +192,6 @@ object ChocolateFactoryAPI {
fun isEnabled() = LorenzUtils.inSkyBlock && config.enabled
- // TODO add debug toggle
- fun isHoppityEvent() = SkyblockSeason.currentSeason == SkyblockSeason.SPRING
-
fun isMaxPrestige() = currentPrestige >= maxPrestige
fun timeTowerChargeDuration() =
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBarnManager.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBarnManager.kt
index 20d752e35..bd1fc8717 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBarnManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryBarnManager.kt
@@ -2,6 +2,7 @@ package at.hannibal2.skyhanni.features.inventory.chocolatefactory
import at.hannibal2.skyhanni.events.InventoryCloseEvent
import at.hannibal2.skyhanni.events.LorenzChatEvent
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityAPI
import at.hannibal2.skyhanni.features.event.hoppity.HoppityCollectionData
import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggsCompactChat
import at.hannibal2.skyhanni.features.event.hoppity.HoppityEggsManager
@@ -56,6 +57,7 @@ object ChocolateFactoryBarnManager {
}
ChocolateAmount.addToAll(amount)
HoppityEggsCompactChat.compactChat(event, lastDuplicateAmount = amount)
+ HoppityAPI.attemptFire(event, lastDuplicateAmount = amount)
}
rabbitCrashedPattern.matchMatcher(event.message) {
@@ -101,7 +103,7 @@ object ChocolateFactoryBarnManager {
return
}
- if (config.rabbitCrushOnlyDuringHoppity && !ChocolateFactoryAPI.isHoppityEvent()) return
+ if (config.rabbitCrushOnlyDuringHoppity && !HoppityAPI.isHoppityEvent()) return
val fullLevel = if (profileStorage.currentRabbits == profileStorage.maxRabbits) "full" else "almost full"
ChatUtils.clickableChat(
diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryStrayTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryStrayTracker.kt
index 5dd90a82b..e8ad465cd 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryStrayTracker.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/chocolatefactory/ChocolateFactoryStrayTracker.kt
@@ -4,6 +4,8 @@ import at.hannibal2.skyhanni.events.GuiContainerEvent
import at.hannibal2.skyhanni.events.GuiRenderEvent
import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent
import at.hannibal2.skyhanni.events.SecondPassedEvent
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityAPI
+import at.hannibal2.skyhanni.features.event.hoppity.HoppityEventSummary
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
import at.hannibal2.skyhanni.utils.CollectionUtils.addAsSingletonList
import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut
@@ -12,6 +14,7 @@ import at.hannibal2.skyhanni.utils.InventoryUtils
import at.hannibal2.skyhanni.utils.ItemUtils.getLore
import at.hannibal2.skyhanni.utils.ItemUtils.itemName
import at.hannibal2.skyhanni.utils.ItemUtils.name
+import at.hannibal2.skyhanni.utils.LorenzRarity
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.NumberUtil.formatLong
import at.hannibal2.skyhanni.utils.RegexUtils.matchMatcher
@@ -137,6 +140,11 @@ object ChocolateFactoryStrayTracker {
tracker.modify { it.straysCaught.addOrPut(rarity, 1) }
val extraTime = ChocolateFactoryAPI.timeUntilNeed(chocAmount + 1)
tracker.modify { it.straysExtraChocMs.addOrPut(rarity, extraTime.inWholeMilliseconds) }
+ if (HoppityAPI.isHoppityEvent()) {
+ LorenzRarity.getByName(rarity)?.let {
+ HoppityEventSummary.addStrayCaught(it, chocAmount)
+ }
+ }
}
private fun incrementGoldenType(typeCaught: String, amount: Int = 1) {
@@ -276,7 +284,6 @@ object ChocolateFactoryStrayTracker {
@SubscribeEvent(priority = EventPriority.HIGH)
fun onSlotClick(event: GuiContainerEvent.SlotClickEvent) {
- if (!isEnabled()) return
val index = event.slot?.slotIndex ?: return
if (index == -999) return
if (claimedStraysSlots.contains(index)) return
@@ -287,6 +294,9 @@ object ChocolateFactoryStrayTracker {
val nameText = (if (clickedStack.hasDisplayName()) clickedStack.displayName else clickedStack.itemName)
if (!nameText.equals("§6§lGolden Rabbit §8- §aSide Dish")) return
+ HoppityAPI.fireSideDishMessage()
+ if (!isEnabled()) return
+
claimedStraysSlots.add(index)
incrementGoldenType("sidedish")
incrementRarity("legendary", 0)
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt
index 100d93b19..cc0e20efe 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzRarity.kt
@@ -27,7 +27,7 @@ enum class LorenzRarity(val color: LorenzColor, val id: Int) {
ErrorManager.logErrorStateWithData(
"Problem with item rarity detected.",
"Trying to get an item rarity below common",
- "ordinal" to ordinal
+ "ordinal" to ordinal,
)
}
return rarityBelow
@@ -39,7 +39,7 @@ enum class LorenzRarity(val color: LorenzColor, val id: Int) {
ErrorManager.logErrorStateWithData(
"Problem with item rarity detected.",
"Trying to get an item rarity above special",
- "ordinal" to ordinal
+ "ordinal" to ordinal,
)
}
return rarityBelow
@@ -50,6 +50,7 @@ enum class LorenzRarity(val color: LorenzColor, val id: Int) {
companion object {
fun getById(id: Int) = if (entries.size > id) entries[id] else null
- fun getByName(name: String) = entries.firstOrNull { it.name == name }
+
+ fun getByName(name: String): LorenzRarity? = entries.find { it.name.equals(name, ignoreCase = true) }
}
}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockTime.kt b/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockTime.kt
index 1fc21df52..6d52de55c 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockTime.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockTime.kt
@@ -28,15 +28,19 @@ data class SkyBlockTime(
companion object {
private const val SKYBLOCK_EPOCH_START_MILLIS = 1559829300000L // Day 1, Year 1
const val SKYBLOCK_YEAR_MILLIS = 124 * 60 * 60 * 1000L
+ const val SKYBLOCK_SEASON_MILLIS = SKYBLOCK_YEAR_MILLIS / 4
private const val SKYBLOCK_MONTH_MILLIS = SKYBLOCK_YEAR_MILLIS / 12
- private const val SKYBLOCK_DAY_MILLIS = SKYBLOCK_MONTH_MILLIS / 31
- private const val SKYBLOCK_HOUR_MILLIS = SKYBLOCK_DAY_MILLIS / 24
+ const val SKYBLOCK_DAY_MILLIS = SKYBLOCK_MONTH_MILLIS / 31
+ const val SKYBLOCK_HOUR_MILLIS = SKYBLOCK_DAY_MILLIS / 24
private const val SKYBLOCK_MINUTE_MILLIS = SKYBLOCK_HOUR_MILLIS / 60
private const val SKYBLOCK_SECOND_MILLIS = SKYBLOCK_MINUTE_MILLIS / 60
fun fromInstant(instant: Instant): SkyBlockTime =
calculateSkyBlockTime(instant.toEpochMilli() - SKYBLOCK_EPOCH_START_MILLIS)
+ fun fromSbYear(year: Int): SkyBlockTime =
+ fromInstant(Instant.ofEpochMilli(SKYBLOCK_EPOCH_START_MILLIS + (SKYBLOCK_YEAR_MILLIS * year)))
+
fun now(): SkyBlockTime = fromInstant(Instant.now())
private fun calculateSkyBlockTime(realMillis: Long): SkyBlockTime {