From 290676ba3954d653bc1cbc0630d2cde90a3249f4 Mon Sep 17 00:00:00 2001 From: David Cole <40234707+DavidArthurCole@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:42:33 -0400 Subject: Feature + Fix: Hoppity Event Summarization (#2311) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../hannibal2/skyhanni/config/commands/Commands.kt | 5 + .../skyhanni/config/features/dev/DebugConfig.java | 5 + .../config/features/event/EventConfig.java | 1 + .../config/features/event/HoppityEggsConfig.java | 192 -------------- .../features/event/hoppity/HoppityEggsConfig.java | 198 +++++++++++++++ .../event/hoppity/HoppityEventSummaryConfig.java | 73 ++++++ .../config/storage/ProfileSpecificStorage.java | 36 +++ .../skyhanni/events/hoppity/RabbitFoundEvent.kt | 14 ++ .../skyhanni/features/event/hoppity/HoppityAPI.kt | 175 +++++++++++++ .../event/hoppity/HoppityEggDisplayManager.kt | 2 +- .../features/event/hoppity/HoppityEggLocator.kt | 3 +- .../features/event/hoppity/HoppityEggType.kt | 15 +- .../event/hoppity/HoppityEggsCompactChat.kt | 33 ++- .../features/event/hoppity/HoppityEggsManager.kt | 40 ++- .../features/event/hoppity/HoppityEventSummary.kt | 277 +++++++++++++++++++++ .../skyhanni/features/event/hoppity/HoppityNpc.kt | 4 +- .../chocolatefactory/ChocolateFactoryAPI.kt | 3 - .../ChocolateFactoryBarnManager.kt | 4 +- .../ChocolateFactoryStrayTracker.kt | 12 +- .../at/hannibal2/skyhanni/utils/LorenzRarity.kt | 7 +- .../at/hannibal2/skyhanni/utils/SkyBlockTime.kt | 8 +- 21 files changed, 878 insertions(+), 229 deletions(-) delete mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/event/HoppityEggsConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEggsConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEventSummaryConfig.java create mode 100644 src/main/java/at/hannibal2/skyhanni/events/hoppity/RabbitFoundEvent.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityAPI.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/event/hoppity/HoppityEventSummary.kt (limited to 'src/main/java/at/hannibal2') 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/HoppityEggsConfig.java deleted file mode 100644 index 386aa7021..000000000 --- a/src/main/java/at/hannibal2/skyhanni/config/features/event/HoppityEggsConfig.java +++ /dev/null @@ -1,192 +0,0 @@ -package at.hannibal2.skyhanni.config.features.event; - -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.ConfigEditorBoolean; -import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorColour; -import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDropdown; -import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider; -import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorText; -import io.github.notenoughupdates.moulconfig.annotations.ConfigLink; -import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; - -public class HoppityEggsConfig { - - @Expose - @ConfigOption(name = "Hoppity Waypoints", desc = "Toggle guess waypoints for Hoppity's Hunt.") - @ConfigEditorBoolean - @FeatureToggle - public boolean waypoints = true; - - @Expose - @ConfigOption( - name = "Show Waypoints Immediately", - desc = "Show an estimated waypoint immediately after clicking.\n" + - "§cThis might cause issues with other particle sources." - ) - @ConfigEditorBoolean - public boolean waypointsImmediately = false; - - @Expose - @ConfigOption(name = "Color", desc = "Color of the waypoint.") - @ConfigEditorColour - public String waypointColor = "0:53:46:224:73"; - - @Expose - @ConfigOption(name = "Show Line", desc = "Show a line to the waypoint.") - @ConfigEditorBoolean - @FeatureToggle - public boolean showLine = false; - - @Expose - @ConfigOption(name = "Show All Waypoints", desc = "Show all possible egg waypoints for the current lobby. §e" + - "Only works when you don't have an Egglocator in your inventory.") - @ConfigEditorBoolean - public boolean showAllWaypoints = false; - - @Expose - @ConfigOption(name = "Hide Duplicate Waypoints", desc = "Hide egg waypoints you have already found.\n" + - "§eOnly works when you don't have an Egglocator in your inventory.") - @ConfigEditorBoolean - @FeatureToggle - public boolean hideDuplicateWaypoints = false; - - @Expose - @ConfigOption(name = "Mark Duplicate Locations", desc = "Marks egg location waypoints which you have already found in red.") - @ConfigEditorBoolean - @FeatureToggle - public boolean highlightDuplicateEggLocations = false; - - @Expose - @ConfigOption(name = "Mark Nearby Duplicates", desc = "Always show duplicate egg locations when nearby.") - @ConfigEditorBoolean - @FeatureToggle - public boolean showNearbyDuplicateEggLocations = false; - - @Expose - @ConfigOption(name = "Load from NEU PV", desc = "Load Hoppity Egg Location data from API when opening the NEU Profile Viewer.") - @ConfigEditorBoolean - @FeatureToggle - public boolean loadFromNeuPv = true; - - @Expose - @ConfigOption(name = "Show Unclaimed Eggs", desc = "Display which eggs haven't been found in the last SkyBlock day.") - @ConfigEditorBoolean - @FeatureToggle - public boolean showClaimedEggs = false; - - @Expose - @ConfigOption(name = "Show Collected Locations", desc = "Show the number of found egg locations on this island.\n" + - "§eThis is not retroactive and may not be fully synced with Hypixel's count.") - @ConfigEditorBoolean - @FeatureToggle - public boolean showCollectedLocationCount = false; - - @Expose - @ConfigOption(name = "Warn When Unclaimed", desc = "Warn when all three eggs are ready to be found.") - @ConfigEditorBoolean - @FeatureToggle - public boolean warnUnclaimedEggs = false; - - @Expose - @ConfigOption(name = "Click to Warp", desc = "Make the eggs ready chat message & unclaimed timer display clickable to warp you to an island.") - @ConfigEditorBoolean - public boolean warpUnclaimedEggs = false; - - @Expose - @ConfigOption(name = "Warp Destination", desc = "A custom island to warp to in the above option.") - @ConfigEditorText - public String warpDestination = "nucleus"; - - @Expose - @ConfigOption(name = "Show While Busy", desc = "Show while \"busy\" (in a farming contest, doing Kuudra, in the rift, etc).") - @ConfigEditorBoolean - @FeatureToggle - public boolean showWhileBusy = false; - - @Expose - @ConfigOption(name = "Warn While Busy", desc = "Warn while \"busy\" (in a farming contest, doing Kuudra, in the rift, etc).") - @ConfigEditorBoolean - @FeatureToggle - public boolean warnWhileBusy = false; - - @Expose - @ConfigOption(name = "Show Outside SkyBlock", desc = "Show on Hypixel even when not playing SkyBlock.") - @ConfigEditorBoolean - @FeatureToggle - public boolean showOutsideSkyblock = false; - - @Expose - @ConfigOption(name = "Shared Hoppity Waypoints", desc = "Enable being able to share and receive egg waypoints in your lobby.") - @ConfigEditorBoolean - @FeatureToggle - public boolean sharedWaypoints = true; - - @Expose - @ConfigOption(name = "Adjust player opacity", desc = "Adjust the opacity of players near shared & guessed egg waypoints. (in %)") - @ConfigEditorSlider(minValue = 0, maxValue = 100, minStep = 1) - public int playerOpacity = 40; - - @Expose - @ConfigLink(owner = HoppityEggsConfig.class, field = "showClaimedEggs") - public Position position = new Position(200, 120, false, true); - - @Expose - @ConfigOption(name = "Highlight Hoppity Shop", desc = "Highlight items that haven't been bought from the Hoppity shop yet.") - @ConfigEditorBoolean - @FeatureToggle - public boolean highlightHoppityShop = true; - - @Expose - @ConfigOption(name = "Hoppity Shop Reminder", desc = "Remind you to open the Hoppity Shop each year.") - @ConfigEditorBoolean - @FeatureToggle - public boolean hoppityShopReminder = true; - - @Expose - @ConfigOption(name = "Time in Chat", desc = "When the Egglocator can't find an egg, show the time until the next Hoppity event or egg spawn.") - @ConfigEditorBoolean - @FeatureToggle - public boolean timeInChat = true; - - @Expose - @ConfigOption(name = "Compact Chat", desc = "Compact chat events when finding a Hoppity Egg.") - @ConfigEditorBoolean - @FeatureToggle - public boolean compactChat = false; - - @Expose - @ConfigOption(name = "Compacted Rarity", desc = "Show rarity of found rabbit in Compacted chat messages.") - @ConfigEditorDropdown - public CompactRarityTypes rarityInCompact = CompactRarityTypes.NEW; - - public enum CompactRarityTypes { - NONE("Neither"), - NEW("New Rabbits"), - DUPE("Duplicate Rabbits"), - BOTH("New & Duplicate Rabbits"), - ; - - private final String name; - - CompactRarityTypes(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - - @Expose - @ConfigOption( - name = "Rabbit Pet Warning", - desc = "Warn when using the Egglocator without a §d§lMythic Rabbit Pet §7equipped. " + - "§eOnly enable this setting when you own a mythic Rabbit pet." - ) - @ConfigEditorBoolean - @FeatureToggle - public boolean petWarning = false; -} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEggsConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEggsConfig.java new file mode 100644 index 000000000..50a5731a5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/event/hoppity/HoppityEggsConfig.java @@ -0,0 +1,198 @@ +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; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorText; +import io.github.notenoughupdates.moulconfig.annotations.ConfigLink; +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 + public boolean waypoints = true; + + @Expose + @ConfigOption( + name = "Show Waypoints Immediately", + desc = "Show an estimated waypoint immediately after clicking.\n" + + "§cThis might cause issues with other particle sources." + ) + @ConfigEditorBoolean + public boolean waypointsImmediately = false; + + @Expose + @ConfigOption(name = "Color", desc = "Color of the waypoint.") + @ConfigEditorColour + public String waypointColor = "0:53:46:224:73"; + + @Expose + @ConfigOption(name = "Show Line", desc = "Show a line to the waypoint.") + @ConfigEditorBoolean + @FeatureToggle + public boolean showLine = false; + + @Expose + @ConfigOption(name = "Show All Waypoints", desc = "Show all possible egg waypoints for the current lobby. §e" + + "Only works when you don't have an Egglocator in your inventory.") + @ConfigEditorBoolean + public boolean showAllWaypoints = false; + + @Expose + @ConfigOption(name = "Hide Duplicate Waypoints", desc = "Hide egg waypoints you have already found.\n" + + "§eOnly works when you don't have an Egglocator in your inventory.") + @ConfigEditorBoolean + @FeatureToggle + public boolean hideDuplicateWaypoints = false; + + @Expose + @ConfigOption(name = "Mark Duplicate Locations", desc = "Marks egg location waypoints which you have already found in red.") + @ConfigEditorBoolean + @FeatureToggle + public boolean highlightDuplicateEggLocations = false; + + @Expose + @ConfigOption(name = "Mark Nearby Duplicates", desc = "Always show duplicate egg locations when nearby.") + @ConfigEditorBoolean + @FeatureToggle + public boolean showNearbyDuplicateEggLocations = false; + + @Expose + @ConfigOption(name = "Load from NEU PV", desc = "Load Hoppity Egg Location data from API when opening the NEU Profile Viewer.") + @ConfigEditorBoolean + @FeatureToggle + public boolean loadFromNeuPv = true; + + @Expose + @ConfigOption(name = "Show Unclaimed Eggs", desc = "Display which eggs haven't been found in the last SkyBlock day.") + @ConfigEditorBoolean + @FeatureToggle + public boolean showClaimedEggs = false; + + @Expose + @ConfigOption(name = "Show Collected Locations", desc = "Show the number of found egg locations on this island.\n" + + "§eThis is not retroactive and may not be fully synced with Hypixel's count.") + @ConfigEditorBoolean + @FeatureToggle + public boolean showCollectedLocationCount = false; + + @Expose + @ConfigOption(name = "Warn When Unclaimed", desc = "Warn when all three eggs are ready to be found.") + @ConfigEditorBoolean + @FeatureToggle + public boolean warnUnclaimedEggs = false; + + @Expose + @ConfigOption(name = "Click to Warp", desc = "Make the eggs ready chat message & unclaimed timer display clickable to warp you to an island.") + @ConfigEditorBoolean + public boolean warpUnclaimedEggs = false; + + @Expose + @ConfigOption(name = "Warp Destination", desc = "A custom island to warp to in the above option.") + @ConfigEditorText + public String warpDestination = "nucleus"; + + @Expose + @ConfigOption(name = "Show While Busy", desc = "Show while \"busy\" (in a farming contest, doing Kuudra, in the rift, etc).") + @ConfigEditorBoolean + @FeatureToggle + public boolean showWhileBusy = false; + + @Expose + @ConfigOption(name = "Warn While Busy", desc = "Warn while \"busy\" (in a farming contest, doing Kuudra, in the rift, etc).") + @ConfigEditorBoolean + @FeatureToggle + public boolean warnWhileBusy = false; + + @Expose + @ConfigOption(name = "Show Outside SkyBlock", desc = "Show on Hypixel even when not playing SkyBlock.") + @ConfigEditorBoolean + @FeatureToggle + public boolean showOutsideSkyblock = false; + + @Expose + @ConfigOption(name = "Shared Hoppity Waypoints", desc = "Enable being able to share and receive egg waypoints in your lobby.") + @ConfigEditorBoolean + @FeatureToggle + public boolean sharedWaypoints = true; + + @Expose + @ConfigOption(name = "Adjust player opacity", desc = "Adjust the opacity of players near shared & guessed egg waypoints. (in %)") + @ConfigEditorSlider(minValue = 0, maxValue = 100, minStep = 1) + public int playerOpacity = 40; + + @Expose + @ConfigLink(owner = HoppityEggsConfig.class, field = "showClaimedEggs") + public Position position = new Position(200, 120, false, true); + + @Expose + @ConfigOption(name = "Highlight Hoppity Shop", desc = "Highlight items that haven't been bought from the Hoppity shop yet.") + @ConfigEditorBoolean + @FeatureToggle + public boolean highlightHoppityShop = true; + + @Expose + @ConfigOption(name = "Hoppity Shop Reminder", desc = "Remind you to open the Hoppity Shop each year.") + @ConfigEditorBoolean + @FeatureToggle + public boolean hoppityShopReminder = true; + + @Expose + @ConfigOption(name = "Time in Chat", desc = "When the Egglocator can't find an egg, show the time until the next Hoppity event or egg spawn.") + @ConfigEditorBoolean + @FeatureToggle + public boolean timeInChat = true; + + @Expose + @ConfigOption(name = "Compact Chat", desc = "Compact chat events when finding a Hoppity Egg.") + @ConfigEditorBoolean + @FeatureToggle + public boolean compactChat = false; + + @Expose + @ConfigOption(name = "Compacted Rarity", desc = "Show rarity of found rabbit in Compacted chat messages.") + @ConfigEditorDropdown + public CompactRarityTypes rarityInCompact = CompactRarityTypes.NEW; + + public enum CompactRarityTypes { + NONE("Neither"), + NEW("New Rabbits"), + DUPE("Duplicate Rabbits"), + BOTH("New & Duplicate Rabbits"), + ; + + private final String name; + + CompactRarityTypes(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + + @Expose + @ConfigOption( + name = "Rabbit Pet Warning", + desc = "Warn when using the Egglocator without a §d§lMythic Rabbit Pet §7equipped. " + + "§eOnly enable this setting when you own a mythic Rabbit pet." + ) + @ConfigEditorBoolean + @FeatureToggle + public boolean petWarning = false; +} 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 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 hoppityEventStats = new HashMap<>(); + + public static class HoppityEventStats { + @Expose + public Map mealsFound = new HashMap<>(); + + @Expose + public Map 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() + 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", + "(?:§.)*?(?\\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(?[\\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(?[\\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,12 +38,36 @@ 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 (?\\w+) Egg §r§d(?.*)§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(?.*) §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(?[\\w ]+) Rabbit §r§din the Chocolate Factory§r§d!", + ) + + val eggFoundPatterns: List = 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\\+(?[\\d,]+) Chocolate" + "§7§lDUPLICATE RABBIT! §6\\+(?[\\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 { + 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 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) { + 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, name: String): List { + 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 { -- cgit