diff --git a/CHANGELOG.md b/CHANGELOG.md index 31ade812776b..316325c47d87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ + Added Sea Creature Tracker. - hannibal2 + Allows to only show single variants, e.g. water or lava or winter. -### Changed +### Changes #### Garden Changes @@ -42,6 +42,10 @@ + Odger highlight feature tells in description that it is only useful for users without abiphone. - hannibal2 +#### Mining Changes + ++ Show Powder Tracker immediately after joining the Crystal Hollows. - hannibal2 + #### Bingo Changes + Show the guide text when hovering over the missing bingo goal list. - hannibal2 @@ -50,11 +54,16 @@ + Removed flawless gemstones from sack display. - CalMWolfs + Hypixel removed them from sacks. ++ Show a warning in Bestiary Display when Overall Progress is not enabled. - HiZe #### Dungeon Changes + Changed the description of the Dungeon Chat Filter feature to be more descriptive. - Wambo +#### Chat Changes + ++ Hide new Fire Sale message format. - Thunderblade73 + #### Misc Changes + Tia Relay Helper: Suggest /togglemusic. - alexia @@ -67,8 +76,14 @@ + Fixed progress to maxed milestone appearing twice in the crop milestone menu when having milestone 20. - Empa + Fixed max crop milestone display being too long in the crop milestone menu. - obsidian + Fixed Mooshroom Cow Perk counter when farming sugar cane/cactus with Mooshroom Cow. - alexia -+ Show an error message for the commands /shcropsin and /shcroptime if show money per hour display is not loaded. - hannibal2 ++ Show an error message for the commands /shcropsin and /shcroptime if show money per hour display is not loaded. - + hannibal2 + Auto-fixing plots marked as pests when killing all pests without SkyHanni earlier. - hannibal2 ++ Fixed error message that nearest pests cannot get removed properly. - hannibal2 + +#### Mining Fixes + ++ Fixed an error when showing all elements in Powder Tracker. - hannibal2 #### Rift Fixes @@ -88,10 +103,13 @@ #### Bingo Fixes + Fixed detecting bingo profile while visiting other players bingo island. - hannibal2 ++ Fixed performance issues with Bingo Minion Craft Helper. - hannibal2 ++ Fixed Bingo Minion Craft Helper not detecting crafted tier one minion. - hannibal2 #### Misc Fixes + Maybe fixed Tia Relay Helper. - Thunderblade73 ++ Fixed wording in trackers when the item is newly obtained. - hannibal2 ### Technical Changes @@ -103,7 +121,11 @@ + Added Dark Auction as IslandType and fixed IslandType detection for dungeons. - j10a1n15 + Modify instead of blocking trophy fishing and sea creature chat messages. - appable + Changed regex in case Hypixel changes color codes for island names in the tab list. - Empa -+ Extract FirstMinionTier logic from the Bingo Minion Craft Helper to better analyze the performance problems some users have. - hannibal2 ++ Extract FirstMinionTier logic from the Bingo Minion Craft Helper to better analyze the performance problems some users + have. - hannibal2 ++ Moving minion craft helper fully over to neu internal names. - hannibal2 ++ Added information about trackers to the Discord FAQ. - j10a1n15 ++ Defined the way how dependent PRs should be written in contributing.md. - Thunderblade73 ## Version 0.22 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b439bc15204..67d8e3c0b4c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,9 @@ Please use a prefix for the name of the PR (E.g. Feature, Fix, Backend, Change). You can write in the description of the pr the wording for the changelog as well (optional). +If your PR relies on another PR, please include this information at the beginning of the description. Consider using a +format like "- #821" to illustrate the dependency. + ## Coding Styles and Conventions - Follow the [Hypixel Rules](https://hypixel.net/rules). diff --git a/build.gradle.kts b/build.gradle.kts index 2888727dbac9..79db1f6c6a66 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ plugins { } group = "at.hannibal2.skyhanni" -version = "0.23.Beta.4" +version = "0.23.Beta.5" val gitHash by lazy { val baos = ByteArrayOutputStream() diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 51ff0bd62271..df46f6194979 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -8,6 +8,7 @@ import at.hannibal2.skyhanni.config.Features import at.hannibal2.skyhanni.config.SackData import at.hannibal2.skyhanni.config.commands.Commands.init import at.hannibal2.skyhanni.data.ActionBarStatsData +import at.hannibal2.skyhanni.data.BitsAPI import at.hannibal2.skyhanni.data.BlockData import at.hannibal2.skyhanni.data.ChatManager import at.hannibal2.skyhanni.data.CropAccessoryData @@ -26,13 +27,15 @@ import at.hannibal2.skyhanni.data.ItemClickData import at.hannibal2.skyhanni.data.ItemRenderBackground import at.hannibal2.skyhanni.data.ItemTipHelper import at.hannibal2.skyhanni.data.LocationFixData -import at.hannibal2.skyhanni.data.MayorElection +import at.hannibal2.skyhanni.data.MaxwellAPI +import at.hannibal2.skyhanni.data.MayorAPI import at.hannibal2.skyhanni.data.MinecraftData import at.hannibal2.skyhanni.data.OtherInventoryData import at.hannibal2.skyhanni.data.OwnInventoryData import at.hannibal2.skyhanni.data.PartyAPI import at.hannibal2.skyhanni.data.ProfileStorageData import at.hannibal2.skyhanni.data.PurseAPI +import at.hannibal2.skyhanni.data.QuiverAPI import at.hannibal2.skyhanni.data.RenderData import at.hannibal2.skyhanni.data.SackAPI import at.hannibal2.skyhanni.data.ScoreboardData @@ -257,6 +260,8 @@ import at.hannibal2.skyhanni.features.misc.TpsCounter import at.hannibal2.skyhanni.features.misc.compacttablist.AdvancedPlayerList import at.hannibal2.skyhanni.features.misc.compacttablist.TabListReader import at.hannibal2.skyhanni.features.misc.compacttablist.TabListRenderer +import at.hannibal2.skyhanni.features.misc.customscoreboard.CustomScoreboard +import at.hannibal2.skyhanni.features.misc.customscoreboard.ScoreboardPattern import at.hannibal2.skyhanni.features.misc.discordrpc.DiscordRPCManager import at.hannibal2.skyhanni.features.misc.items.AuctionHouseCopyUnderbidPrice import at.hannibal2.skyhanni.features.misc.items.EstimatedItemValue @@ -340,6 +345,7 @@ import at.hannibal2.skyhanni.utils.EntityOutlineRenderer import at.hannibal2.skyhanni.utils.KeyboardManager import at.hannibal2.skyhanni.utils.MinecraftConsoleFilter.Companion.initLogging import at.hannibal2.skyhanni.utils.NEUVersionCheck.checkIfNeuIsLoaded +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils import at.hannibal2.skyhanni.utils.TabListData import at.hannibal2.skyhanni.utils.repopatterns.RepoPatternManager import kotlinx.coroutines.CoroutineName @@ -363,7 +369,7 @@ import org.apache.logging.log4j.Logger clientSideOnly = true, useMetadata = true, guiFactory = "at.hannibal2.skyhanni.config.ConfigGuiForgeInterop", - version = "0.23.Beta.4", + version = "0.23.Beta.5", ) class SkyHanniMod { @Mod.EventHandler @@ -403,7 +409,6 @@ class SkyHanniMod { loadModule(GuiEditManager()) loadModule(UpdateManager) loadModule(CropAccessoryData()) - loadModule(MayorElection()) loadModule(GardenComposterUpgradesData()) loadModule(ActionBarStatsData) loadModule(GardenCropMilestoneInventory()) @@ -420,6 +425,8 @@ class SkyHanniMod { loadModule(BingoCardReader()) loadModule(GardenBestCropTime()) loadModule(TrackerManager) + loadModule(SkyBlockItemModifierUtils) + loadModule(ScoreboardPattern) // APIs loadModule(BazaarApi()) @@ -432,11 +439,15 @@ class SkyHanniMod { loadModule(PartyAPI) loadModule(GuildAPI) loadModule(SlayerAPI) - loadModule(PurseAPI()) + loadModule(PurseAPI) loadModule(RiftAPI) loadModule(SackAPI) loadModule(BingoAPI) loadModule(FishingAPI) + loadModule(MaxwellAPI) + loadModule(QuiverAPI) + loadModule(BitsAPI) + loadModule(MayorAPI) // features loadModule(BazaarOrderHelper()) @@ -682,6 +693,7 @@ class SkyHanniMod { loadModule(DungeonFinderFeatures()) loadModule(PabloHelper()) loadModule(FishingBaitWarnings()) + loadModule(CustomScoreboard()) loadModule(RepoPatternManager) loadModule(PestSpawn()) loadModule(PestSpawnTimer) diff --git a/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt b/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt index e73f63813a93..c2701c61b9ec 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/ConfigUpdaterMigrator.kt @@ -9,7 +9,7 @@ import com.google.gson.JsonPrimitive object ConfigUpdaterMigrator { val logger = LorenzLogger("ConfigMigration") - const val CONFIG_VERSION = 18 + const val CONFIG_VERSION = 22 fun JsonElement.at(chain: List, init: Boolean): JsonElement? { if (chain.isEmpty()) return this if (this !is JsonObject) return null diff --git a/src/main/java/at/hannibal2/skyhanni/config/Storage.java b/src/main/java/at/hannibal2/skyhanni/config/Storage.java index 6e899692ca77..ea6a4fc4e694 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/Storage.java +++ b/src/main/java/at/hannibal2/skyhanni/config/Storage.java @@ -1,5 +1,8 @@ package at.hannibal2.skyhanni.config; +import at.hannibal2.skyhanni.data.FameRank; +import at.hannibal2.skyhanni.data.MaxwellPowers; +import at.hannibal2.skyhanni.data.QuiverArrowType; import at.hannibal2.skyhanni.data.model.ComposterUpgrade; import at.hannibal2.skyhanni.features.bingo.card.goals.BingoGoal; import at.hannibal2.skyhanni.features.combat.endernodetracker.EnderNodeTracker; @@ -118,6 +121,42 @@ public static class ProfileSpecific { @Expose public String currentPet = ""; + @Expose + public MaxwellPowerStorage maxwell = new MaxwellPowerStorage(); + + public static class MaxwellPowerStorage { + @Expose + public MaxwellPowers currentPower = null; + + @Expose + public int magicalPower = -1; + } + + @Expose + public ArrowsStorage arrows = new ArrowsStorage(); + + public static class ArrowsStorage { + @Expose + public QuiverArrowType currentArrow = null; + + @Expose + public Map arrowAmount = new HashMap<>(); + } + + @Expose + public BitsStorage bits = new BitsStorage(); + + public static class BitsStorage { + @Expose + public int bits = -1; + + @Expose + public FameRank currentFameRank = null; + + @Expose + public int bitsToClaim = -1; + } + @Expose public Map minions = new HashMap<>(); 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 f958591c196d..2e962909944a 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt @@ -51,6 +51,7 @@ import at.hannibal2.skyhanni.test.PacketTest import at.hannibal2.skyhanni.test.SkyHanniConfigSearchResetCommand import at.hannibal2.skyhanni.test.SkyHanniDebugsAndTests import at.hannibal2.skyhanni.test.TestBingo +import at.hannibal2.skyhanni.test.command.CopyActionBar import at.hannibal2.skyhanni.test.WorldEdit import at.hannibal2.skyhanni.test.command.CopyItemCommand import at.hannibal2.skyhanni.test.command.CopyNearbyEntitiesCommand @@ -375,6 +376,10 @@ object Commands { "shconfigmanagerreset", "Reloads the config manager and rendering processors of MoulConfig. This §cWILL RESET §7your config, but also updating the java config files (names, description, orderings and stuff)." ) { SkyHanniDebugsAndTests.configManagerResetCommand(it) } + registerCommand( + "shcopyactionbar", + "Copies the actionbar to the clipboard" + ) { CopyActionBar.command(it) } registerCommand( "readcropmilestonefromclipboard", "Read crop milestone from clipboard. This helps fixing wrong crop milestone data" diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java index 89170c027faa..3b5200c39c89 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java @@ -44,6 +44,11 @@ public class DevConfig { @ConfigEditorBoolean public boolean worldEdit = false; + @Expose + @ConfigOption(name = "Bow Sound distance", desc = "The distance in blocks where the sound of shooting a bow will be used for the QuiverAPI.") + @ConfigEditorSlider(minValue = 0, maxValue = 50, minStep = 1) + public int bowSoundDistance = 5; + @ConfigOption(name = "Parkour Waypoints", desc = "") @Accordion @Expose diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/GUIConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/GUIConfig.java index 4eeadf221fad..36b4d055d0cb 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/gui/GUIConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/GUIConfig.java @@ -2,9 +2,11 @@ import at.hannibal2.skyhanni.config.FeatureToggle; import at.hannibal2.skyhanni.config.core.config.Position; +import at.hannibal2.skyhanni.config.features.gui.customscoreboard.CustomScoreboardConfig; import at.hannibal2.skyhanni.data.GuiEditManager; import com.google.gson.annotations.Expose; import io.github.moulberry.moulconfig.annotations.Accordion; +import io.github.moulberry.moulconfig.annotations.Category; import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; import io.github.moulberry.moulconfig.annotations.ConfigEditorButton; import io.github.moulberry.moulconfig.annotations.ConfigEditorKeybind; @@ -28,6 +30,11 @@ public class GUIConfig { @ConfigEditorSlider(minValue = 0.1F, maxValue = 10, minStep = 0.05F) public float globalScale = 1F; + + @Expose + @Category(name = "Custom Scoreboard", desc = "Custom Scoreboard Settings") + public CustomScoreboardConfig customScoreboard = new CustomScoreboardConfig(); + @Expose @ConfigOption(name = "Modify Visual Words", desc = "") @Accordion diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/AlignmentConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/AlignmentConfig.java new file mode 100644 index 000000000000..2e5796f95f49 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/AlignmentConfig.java @@ -0,0 +1,17 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +public class AlignmentConfig { + @Expose + @ConfigOption(name = "Align to the right", desc = "Align the scoreboard to the right side of the screen.") + @ConfigEditorBoolean + public boolean alignRight = false; + + @Expose + @ConfigOption(name = "Align to the center vertically", desc = "Align the scoreboard to the center of the screen vertically.") + @ConfigEditorBoolean + public boolean alignCenterVertically = false; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/BackgroundConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/BackgroundConfig.java new file mode 100644 index 000000000000..382d242025b0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/BackgroundConfig.java @@ -0,0 +1,41 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigEditorColour; +import io.github.moulberry.moulconfig.annotations.ConfigEditorInfoText; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +public class BackgroundConfig { + @Expose + @ConfigOption( + name = "Enabled", + desc = "Show a background behind the scoreboard." + ) + @ConfigEditorBoolean + public boolean enabled = true; + + @Expose + @ConfigOption( + name = "Background Color", + desc = "The color of the background." + ) + @ConfigEditorColour + public String color = "0:102:0:0:0"; + + @Expose + @ConfigOption( + name = "Use Custom Background Image", + desc = "Put that image into a resource pack, using the path \"skyhanni/scoreboard.png\"." + ) + @ConfigEditorBoolean + public boolean useCustomBackgroundImage = false; + + @Expose + @ConfigOption( + name = "Custom Background", + desc = "Add an image named \"scoreboard.png\" to your texture pack at \"\\assets\\skyhanni\\scoreboard.png.\" Activate the texture pack in Minecraft, then reload the game." + ) + @ConfigEditorInfoText + public String useless; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/CustomScoreboardConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/CustomScoreboardConfig.java new file mode 100644 index 000000000000..a5d556d75e60 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/CustomScoreboardConfig.java @@ -0,0 +1,66 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import at.hannibal2.skyhanni.config.core.config.Position; +import at.hannibal2.skyhanni.features.misc.customscoreboard.ScoreboardElements; +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.Accordion; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigEditorDraggableList; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class CustomScoreboardConfig { + @Expose + @ConfigOption( + name = "Enabled", + desc = "Show a custom scoreboard instead of the vanilla one." + ) + @ConfigEditorBoolean + @FeatureToggle + public boolean enabled = false; + + @Expose + @ConfigOption( + name = "Appearance", + desc = "Drag text to change the appearance of the advanced scoreboard." // supporting both custom & advanced search + ) + @ConfigEditorDraggableList() + public List scoreboardEntries = new ArrayList<>(Arrays.asList(ScoreboardElements.values())); + + @Expose + @ConfigOption(name = "Display Options", desc = "") + @Accordion + public DisplayConfig displayConfig = new DisplayConfig(); + + @Expose + @ConfigOption(name = "Information Filtering", desc = "") + @Accordion + public InformationFilteringConfig informationFilteringConfig = new InformationFilteringConfig(); + + @Expose + @ConfigOption(name = "Background Options", desc = "") + @Accordion + public BackgroundConfig backgroundConfig = new BackgroundConfig(); + + @Expose + @ConfigOption(name = "Party Options", desc = "") + @Accordion + public PartyConfig partyConfig = new PartyConfig(); + + @Expose + @ConfigOption(name = "Mayor Options", desc = "") + @Accordion + public MayorConfig mayorConfig = new MayorConfig(); + + @Expose + @ConfigOption(name = "Unknown Lines warning", desc = "Gives a chat warning when unknown lines are found in the scoreboard.") + @ConfigEditorBoolean + public boolean unknownLinesWarning = true; + + @Expose + public Position position = new Position(10, 80, false, true); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/DisplayConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/DisplayConfig.java new file mode 100644 index 000000000000..c8c42dc9f504 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/DisplayConfig.java @@ -0,0 +1,69 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.Accordion; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigEditorDropdown; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +public class DisplayConfig { + @Expose + @ConfigOption(name = "Hide Vanilla Scoreboard", desc = "Hide the vanilla scoreboard.") + @ConfigEditorBoolean + @FeatureToggle + public boolean hideVanillaScoreboard = false; + + @Expose + @ConfigOption(name = "Display Numbers First", desc = "Determines whether the number or line name displays first. " + + "§eNote: Will not update the preview above!") + @ConfigEditorBoolean + public boolean displayNumbersFirst = false; + + @Expose + @ConfigOption(name = "Show unclaimed bits", desc = "Show the amount of available Bits that can still be claimed.") + @ConfigEditorBoolean + public boolean showUnclaimedBits = false; + + @Expose + @ConfigOption(name = "Show all active events", desc = "Show all active events in the scoreboard instead of one.") + @ConfigEditorBoolean + public boolean showAllActiveEvents = false; + + @Expose + @ConfigOption(name = "Cache Scoreboard on Island Switch", + desc = "Will stop the Scoreboard from updating while switching islands.\nRemoves the shaking when loading data.") + @ConfigEditorBoolean + public boolean cacheScoreboardOnIslandSwitch = false; + + @Expose + @ConfigOption(name = "Number Format", desc = "") + @ConfigEditorDropdown + public NumberFormat numberFormat = NumberFormat.LONG; + + public enum NumberFormat { + LONG("1,234,567"), + SHORT("1.2M"); + + private final String str; + + NumberFormat(String str) { + this.str = str; + } + + @Override + public String toString() { + return str; + } + } + + @Expose + @ConfigOption(name = "Alignment Options", desc = "") + @Accordion + public AlignmentConfig alignment = new AlignmentConfig(); + + @Expose + @ConfigOption(name = "Title and Footer Options", desc = "") + @Accordion + public TitleAndFooterConfig titleAndFooter = new TitleAndFooterConfig(); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/InformationFilteringConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/InformationFilteringConfig.java new file mode 100644 index 000000000000..02a8f83a056b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/InformationFilteringConfig.java @@ -0,0 +1,23 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +public class InformationFilteringConfig { + @Expose + @ConfigOption(name = "Hide lines with no info", desc = "Hide lines that have no info to display, like hiding the party when not being in one.") + @ConfigEditorBoolean + public boolean hideEmptyLines = true; + + @Expose + @ConfigOption(name = "Hide consecutive empty lines", desc = "Hide lines that are empty and have an empty line above them.") + @ConfigEditorBoolean + public boolean hideConsecutiveEmptyLines = true; + + @Expose + @ConfigOption(name = "Hide non relevant info", desc = "Hide lines that are not relevant to the current location." + + "\n§cIt's generally not recommended to turn this off.") + @ConfigEditorBoolean + public boolean hideIrrelevantLines = true; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/MayorConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/MayorConfig.java new file mode 100644 index 000000000000..5a47e5a48123 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/MayorConfig.java @@ -0,0 +1,17 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +public class MayorConfig { + @Expose + @ConfigOption(name = "Show Mayor Perks", desc = "Show the perks of the current mayor.") + @ConfigEditorBoolean + public boolean showMayorPerks = true; + + @Expose + @ConfigOption(name = "Show Time till next mayor", desc = "Show the time till the next mayor is elected.") + @ConfigEditorBoolean + public boolean showTimeTillNextMayor = true; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/PartyConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/PartyConfig.java new file mode 100644 index 000000000000..52cc90d54251 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/PartyConfig.java @@ -0,0 +1,24 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigEditorSlider; +import io.github.moulberry.moulconfig.annotations.ConfigOption; +import io.github.moulberry.moulconfig.observer.Property; + +public class PartyConfig { + @Expose + @ConfigOption(name = "Max Party List", desc = "Max number of party members to show in the party list. (You are not included)") + @ConfigEditorSlider( + minValue = 0, + maxValue = 25, // why do I even set it so high + minStep = 1 + ) + public Property maxPartyList = Property.of(4); + + @Expose + @ConfigOption(name = "Show Party everywhere", desc = "Show the party list everywhere.\nIf disabled, it will only show in Dungeon hub, Crimson Isle & Kuudra") + @ConfigEditorBoolean + public boolean showPartyEverywhere = false; +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/TitleAndFooterConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/TitleAndFooterConfig.java new file mode 100644 index 000000000000..ee0fd8c1ea20 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/TitleAndFooterConfig.java @@ -0,0 +1,29 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigEditorText; +import io.github.moulberry.moulconfig.annotations.ConfigOption; +import io.github.moulberry.moulconfig.observer.Property; + +public class TitleAndFooterConfig { + @Expose + @ConfigOption(name = "Center Title and Footer", desc = "Center the title and footer to the scoreboard width.") + @ConfigEditorBoolean + public boolean centerTitleAndFooter = false; + + @Expose + @ConfigOption(name = "Custom Title", desc = "What should be displayed as the title of the scoreboard.\nUse & for colors.") + @ConfigEditorText + public Property customTitle = Property.of("&6&lSKYBLOCK"); + + @Expose + @ConfigOption(name = "Use Hypixel's Title Animation", desc = "Will overwrite the custom title with Hypixel's title animation.") + @ConfigEditorBoolean + public boolean useHypixelTitleAnimation = false; + + @Expose + @ConfigOption(name = "Custom Footer", desc = "What should be displayed as the footer of the scoreboard.\nUse & for colors.") + @ConfigEditorText + public Property customFooter = Property.of("&ewww.hypixel.net"); +} diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/mining/PowderTrackerConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/mining/PowderTrackerConfig.java index 1e658c5a2ffd..660c9493c0ac 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/mining/PowderTrackerConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/mining/PowderTrackerConfig.java @@ -16,7 +16,6 @@ import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.AMBER; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.AMETHYST; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.DIAMOND_ESSENCE; -import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.DISPLAY_MODE; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.DOUBLE_POWDER; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.ELECTRON; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.FTX; @@ -29,7 +28,6 @@ import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.SAPPHIRE; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.SPACER_1; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.SPACER_2; -import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.TITLE; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.TOPAZ; import static at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDisplayEntry.TOTAL_CHESTS; @@ -58,8 +56,6 @@ public class PowderTrackerConfig { ) @ConfigEditorDraggableList() public Property> textFormat = Property.of(new ArrayList<>(Arrays.asList( - TITLE, - DISPLAY_MODE, TOTAL_CHESTS, DOUBLE_POWDER, MITHRIL_POWDER, @@ -80,8 +76,6 @@ public class PowderTrackerConfig { ))); public enum PowderDisplayEntry implements HasLegacyId { - TITLE("§b§lPowder Tracker", 0), - DISPLAY_MODE("§7Display Mode: §a[Total] §e[This Session]", 1), TOTAL_CHESTS("§d852 Total chests Picked §7(950/h)", 2), DOUBLE_POWDER("§bx2 Powder: §aActive!", 3), MITHRIL_POWDER("§b250,420 §aMithril Powder §7(350,000/h)", 4), diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java index 53a70019b125..a1acdfbdfb36 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java @@ -20,7 +20,7 @@ public class MiscConfig { @ConfigOption(name = "Hide Armor", desc = "") @Accordion @Expose - // TOOD maybe we can migrate this already + // TODO maybe we can migrate this already public HideArmorConfig hideArmor2 = new HideArmorConfig(); @Expose @@ -93,6 +93,11 @@ public class MiscConfig { @Accordion public TrackerConfig tracker = new TrackerConfig(); + @Expose + @ConfigOption(name = "Pet Candy Display", desc = "") + @Accordion + public PetCandyDisplayConfig petCandy = new PetCandyDisplayConfig(); + @Expose @ConfigOption(name = "Exp Bottles", desc = "Hides all the experience orbs lying on the ground.") @ConfigEditorBoolean @@ -120,6 +125,12 @@ public class MiscConfig { @FeatureToggle public boolean hidePiggyScoreboard = true; + @Expose + @ConfigOption(name = "Color Month Names", desc = "Color the month names in the Scoreboard.\nAlso applies to the Custom Scoreboard") + @ConfigEditorBoolean + @FeatureToggle + public boolean colorMonthNames = false; + @Expose @ConfigOption(name = "Explosions Hider", desc = "Hide explosions.") @ConfigEditorBoolean @@ -153,12 +164,6 @@ public class MiscConfig { @Expose public Position playerMovementSpeedPos = new Position(394, 124, false, true); - @Expose - @ConfigOption(name = "Pet Candy Used", desc = "Show the number of Pet Candy used on a pet.") - @ConfigEditorBoolean - @FeatureToggle - public boolean petCandyUsed = true; - @Expose @ConfigOption(name = "Server Restart Title", desc = "Show a title with seconds remaining until the server restarts after a Game Update or Scheduled Restart.") @ConfigEditorBoolean diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/misc/PetCandyDisplayConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/misc/PetCandyDisplayConfig.java new file mode 100644 index 000000000000..57d97459b24f --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/misc/PetCandyDisplayConfig.java @@ -0,0 +1,20 @@ +package at.hannibal2.skyhanni.config.features.misc; + +import at.hannibal2.skyhanni.config.FeatureToggle; +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigOption; + +public class PetCandyDisplayConfig { + @Expose + @ConfigOption(name = "Pet Candy Used", desc = "Show the number of Pet Candy used on a pet.") + @ConfigEditorBoolean + @FeatureToggle + public boolean showCandy = true; + + @Expose + @ConfigOption(name = "Hide On Maxed", desc = "Hides the candy count on pets that are max level.") + @ConfigEditorBoolean + @FeatureToggle + public boolean hideOnMaxed = false; +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/ActionBarStatsData.kt b/src/main/java/at/hannibal2/skyhanni/data/ActionBarStatsData.kt index cddf83d0bdcb..91aeda7449c5 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/ActionBarStatsData.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/ActionBarStatsData.kt @@ -15,11 +15,14 @@ object ActionBarStatsData { ) var groups = mutableMapOf("health" to "", "riftTime" to "", "defense" to "", "mana" to "") + var actionBar = "" @SubscribeEvent fun onActionBar(event: LorenzActionBarEvent) { if (!LorenzUtils.inSkyBlock) return + actionBar = event.message + for ((groupName, pattern) in patterns) { pattern.matchMatcher(event.message) { groups[groupName] = group(groupName) diff --git a/src/main/java/at/hannibal2/skyhanni/data/BitsAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/BitsAPI.kt new file mode 100644 index 000000000000..179da394e147 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/BitsAPI.kt @@ -0,0 +1,147 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.events.ConfigLoadEvent +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.ProfileJoinEvent +import at.hannibal2.skyhanni.events.ScoreboardChangeEvent +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpaceAndResets +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object BitsAPI { + var bits = 0 + var currentFameRank = FameRank.NEW_PLAYER + var bitsToClaim = 0 + + private const val defaultcookiebits = 4800 + + private val group = RepoPattern.group("data.bits") + val bitsScoreboardPattern by group.pattern( + "scoreboard.bits", + "^Bits: §b(?(,?\\d{1,3})*)(.\\d+)?( ?(§?3?(\\((?[+-](,?\\d)*)?\\))?)?)?$" + ) + private val bitsFromFameRankUpChatPattern by group.pattern( + "chat.bitsfromfamerankup", + "§eYou gained §3(?.*) Bits Available §ecompounded from all your §epreviously eaten §6cookies§e! Click here to open §6cookie menu§e!" + ) + private val bitsEarnedChatPattern by group.pattern("chat.earned", "§f\\s+§8\\+§b(?.*)\\s+Bits\n") + private val boosterCookieAte by group.pattern("chat.boostercookieate", "§eYou consumed a §6Booster Cookie§e! §d.*") + private val bitsAvailableMenu by group.pattern( + "gui.bitsavailablemenu", + "§7Bits Available: §b(?[\\w,]+)(§3.+)?" + ) + private val fameRankSbmenu by group.pattern("gui.fameranksbmenu", "§7Your rank: §e(?.*)") + private val fameRankCommunityShop by group.pattern("gui.famerankcommunityshop", "§7Fame Rank: §e(?.*)") + + @SubscribeEvent + fun onScoreboardChange(event: ScoreboardChangeEvent) { + for (line in event.newList) { + val message = line.trimWhiteSpaceAndResets().removeResets() + + bitsScoreboardPattern.matchMatcher(message) { + val amount = group("amount").formatNumber().toInt() + val earned = group("earned")?.formatNumber()?.toInt() ?: 0 + bits = amount + if (earned > 0) bitsToClaim -= earned + + save() + } + } + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + val message = event.message.trimWhiteSpaceAndResets().removeResets() + + bitsFromFameRankUpChatPattern.matchMatcher(message) { + val amount = group("amount").formatNumber().toInt() + bitsToClaim += amount + + save() + } + + bitsEarnedChatPattern.matchMatcher(message) { + // Only two locations where the bits line isn't shown, but you can still get bits + if (!listOf(IslandType.CATACOMBS, IslandType.THE_RIFT).contains(HypixelData.skyBlockIsland)) return + + val amount = group("amount").formatNumber().toInt() + bits += amount + bitsToClaim -= amount + + save() + } + + boosterCookieAte.matchMatcher(message) { + bitsToClaim += (defaultcookiebits * currentFameRank.bitsMultiplier).toInt() + + save() + } + } + + @SubscribeEvent + fun onInventoryFullyLoaded(event: InventoryFullyOpenedEvent) { + if (!LorenzUtils.inSkyBlock) return + val allowedNames = listOf("SkyBlock Menu", "Booster Cookie", "Community Shop") + if (!allowedNames.contains(event.inventoryName)) return + + val stacks = event.inventoryItems + for (stack in stacks.values) { + val lore = stack.getLore() + if (lore.isEmpty()) continue + + for (line in lore) { + bitsAvailableMenu.matchMatcher(line) { + val toClaim = group("toClaim").formatNumber().toInt() + bitsToClaim = toClaim + + save() + } + + fameRankSbmenu.matchMatcher(line) { + val rank = group("rank") + currentFameRank = FameRank.entries.firstOrNull { it.rank == rank } ?: FameRank.NEW_PLAYER + + save() + } + + fameRankCommunityShop.matchMatcher(line) { + val rank = group("rank") + currentFameRank = FameRank.entries.firstOrNull { it.rank == rank } ?: FameRank.NEW_PLAYER + + save() + } + } + } + } + + + // Handle Storage data + @SubscribeEvent + fun onConfigLoad(event: ConfigLoadEvent) { + val config = ProfileStorageData.profileSpecific ?: return + bits = config.bits.bits + currentFameRank = config.bits.currentFameRank + bitsToClaim = config.bits.bitsToClaim + } + + @SubscribeEvent + fun onProfileJoin(event: ProfileJoinEvent) { + val config = ProfileStorageData.profileSpecific ?: return + config.bits.bits = bits + config.bits.currentFameRank = currentFameRank + config.bits.bitsToClaim = bitsToClaim + } + + private fun save() { + val config = ProfileStorageData.profileSpecific ?: return + config.bits.bits = bits + config.bits.currentFameRank = currentFameRank + config.bits.bitsToClaim = bitsToClaim + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/FameRank.kt b/src/main/java/at/hannibal2/skyhanni/data/FameRank.kt new file mode 100644 index 000000000000..2f1fd94e91fc --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/FameRank.kt @@ -0,0 +1,26 @@ +package at.hannibal2.skyhanni.data + +enum class FameRank( + val rank: String, + val fameRequired: Long, + val bitsMultiplier: Double, + val electionVotes: Int, +) { + NEW_PLAYER("New Player", 0, 1.0, 1), + SETTLER("Settler", 20_000, 1.1, 1), + CITIZEN("Citizen", 80_000, 1.2, 2), + CONTRIBUTOR("Contributor", 200_000, 1.3, 3), + PHILANTHROPIST("Philanthropist", 40_0000, 1.4, 5), + PATRON("Patron", 800_000, 1.6, 10), + FAMOUS_PLAYER("Famous Player", 1_500_000, 1.8, 20), + ATTACHE("Attaché", 3_000_000, 1.9, 25), + AMBASSADOR("Ambassador", 10_000_000, 2.0, 50), + STATESPERSON("Statesperson", 20_000_000, 2.04, 75), + SENATOR("Senator", 33_000_000, 2.08, 100), + DIGNITARY("Dignitary", 50_000_000, 2.12, 100), + COUNCILOR("Councilor", 72_000_000, 2.16, 100), + MINISTER("Minister", 100_000_000, 2.2, 100), + PREMIER("Premier", 135_000_000, 2.22, 100), + CHANCELLOR("Chancellor", 178_000_000, 2.24, 100), + SUPREME("Supreme", 230_000_000, 2.26, 100) +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/IslandType.kt b/src/main/java/at/hannibal2/skyhanni/data/IslandType.kt index aa942f5bc651..a3eef5dd9df5 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/IslandType.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/IslandType.kt @@ -5,7 +5,7 @@ enum class IslandType(val displayName: String, val modeName: String = "null") { PRIVATE_ISLAND("Private Island"), PRIVATE_ISLAND_GUEST("Private Island Guest"), THE_END("The End"), - KUUDRA_ARENA("Instanced"), + KUUDRA_ARENA("Kuudra"), CRIMSON_ISLE("Crimson Isle"), DWARVEN_MINES("Dwarven Mines"), DUNGEON_HUB("Dungeon Hub", "dungeon_hub"), diff --git a/src/main/java/at/hannibal2/skyhanni/data/MaxwellAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/MaxwellAPI.kt new file mode 100644 index 000000000000..fe1669b4670c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/MaxwellAPI.kt @@ -0,0 +1,111 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.events.ConfigLoadEvent +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.ProfileJoinEvent +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpaceAndResets +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object MaxwellAPI { + val config = ProfileStorageData.profileSpecific + var currentPower: MaxwellPowers? = null + var magicalPower = -1 + + private val group = RepoPattern.group("data.maxwell") + private val chatPowerpattern by group.pattern( + "chat.power", + "§eYou selected the §a(?.*) §e(power )?for your §aAccessory Bag§e!" + ) + private val inventoryPowerPattern by group.pattern( + "inventory.power", + "§7Selected Power: §a(?.*)" + ) + private val inventoryMPPattern by group.pattern( + "inventory.mp", + "§7Magical Power: §6(?[\\d,]+)" + ) + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + val message = event.message.trimWhiteSpaceAndResets().removeResets() + + chatPowerpattern.matchMatcher(message) { + val power = group("power") + currentPower = byNameOrNull(power) ?: return + savePower(currentPower) + } + } + + @SubscribeEvent + fun onInventoryFullyLoaded(event: InventoryFullyOpenedEvent) { + if (!LorenzUtils.inSkyBlock) return + + if (event.inventoryName.contains("Accessory Bag Thaumaturgyr")) { + val stacks = event.inventoryItems + val selectedPower = + stacks.values.find { it.getLore().isNotEmpty() && it.getLore().last() == "§aPower is selected!" } + ?: return + + currentPower = byNameOrNull(selectedPower.getLore().first()) ?: return + savePower(currentPower) + + } else if (event.inventoryName.contains("Your Bags")) { + val stacks = event.inventoryItems + + for (stack in stacks.values) { + val lore = stack.getLore() + for (line in lore) { + inventoryPowerPattern.matchMatcher(line) { + val power = group("power") + currentPower = byNameOrNull(power) ?: return + savePower(currentPower) + } + inventoryMPPattern.matchMatcher(line) { + // Mp is boosted in catacombs + if (IslandType.CATACOMBS.isInIsland()) return + + val mp = group("mp") + magicalPower = mp.replace(",", "").toIntOrNull() ?: return + saveMP(magicalPower) + } + } + } + } + } + + fun byNameOrNull(name: String) = MaxwellPowers.entries.find { it.power == name } + + // Handle storage + @SubscribeEvent + fun onConfigLoad(event: ConfigLoadEvent) { + val config = ProfileStorageData.profileSpecific ?: return + currentPower = config.maxwell.currentPower + magicalPower = config.maxwell.magicalPower + } + + @SubscribeEvent + fun onProfileJoin(event: ProfileJoinEvent) { + val config = ProfileStorageData.profileSpecific ?: return + currentPower = config.maxwell.currentPower ?: null + magicalPower = config.maxwell.magicalPower + } + + private fun savePower(power: MaxwellPowers?) { + if (power == null) return + val config = ProfileStorageData.profileSpecific ?: return + config.maxwell.currentPower = power + } + + private fun saveMP(mp: Int?) { + if (mp == null) return + val config = ProfileStorageData.profileSpecific ?: return + config.maxwell.magicalPower = mp + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/MaxwellPowers.kt b/src/main/java/at/hannibal2/skyhanni/data/MaxwellPowers.kt new file mode 100644 index 000000000000..bd6fefa484ed --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/MaxwellPowers.kt @@ -0,0 +1,41 @@ +package at.hannibal2.skyhanni.data + +enum class MaxwellPowers(val power: String) { + // Standard + NO_POWER("No Power"), + FORTUITOUS("Fortuitous"), + PRETTY("Pretty"), + PROTECTED("Protected"), + SIMPLE("Simple"), + WARRIOR("Warrior"), + COMMANDO("Commando"), + DISCIPLINED("Disciplined"), + INSPIRED("Inspired"), + OMINOUS("Ominous"), + PREPARED("Prepared"), + + // Unlockable + SILKY("Silky"), + SWEET("Sweet"), + BLOODY("Bloody"), + ITCHY("Itchy"), + SIGHTED("Sighted"), + ADEPT("Adept"), + MYTHICAL("Mythical"), + FORCEFUL("Forceful"), + SHADED("Shaded"), + STRONG("Strong"), + DEMONIC("Demonic"), + PLEASANT("Pleasant"), + HURTFUL("Hurtful"), + BIZARRE("Bizarre"), + HEALTHY("Healthy"), + SLENDER("Slender"), + SCORCHING("Scorching"), + CRUMBLY("Crumbly"), + BUBBA("Bubba"), + SANGUISUGE("Sanguisuge"), + + UNKNOWN("Unknown"), + ; +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/MayorElection.kt b/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt similarity index 53% rename from src/main/java/at/hannibal2/skyhanni/data/MayorElection.kt rename to src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt index 231f059d1c76..6e700548b28e 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/MayorElection.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt @@ -13,30 +13,67 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -class MayorElection { +object MayorAPI { private var lastUpdate = 0L private var dispatcher = Dispatchers.IO - companion object { - var rawMayorData: MayorJson? = null - var candidates = mapOf() - var currentCandidate: MayorJson.Candidate? = null + var rawMayorData: MayorJson? = null + var candidates = mapOf() + var currentMayor: MayorJson.Candidate? = null + var timeTillNextMayor = 0L - fun isPerkActive(mayor: String, perk: String) = currentCandidate?.let { currentCandidate -> - currentCandidate.name == mayor && currentCandidate.perks.any { it.name == perk } - } ?: false - } + fun isPerkActive(mayor: String, perk: String) = currentMayor?.let { currentCandidate -> + currentCandidate.name == mayor && currentCandidate.perks.any { it.name == perk } + } ?: false + + /** + * @param input: The name of the mayor + * @return: The neu color of the mayor + the name of the mayor; If no mayor was found, it will return "§cUnknown Mayor: §7$input" + */ + fun mayorNameToColorCode(input: String): String { + return when (input) { + // Normal Mayors + "Aatrox" -> "§3$input" + "Cole" -> "§e$input" + "Diana" -> "§2$input" + "Diaz" -> "§6$input" + "Finnegan" -> "§c$input" + "Foxy" -> "§d$input" + "Marina" -> "§b$input" + "Paul" -> "§c$input" + // Special Mayors + "Scorpius" -> "§d$input" + "Jerry" -> "§d$input" + "Derpy" -> "§d$input" + "Dante" -> "§d$input" + else -> "§cUnknown Mayor: §7$input" + } + } @SubscribeEvent fun onTick(event: LorenzTickEvent) { if (!LorenzUtils.onHypixel) return if (event.repeatSeconds(3)) { - check() + checkHypixelAPI() + getTimeTillNextMayor() } } - private fun check() { + private fun getTimeTillNextMayor() { + var currentYear = SkyBlockTime.now().year + val month = 3 // Late Spring + // check if either the month is already over or the day is after 27th and third month + if (SkyBlockTime.now().month > month || (SkyBlockTime.now().day >= 27 && SkyBlockTime.now().month == month)) { + // if so, next mayor will be in the next year + currentYear++ + } + val nextMayorTime = SkyBlockTime(currentYear, month, day = 27).toMillis() + + timeTillNextMayor = nextMayorTime - System.currentTimeMillis() + } + + private fun checkHypixelAPI() { if (System.currentTimeMillis() > lastUpdate + 60_000 * 5) { lastUpdate = System.currentTimeMillis() SkyHanniMod.coroutineScope.launch { @@ -67,7 +104,7 @@ class MayorElection { if (nextMayorTime > System.currentTimeMillis()) { currentYear-- } - currentCandidate = candidates[currentYear] + currentMayor = candidates[currentYear] } private fun MayorJson.Election.getPairs() = year + 1 to candidates.bestCandidate() diff --git a/src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt index 90fe4783ebc3..60da069ccf95 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt @@ -15,6 +15,10 @@ object PartyAPI { // TODO USE SH-REPO private val youJoinedPartyPattern = "§eYou have joined (?.*)'s §eparty!".toPattern() private val othersJoinedPartyPattern = "(?.*) §ejoined the party.".toPattern() + // §dParty Finder §f> §bSkirtwearer §ejoined the group! (§3Combat Level 46§e) + private val kuudraFinderJoinPattern = "§dParty Finder §f> (?.*?) §ejoined the group! \\(§[a-fA-F0-9]+Combat Level \\d+§e\\)".toPattern() + // §dParty Finder §f> §bSkirtwearer §ejoined the dungeon group! (§bArcher Level 22§e) + private val dungeonFinderJoinPattern = "§dParty Finder §f> (?.*?) §ejoined the dungeon group! \\(§[a-fA-F0-9].* Level \\d+§[a-fA-F0-9]\\)".toPattern() private val othersInThePartyPattern = "§eYou'll be partying with: (?.*)".toPattern() private val otherLeftPattern = "(?.*) §ehas left the party.".toPattern() private val otherKickedPattern = "(?.*) §ehas been removed from the party.".toPattern() @@ -66,6 +70,16 @@ object PartyAPI { if (!partyMembers.contains(playerName)) partyMembers.add(playerName) } } + kuudraFinderJoinPattern.matchMatcher(message) { + val name = group("name").cleanPlayerName() + if (name == LorenzUtils.getPlayerName()) return@matchMatcher + if (!partyMembers.contains(name)) partyMembers.add(name) + } + dungeonFinderJoinPattern.matchMatcher(message) { + val name = group("name").cleanPlayerName() + if (name == LorenzUtils.getPlayerName()) return@matchMatcher + if (!partyMembers.contains(name)) partyMembers.add(name) + } // one member got removed otherLeftPattern.matchMatcher(message) { diff --git a/src/main/java/at/hannibal2/skyhanni/data/ProfileStorageData.kt b/src/main/java/at/hannibal2/skyhanni/data/ProfileStorageData.kt index 8e6f9bb7d858..a7e6810aeee6 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/ProfileStorageData.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/ProfileStorageData.kt @@ -26,6 +26,7 @@ object ProfileStorageData { // TODO USE SH-REPO private val profileSwitchPattern = "§7Switching to profile (?.*)\\.\\.\\.".toPattern() + val profileTablistPattern = "§e§lProfile: §r§a(?.*)".toPattern() private var sackPlayers: SackData.PlayerSpecific? = null var sackProfiles: SackData.ProfileSpecific? = null @@ -83,8 +84,7 @@ object ProfileStorageData { val playerSpecific = playerSpecific ?: return val sackPlayers = sackPlayers ?: return for (line in event.tabList) { - val pattern = "§e§lProfile: §r§a(?.*)".toPattern() - pattern.matchMatcher(line) { + profileTablistPattern.matchMatcher(line) { val profileName = group("name").lowercase() loadProfileSpecific(playerSpecific, sackPlayers, profileName) nextProfile = null diff --git a/src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt index 0deb53164d2a..09bcfccc2646 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt @@ -5,16 +5,16 @@ import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.PurseChangeCause import at.hannibal2.skyhanni.events.PurseChangeEvent import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber -import at.hannibal2.skyhanni.utils.NumberUtil.milion +import at.hannibal2.skyhanni.utils.NumberUtil.million import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraft.client.Minecraft import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -class PurseAPI { - // TODO USE SH-REPO - private val pattern = "(Piggy|Purse): §6(?[\\d,]*).*".toPattern() - private var currentPurse = 0.0 +object PurseAPI { + val pursePattern by RepoPattern.pattern("data.purse.coins", "(Piggy|Purse): §6(?[\\d,]*(\\.\\d)?)( ?(§.)*\\([+-](?[\\w,.]+)\\)?|.*)?$") private var inventoryCloseTime = 0L + var currentPurse = 0.0 @SubscribeEvent fun onInventoryClose(event: InventoryCloseEvent) { @@ -23,9 +23,8 @@ class PurseAPI { @SubscribeEvent fun onTick(event: LorenzTickEvent) { - for (line in ScoreboardData.sidebarLinesFormatted) { - val newPurse = pattern.matchMatcher(line) { + val newPurse = pursePattern.matchMatcher(line) { group("coins").formatNumber().toDouble() } ?: continue val diff = newPurse - currentPurse @@ -43,7 +42,7 @@ class PurseAPI { return PurseChangeCause.GAIN_TALISMAN_OF_COINS } - if (diff == 15.milion || diff == 100.milion) { + if (diff == 15.million || diff == 100.million) { return PurseChangeCause.GAIN_DICE_ROLL } diff --git a/src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt new file mode 100644 index 000000000000..46cbd15468c5 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt @@ -0,0 +1,192 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.jsonobjects.repo.ItemsJson +import at.hannibal2.skyhanni.events.ConfigLoadEvent +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.PlaySoundEvent +import at.hannibal2.skyhanni.events.ProfileJoinEvent +import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LocationUtils.distanceToPlayer +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName +import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getEnchantments +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpaceAndResets +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraft.client.Minecraft +import net.minecraft.item.ItemBow +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +private var infinityQuiverLevelMultiplier = 0.03f + +object QuiverAPI { + var currentArrow: QuiverArrowType? = null + var currentAmount: Int = 0 + var arrowAmount: MutableMap = mutableMapOf() + + private val fakeBows = listOf("BOSS_SPIRIT_BOW".asInternalName()) + + private val group = RepoPattern.group("data.quiver.chat") + private val selectPattern by group.pattern("select", "§aYou set your selected arrow type to §f(?.*)§a!") + private val fillUpJaxPattern by group.pattern( + "fillupjax", + "§aJax forged §f(?.*)§8 x(?.*) §afor §6(?.*) Coins§a!" + ) + private val fillUpPattern by group.pattern( + "fillup", + "§aYou filled your quiver with §f(?.*) §aextra arrows!" + ) + private val clearedPattern by group.pattern("cleared", "§aCleared your quiver!") + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + val message = event.message.trimWhiteSpaceAndResets().removeResets() + + selectPattern.matchMatcher(message) { + val arrow = group("arrow") + currentArrow = QuiverArrowType.entries.find { arrow.contains(it.arrow) } ?: QuiverArrowType.NONE + currentAmount = arrowAmount[currentArrow]?.toInt() ?: 0 + + return saveArrowType() + } + + fillUpJaxPattern.matchMatcher(message) { + val type = group("type") + val amount = group("amount").formatNumber().toFloat() + + val filledUpType = QuiverArrowType.entries.find { type.contains(it.arrow) } ?: return + + val existingAmount = arrowAmount[filledUpType] ?: 0f + val newAmount = existingAmount + amount + arrowAmount[filledUpType] = newAmount + + return saveArrowAmount() + } + + fillUpPattern.matchMatcher(message) { + val flintAmount = group("flintAmount").formatNumber().toFloat() + val existingAmount = arrowAmount[QuiverArrowType.FLINT] ?: 0.0f + val newAmount = existingAmount + flintAmount + + arrowAmount[QuiverArrowType.FLINT] = newAmount + + return saveArrowAmount() + } + + clearedPattern.matchMatcher(message) { + currentAmount = 0 + arrowAmount.clear() + + saveArrowAmount() + } + } + + @SubscribeEvent + fun onInventoryFullyLoaded(event: InventoryFullyOpenedEvent) { + if (!LorenzUtils.inSkyBlock) return + if (event.inventoryName != "Quiver") return + + // clear to prevent duplicates + currentAmount = 0 + if (arrowAmount.isNotEmpty()) + arrowAmount.clear() + + val stacks = event.inventoryItems + for (stack in stacks.values) { + val lore = stack.getLore() + if (lore.isEmpty()) continue + + val arrow = stack.getInternalNameOrNull() ?: continue + val amount = stack.stackSize + + val arrowType = QuiverArrowType.entries.find { arrow == it.internalName } ?: continue + val arrowAmount = amount + (this.arrowAmount[arrowType] ?: 0.0f) + + this.arrowAmount[arrowType] = arrowAmount + } + + saveArrowAmount() + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + // Inspired by SkyblockFeatures - https://github.com/MrFast-js/SkyblockFeatures/ + fun onPlaySound(event: PlaySoundEvent) { + val holdingBow = InventoryUtils.getItemInHand()?.item is ItemBow && !fakeBows.contains( + InventoryUtils.getItemInHand()?.getInternalNameOrNull() + ) + + // check if sound location is more than 1 block away from player + val soundLocation = event.location + if (soundLocation.distanceToPlayer() > SkyHanniMod.feature.dev.bowSoundDistance) return + + if (event.soundName == "random.bow" && holdingBow) { + val arrowType = currentArrow ?: return + val arrowAmount = QuiverAPI.arrowAmount[arrowType] ?: return + if (arrowAmount <= 0) return + + val infiniteQuiverLevel = InventoryUtils.getItemInHand()?.getEnchantments()?.get("infinite_quiver") ?: 0 + + val amountToRemove = { + when (Minecraft.getMinecraft().thePlayer.isSneaking && infiniteQuiverLevel > 0) { + true -> 1.0f + false -> { + when (infiniteQuiverLevel) { + in 1..10 -> 1 - (infinityQuiverLevelMultiplier * infiniteQuiverLevel) + else -> 1.0f + } + } + } + } + + this.arrowAmount[arrowType] = (arrowAmount - amountToRemove()).coerceAtLeast(0.0f) + + saveArrowAmount() + } + } + + // Handle Storage data + @SubscribeEvent + fun onRepoReload(event: RepositoryReloadEvent) { + val data = event.getConstant("Items") + infinityQuiverLevelMultiplier = data.enchant_multiplier["infinite_quiver"] ?: 0.03f + } + + @SubscribeEvent + fun onConfigLoad(event: ConfigLoadEvent) { + val config = ProfileStorageData.profileSpecific ?: return + currentArrow = config.arrows.currentArrow ?: QuiverArrowType.FLINT + arrowAmount = config.arrows.arrowAmount ?: mutableMapOf() + currentAmount = arrowAmount[currentArrow]?.toInt() ?: 0 + } + + @SubscribeEvent + fun onProfileJoin(event: ProfileJoinEvent) { + val config = ProfileStorageData.profileSpecific ?: return + currentArrow = config.arrows.currentArrow ?: QuiverArrowType.FLINT + arrowAmount = config.arrows.arrowAmount ?: mutableMapOf() + currentAmount = arrowAmount[currentArrow]?.toInt() ?: 0 + } + + private fun saveArrowType() { + val config = ProfileStorageData.profileSpecific ?: return + config.arrows.currentArrow = currentArrow + } + + private fun saveArrowAmount() { + val config = ProfileStorageData.profileSpecific ?: return + + if (arrowAmount != null) + config.arrows.arrowAmount = arrowAmount + + if (arrowAmount.isNotEmpty()) + currentAmount = arrowAmount[currentArrow]?.toInt() ?: 0 + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/QuiverArrowType.kt b/src/main/java/at/hannibal2/skyhanni/data/QuiverArrowType.kt new file mode 100644 index 000000000000..c7672ae0efa2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/QuiverArrowType.kt @@ -0,0 +1,22 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.utils.NEUInternalName +import at.hannibal2.skyhanni.utils.NEUInternalName.Companion.asInternalName + +enum class QuiverArrowType(val arrow: String, val internalName: NEUInternalName) { + NONE("None", "NONE".asInternalName()), + + SLIME_BALL("Slime Ball", "SLIME_BALL".asInternalName()), + PRISMARINE_SHARD("Prismarine Shard", "PRISMARINE_SHARD".asInternalName()), + FLINT("Flint Arrow", "ARROW".asInternalName()), + REINFORCED_IRON_ARROW("Reinforced Iron Arrow", "REINFORCED_IRON_ARROW".asInternalName()), + GOLD_TIPPED_ARROW("Gold-tipped Arrow", "GOLD_TIPPED_ARROW".asInternalName()), + REDSTONE_TIPPED_ARROW("Redstone-tipped Arrow", "REDSTONE_TIPPED_ARROW".asInternalName()), + EMERALD_TIPPED_ARROW("Emerald-tipped Arrow", "EMERALD_TIPPED_ARROW".asInternalName()), + BOUNCY_ARROW("Bouncy Arrow", "BOUNCY_ARROW".asInternalName()), + ICY_ARROW("Icy Arrow", "ICY_ARROW".asInternalName()), + ARMORSHRED_ARROW("Armorshred Arrow", "ARMORSHRED_ARROW".asInternalName()), + EXPLOSIVE_ARROW("Explosive Arrow", "EXPLOSIVE_ARROW".asInternalName()), + GLUE_ARROW("Glue Arrow", "GLUE_ARROW".asInternalName()), + NANSORB_ARROW("Nansorb Arrow", "NANSORB_ARROW".asInternalName()), +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt b/src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt index 71d84328c94a..963979d1b553 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt @@ -34,11 +34,19 @@ class ScoreboardData { fun formatLines(rawList: List): List { val list = mutableListOf() for (line in rawList) { - val seperator = splitIcons.find { line.contains(it) } ?: continue - val split = line.split(seperator) + val separator = splitIcons.find { line.contains(it) } ?: continue + val split = line.split(separator) val start = split[0] var end = split[1] - if (end.length >= 2) { + // get last color code in start + val lastColorIndex = start.lastIndexOf('§') + val lastColor = when { + lastColorIndex != -1 && lastColorIndex + 1 < start.length -> start[lastColorIndex] + "" + start[lastColorIndex + 1] + else -> "" + } + + // remove first color code from end, when it is the same as the last color code in start + if (end.length >= 2 && lastColor.isNotEmpty() && end[0] == '§' && (end[1] in '0'..'9' || end[1] in 'a'..'f') && end[1] == lastColor[1]) { end = end.substring(2) } list.add(start + end) @@ -93,4 +101,4 @@ class ScoreboardData { ScorePlayerTeam.formatPlayerName(scoreboard.getPlayersTeam(it.playerName), it.playerName) } } -} \ No newline at end of file +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt index 2a6b99297f73..3ebcd43b08ff 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt @@ -32,7 +32,7 @@ object SlayerAPI { var latestSlayerCategory = "" private var latestProgressChangeTime = 0L var latestWrongAreaWarning = 0L - private var latestSlayerProgress = "" + var latestSlayerProgress = "" fun hasActiveSlayerQuest() = latestSlayerCategory != "" diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/ItemsJson.java b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/ItemsJson.java index c6bfc90f3e6b..f8c54e3036e8 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/ItemsJson.java +++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/ItemsJson.java @@ -11,4 +11,7 @@ public class ItemsJson { @Expose public Map crimson_tiers; + + @Expose + public Map enchant_multiplier; } diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt index bfcb245016e9..dd060aa9b4a0 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/MinionCraftHelper.kt @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.features.bingo import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent import at.hannibal2.skyhanni.events.LorenzTickEvent @@ -18,6 +19,8 @@ import at.hannibal2.skyhanni.utils.NumberUtil.romanToDecimalIfNecessary import at.hannibal2.skyhanni.utils.RenderUtils.renderStrings import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import com.google.gson.JsonArray +import com.google.gson.JsonObject import io.github.moulberry.notenoughupdates.recipes.CraftingRecipe import net.minecraft.client.Minecraft import net.minecraft.item.ItemStack @@ -117,7 +120,7 @@ class MinionCraftHelper { } } - FirstMinionTier.firstMinionTier(otherItems, minions, tierOneMinions, tierOneMinionsDone) + FirstMinionTier.firstMinionTier(otherItems, minions, tierOneMinions, tierOneMinionsDone) return Pair(minions, otherItems) } @@ -148,10 +151,11 @@ class MinionCraftHelper { val internalName = internalId.asInternalName() if (internalName.endsWith("_GENERATOR_1")) { if (internalName == "REVENANT_GENERATOR_1".asInternalName() || - internalName == "TARANTULA_GENERATOR_1".asInternalName() || - internalName == "VOIDLING_GENERATOR_1".asInternalName() || - internalName == "INFERNO_GENERATOR_1".asInternalName() || - internalName == "VAMPIRE_GENERATOR_1".asInternalName()) continue + internalName == "TARANTULA_GENERATOR_1".asInternalName() || + internalName == "VOIDLING_GENERATOR_1".asInternalName() || + internalName == "INFERNO_GENERATOR_1".asInternalName() || + internalName == "VAMPIRE_GENERATOR_1".asInternalName() + ) continue tierOneMinions.add(internalName) } @@ -265,4 +269,32 @@ class MinionCraftHelper { } } } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.transform(19, "#player.bingoSessions") { element -> + for ((_, data) in element.asJsonObject.entrySet()) { + fixTierOneMinions(data.asJsonObject) + } + element + } + } + + private fun fixTierOneMinions(data: JsonObject) { + val newList = JsonArray() + var counter = 0 + for (entry in data["tierOneMinionsDone"].asJsonArray) { + val name = entry.asString + if (!name.startsWith("INTERNALNAME:")) { + newList.add(entry) + } else { + counter++ + } + } + if (counter > 0) { + println("Removed $counter wrong entries in fixTierOneMinions.") + } + data.add("tierOneMinionsDone", newList) + } + } diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/CompactSplashPotionMessage.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/CompactSplashPotionMessage.kt index e624af88c6d9..cf81e82c6f1f 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/chat/CompactSplashPotionMessage.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/CompactSplashPotionMessage.kt @@ -18,9 +18,9 @@ class CompactSplashPotionMessage { // Fix for Hypixel having a different message for Poisoned Candy. // Did not make the first pattern optional to prevent conflicts with Dungeon Buffs/other things - "§a§lBUFF! §fYou have gained §r(?§2Poisoned Candy I)§r§f!§r".toPattern(), - "§a§lBUFF! §fYou splashed yourself with §r(?§2Poisoned Candy I)§r§f!§r".toPattern(), - "§a§lBUFF! §fYou were splashed by (?.*) §fwith §r(?§2Poisoned Candy I)§r§f!§r".toPattern() + "§a§lBUFF! §fYou have gained §r(?§2Poisoned Candy I)§r§f!".toPattern(), + "§a§lBUFF! §fYou splashed yourself with §r(?§2Poisoned Candy I)§r§f!".toPattern(), + "§a§lBUFF! §fYou were splashed by (?.*) §fwith §r(?§2Poisoned Candy I)§r§f!".toPattern() ) @SubscribeEvent diff --git a/src/main/java/at/hannibal2/skyhanni/features/event/diana/DianaAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/event/diana/DianaAPI.kt index 7a6fa1d1722b..cc716b0d7f2d 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/event/diana/DianaAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/event/diana/DianaAPI.kt @@ -2,7 +2,7 @@ package at.hannibal2.skyhanni.features.event.diana import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.data.IslandType -import at.hannibal2.skyhanni.data.MayorElection +import at.hannibal2.skyhanni.data.MayorAPI import at.hannibal2.skyhanni.data.PetAPI import at.hannibal2.skyhanni.utils.InventoryUtils import at.hannibal2.skyhanni.utils.ItemUtils.getInternalName @@ -16,8 +16,8 @@ object DianaAPI { fun hasSpadeInHand() = InventoryUtils.itemInHandId == spade - private fun isRitualActive() = MayorElection.isPerkActive("Diana", "Mythological Ritual") || - MayorElection.isPerkActive("Jerry", "Perkpocalypse") || SkyHanniMod.feature.event.diana.alwaysDiana + private fun isRitualActive() = MayorAPI.isPerkActive("Diana", "Mythological Ritual") || + MayorAPI.isPerkActive("Jerry", "Perkpocalypse") || SkyHanniMod.feature.event.diana.alwaysDiana fun hasGriffinPet() = PetAPI.isCurrentPet("Griffin") diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropSpeed.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropSpeed.kt index d44dcf359cb8..eaf3e604b848 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropSpeed.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropSpeed.kt @@ -4,7 +4,7 @@ import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.data.ClickType import at.hannibal2.skyhanni.data.GardenCropMilestones.getCounter import at.hannibal2.skyhanni.data.GardenCropMilestones.setCounter -import at.hannibal2.skyhanni.data.MayorElection +import at.hannibal2.skyhanni.data.MayorAPI import at.hannibal2.skyhanni.data.jsonobjects.repo.DicerDropsJson import at.hannibal2.skyhanni.data.jsonobjects.repo.DicerDropsJson.DicerType import at.hannibal2.skyhanni.events.CropClickEvent @@ -170,7 +170,7 @@ object GardenCropSpeed { fun finneganPerkActive(): Boolean { val forcefullyEnabledAlwaysFinnegan = config.forcefullyEnabledAlwaysFinnegan - val perkActive = MayorElection.isPerkActive("Finnegan", "Farming Simulator") + val perkActive = MayorAPI.isPerkActive("Finnegan", "Farming Simulator") return forcefullyEnabledAlwaysFinnegan || perkActive } diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderTracker.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderTracker.kt index ce5a8dd489dd..792f4a901366 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderTracker.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/powdertracker/PowderTracker.kt @@ -6,6 +6,7 @@ import at.hannibal2.skyhanni.config.features.mining.PowderTrackerConfig.PowderDi import at.hannibal2.skyhanni.data.IslandType import at.hannibal2.skyhanni.events.ConfigLoadEvent import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.IslandChangeEvent import at.hannibal2.skyhanni.events.LorenzChatEvent import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.events.LorenzWorldChangeEvent @@ -18,6 +19,8 @@ import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import at.hannibal2.skyhanni.utils.tracker.SkyHanniTracker import at.hannibal2.skyhanni.utils.tracker.TrackerData +import com.google.gson.JsonArray +import com.google.gson.JsonNull import com.google.gson.annotations.Expose import net.minecraft.entity.boss.BossStatus import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -178,10 +181,31 @@ object PowderTracker { event.transform(11, "mining.powderTracker.textFormat") { element -> ConfigUtils.migrateIntArrayListToEnumArrayList(element, PowderDisplayEntry::class.java) } + + event.transform(20, "mining.powderTracker.textFormat") { element -> + val newList = JsonArray() + for (entry in element.asJsonArray) { + if (entry is JsonNull) continue + if (entry.asString.let { it != "TITLE" && it != "DISPLAY_MODE" }) { + newList.add(entry) + } + } + newList + } + } + + @SubscribeEvent + fun onIslandChange(event: IslandChangeEvent) { + if (event.newIsland == IslandType.CRYSTAL_HOLLOWS) { + tracker.firstUpdate() + } } - private fun formatDisplay(map: List>) = buildList { + private fun formatDisplay(map: List>) = buildList> { if (map.isEmpty()) return@buildList + + addAsSingletonList("§b§lPowder Tracker") + for (index in config.textFormat.get()) { // TODO, change functionality to use enum rather than ordinals add(map[index.ordinal]) @@ -195,12 +219,6 @@ object PowderTracker { calculate(data, goldEssenceInfo, PowderChestReward.GOLD_ESSENCE) calculateChest(data) - addAsSingletonList("§b§lPowder Tracker") - - if (!tracker.isInventoryOpen()) { - addAsSingletonList("") - } - val chestPerHour = format(chestInfo.perHour) addAsSingletonList("§d${data.totalChestPicked.addSeparators()} Total Chests Picked §7($chestPerHour/h)") addAsSingletonList("§bDouble Powder: ${if (doublePowder) "§aActive! §7($powderTimer)" else "§cInactive!"}") diff --git a/src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt b/src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt index 5c755b66bdbb..d1284485bf1d 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt @@ -1,7 +1,7 @@ package at.hannibal2.skyhanni.features.minion import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.data.MayorElection +import at.hannibal2.skyhanni.data.MayorAPI import at.hannibal2.skyhanni.data.jsonobjects.repo.MinionXPJson import at.hannibal2.skyhanni.events.IslandChangeEvent import at.hannibal2.skyhanni.events.LorenzToolTipEvent @@ -113,7 +113,7 @@ class MinionXp { // TODO add wisdom and temporary skill exp (Events) to calculation val baseXp = xp.amount * item.stackSize - val xpAmount = if (MayorElection.isPerkActive("Derpy", "MOAR SKILLZ!!!")) { + val xpAmount = if (MayorAPI.isPerkActive("Derpy", "MOAR SKILLZ!!!")) { baseXp * 1.5 } else baseXp diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/PetCandyUsedDisplay.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/PetCandyUsedDisplay.kt index 87def5df3be0..886827e23a1a 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/PetCandyUsedDisplay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/PetCandyUsedDisplay.kt @@ -1,19 +1,28 @@ package at.hannibal2.skyhanni.features.misc import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.events.GuiRenderItemEvent import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.RenderUtils.drawSlotText +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getMaxPetLevel import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getPetCandyUsed +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getPetLevel import net.minecraftforge.fml.common.eventhandler.SubscribeEvent class PetCandyUsedDisplay { + private val config get() = SkyHanniMod.feature.misc.petCandy + @SubscribeEvent fun onRenderItemOverlayPost(event: GuiRenderItemEvent.RenderOverlayEvent.GuiRenderItemPost) { val stack = event.stack ?: return if (!LorenzUtils.inSkyBlock || stack.stackSize != 1) return - if (!SkyHanniMod.feature.misc.petCandyUsed) return + if (!config.showCandy) return + + if (config.hideOnMaxed) { + if (stack.getPetLevel() == stack.getMaxPetLevel()) return + } val petCandyUsed = stack.getPetCandyUsed() ?: return if (petCandyUsed == 0) return @@ -24,4 +33,9 @@ class PetCandyUsedDisplay { event.drawSlotText(x, y, stackTip, .9f) } + + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(22, "misc.petCandyUsed", "misc.petCandy.showCandy") + } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/ServerRestartTitle.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/ServerRestartTitle.kt index 950ed60f3e36..e903ba9603fa 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/ServerRestartTitle.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/ServerRestartTitle.kt @@ -6,14 +6,16 @@ import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import at.hannibal2.skyhanni.utils.TimeUtils +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import kotlin.time.Duration.Companion.seconds class ServerRestartTitle { private val config get() = SkyHanniMod.feature.misc - // TODO USE SH-REPO - private val pattern = "§cServer closing: (?\\d+):(?\\d+) §8.*".toPattern() + companion object{ + val restartingPattern by RepoPattern.pattern("features.misc.serverrestart", "§cServer closing(: (?\\d+):(?\\d+)| soon!) §8.*") + } @SubscribeEvent fun onTick(event: LorenzTickEvent) { @@ -23,7 +25,7 @@ class ServerRestartTitle { if (!event.repeatSeconds(1)) return for (line in ScoreboardData.sidebarLinesFormatted) { - pattern.matchMatcher(line) { + restartingPattern.matchMatcher(line) { val minutes = group("minutes").toInt() val seconds = group("seconds").toInt() val totalSeconds = minutes * 60 + seconds diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/CustomScoreboard.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/CustomScoreboard.kt new file mode 100644 index 000000000000..2aa60006526e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/CustomScoreboard.kt @@ -0,0 +1,115 @@ +// +// Requested by alpaka8123 (https://discord.com/channels/997079228510117908/1162844830360146080) +// Done by J10a1n15, with lots of help from hanni and more contribs <3 +// Also big thanks to the people that tested it before it released, saved me some time <3 +// + +// +// TODO LIST +// V2 RELEASE +// - Soulflow API +// - Bank API (actually maybe not, I like the current design) +// - beacon power +// - skyblock level +// - more bg options (round, blurr, outline) +// - countdown events like fishing festival + fiesta when its not on tablist +// + +package at.hannibal2.skyhanni.features.misc.customscoreboard + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RenderUtils.AlignmentEnum +import at.hannibal2.skyhanni.utils.RenderUtils.renderStringsAlignedWidth +import at.hannibal2.skyhanni.utils.TabListData +import net.minecraftforge.client.GuiIngameForge +import net.minecraftforge.client.event.RenderGameOverlayEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +typealias ScoreboardElement = Pair + +class CustomScoreboard { + private val config get() = SkyHanniMod.feature.gui.customScoreboard + private var display = emptyList() + private var cache = emptyList() + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isCustomScoreboardEnabled()) return + if (display.isEmpty()) return + + RenderBackground().renderBackground() + + if (!TabListData.fullyLoaded && config.displayConfig.cacheScoreboardOnIslandSwitch) { + config.position.renderStringsAlignedWidth(cache, posLabel = "Custom Scoreboard") + } else { + config.position.renderStringsAlignedWidth(display, posLabel = "Custom Scoreboard") + cache = display + } + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isCustomScoreboardEnabled()) return + + // Creating the lines + if (event.isMod(5)) { + display = createLines() + } + + // Remove Known Lines, so we can get the unknown ones + UnknownLinesHandler.handleUnknownLines() + } + + private fun createLines() = buildList { + val lineMap = HashMap>>() + for (element in ScoreboardElements.entries) { + lineMap[element.ordinal] = + if (element.isVisible()) element.getPair() else listOf("" to AlignmentEnum.LEFT) + } + + return formatLines(lineMap) + } + + private fun formatLines(lineMap: HashMap>): MutableList { + val newList = mutableListOf() + for (element in config.scoreboardEntries) { + lineMap[element.ordinal]?.let { + // Hide consecutive empty lines + if (config.informationFilteringConfig.hideConsecutiveEmptyLines && it[0].first == "" && newList.last().first.isEmpty()) { + continue + } + + // Adds empty lines + if (it[0].first == "") { + newList.add("" to AlignmentEnum.LEFT) + continue + } + + // Does not display this line + if (it.any { i -> i.first == "" }) { + continue + } + + // Multiline and singular line support + newList.addAll(it.map { i -> i }) + } + } + + return newList + } + + // Thank you Apec for showing that the ElementType of the stupid scoreboard is FUCKING HELMET WTF + @SubscribeEvent + fun onRenderScoreboard(event: RenderGameOverlayEvent.Post) { + if (event.type == RenderGameOverlayEvent.ElementType.HELMET) { + GuiIngameForge.renderObjective = !isHideVanillaScoreboardEnabled() + } + } + + private fun isCustomScoreboardEnabled() = LorenzUtils.inSkyBlock && config.enabled + private fun isHideVanillaScoreboardEnabled() = + isCustomScoreboardEnabled() && config.displayConfig.hideVanillaScoreboard +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/CustomScoreboardUtils.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/CustomScoreboardUtils.kt new file mode 100644 index 000000000000..847e8cddd956 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/CustomScoreboardUtils.kt @@ -0,0 +1,73 @@ +package at.hannibal2.skyhanni.features.misc.customscoreboard + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.features.gui.customscoreboard.DisplayConfig +import at.hannibal2.skyhanni.data.HypixelData +import at.hannibal2.skyhanni.data.ScoreboardData +import at.hannibal2.skyhanni.mixins.transformers.AccessorGuiPlayerTabOverlay +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.formatNumber +import at.hannibal2.skyhanni.utils.RenderUtils.AlignmentEnum +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpaceAndResets +import net.minecraft.client.Minecraft +import java.util.regex.Pattern + +object CustomScoreboardUtils { + private val config get() = SkyHanniMod.feature.gui.customScoreboard + val numberFormat get() = config.displayConfig.numberFormat + + fun getGroupFromPattern(list: List, pattern: Pattern, group: String): String { + val matchedLine = list.map { it.removeResets().trimWhiteSpaceAndResets().removeResets() } + .firstNotNullOfOrNull { line -> + pattern.matchMatcher(line) { + group(group) + } + } + + return matchedLine ?: "0" + } + + fun getProfileTypeSymbol(): String { + return when { + HypixelData.ironman -> "§7♲ " // Ironman + HypixelData.stranded -> "§a☀ " // Stranded + HypixelData.bingo -> ScoreboardData.sidebarLines.firstOrNull { it.contains("Bingo") }?.substring( + 0, + 3 + ) + "Ⓑ " // Bingo - gets the first 3 chars of " §9Ⓑ §9Bingo" (you are unable to get the Ⓑ for some reason) + else -> "§e" // Default case + } + } + + fun getTablistFooter(): String { + val tabList = Minecraft.getMinecraft().ingameGUI.tabList as AccessorGuiPlayerTabOverlay + if (tabList.footer_skyhanni == null) return "" + return tabList.footer_skyhanni.formattedText.replace("§r", "") + } + + fun getTitleAndFooterAlignment() = when (config.displayConfig.titleAndFooter.centerTitleAndFooter) { + true -> AlignmentEnum.CENTER + false -> AlignmentEnum.LEFT + } + + fun Int.formatNum(): String = when (numberFormat) { + DisplayConfig.NumberFormat.SHORT -> NumberUtil.format(this) + DisplayConfig.NumberFormat.LONG -> this.addSeparators() + else -> "0" + } + + fun String.formatNum(): String { + val number = this.formatNumber()//replace(",", "").toIntOrNull() ?: return "0" + + return when (numberFormat) { + DisplayConfig.NumberFormat.SHORT -> NumberUtil.format(number) + DisplayConfig.NumberFormat.LONG -> number.addSeparators() + else -> "0" + } + } + + class UndetectedScoreboardLines(message: String) : Exception(message) +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/RenderBackground.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/RenderBackground.kt new file mode 100644 index 000000000000..02fb1160d566 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/RenderBackground.kt @@ -0,0 +1,84 @@ +package at.hannibal2.skyhanni.features.misc.customscoreboard + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.core.config.Position +import at.hannibal2.skyhanni.data.GuiEditManager +import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getAbsX +import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getAbsY +import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getDummySize +import at.hannibal2.skyhanni.utils.SpecialColour +import io.github.moulberry.notenoughupdates.util.Utils +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.Gui +import net.minecraft.client.gui.ScaledResolution +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.util.ResourceLocation +import org.lwjgl.opengl.GL11 + +class RenderBackground { + private val config get() = SkyHanniMod.feature.gui.customScoreboard + + fun renderBackground() { + val position = config.position + val border = 5 + + val x = position.getAbsX() + val y = position.getAbsY() + + val elementWidth = position.getDummySize().x + val elementHeight = position.getDummySize().y + + val scaledWidth = ScaledResolution(Minecraft.getMinecraft()).scaledWidth + val scaledHeight = ScaledResolution(Minecraft.getMinecraft()).scaledHeight + + position.set( + Position( + if (config.displayConfig.alignment.alignRight) + scaledWidth - elementWidth - (border * 2) + else x, + if (config.displayConfig.alignment.alignCenterVertically) + scaledHeight / 2 - elementHeight / 2 + else y, + position.getScale(), + position.isCenter + ) + ) + + if (GuiEditManager.isInGui()) return + + // Insert Rounded Rectangle Shader here (https://github.com/hannibal002/SkyHanni/pull/851) + + val textureLocation = ResourceLocation("skyhanni", "scoreboard.png") + + // Save the current color state + GlStateManager.pushMatrix() + GlStateManager.pushAttrib() + + GlStateManager.color(1f, 1f, 1f, 1f) + + if (config.backgroundConfig.enabled && config.backgroundConfig.useCustomBackgroundImage) { + // Draw the default texture + Minecraft.getMinecraft().textureManager.bindTexture(textureLocation) + Utils.drawTexturedRect( + (x - border).toFloat(), + (y - border).toFloat(), + (elementWidth + border * 3).toFloat(), + (elementHeight + border * 2).toFloat(), + GL11.GL_NEAREST + ) + } else if (config.backgroundConfig.enabled) { + // Draw a solid background with a specified color + Gui.drawRect( + x - border, + y - border, + x + elementWidth + border * 2, + y + elementHeight + border, + SpecialColour.specialToChromaRGB(config.backgroundConfig.color) + ) + } + + // Restore the original color state + GlStateManager.popMatrix() + GlStateManager.popAttrib() + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardElements.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardElements.kt new file mode 100644 index 000000000000..5934d10b8b38 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardElements.kt @@ -0,0 +1,605 @@ +package at.hannibal2.skyhanni.features.misc.customscoreboard + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.BitsAPI +import at.hannibal2.skyhanni.data.HypixelData +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.MaxwellAPI +import at.hannibal2.skyhanni.data.MaxwellPowers +import at.hannibal2.skyhanni.data.MayorAPI +import at.hannibal2.skyhanni.data.PartyAPI +import at.hannibal2.skyhanni.data.PurseAPI +import at.hannibal2.skyhanni.data.QuiverAPI +import at.hannibal2.skyhanni.data.QuiverArrowType +import at.hannibal2.skyhanni.data.ScoreboardData +import at.hannibal2.skyhanni.data.SlayerAPI +import at.hannibal2.skyhanni.features.misc.customscoreboard.CustomScoreboardUtils.formatNum +import at.hannibal2.skyhanni.features.misc.customscoreboard.CustomScoreboardUtils.getGroupFromPattern +import at.hannibal2.skyhanni.features.misc.customscoreboard.CustomScoreboardUtils.getTitleAndFooterAlignment +import at.hannibal2.skyhanni.mixins.hooks.replaceString +import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.LorenzUtils.inDungeons +import at.hannibal2.skyhanni.utils.LorenzUtils.nextAfter +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.RenderUtils.AlignmentEnum +import at.hannibal2.skyhanni.utils.StringUtils.firstLetterUppercase +import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.TabListData +import at.hannibal2.skyhanni.utils.TimeUnit +import at.hannibal2.skyhanni.utils.TimeUtils +import at.hannibal2.skyhanni.utils.TimeUtils.formatted +import io.github.moulberry.notenoughupdates.util.SkyBlockTime +import java.util.function.Supplier + +private val config get() = SkyHanniMod.feature.gui.customScoreboard +private val displayConfig get() = config.displayConfig +private val informationFilteringConfig get() = config.informationFilteringConfig + +var unknownLines = listOf() +var amountOfUnknownLines = 0 + +enum class ScoreboardElements( + private val displayPair: Supplier>, + private val showWhen: () -> Boolean, + private val configLine: String +) { + TITLE( + ::getTitleDisplayPair, + { true }, + "§6§lSKYBLOCK" + ), + PROFILE( + ::getProfileDisplayPair, + { true }, + "§7♲ Blueberry" + ), + PURSE( + ::getPurseDisplayPair, + ::getPurseShowWhen, + "Purse: §652,763,737" + ), + MOTES( + ::getMotesDisplayPair, + ::getMotesShowWhen, + "Motes: §d64,647" + ), + BANK( + ::getBankDisplayPair, + ::getBankShowWhen, + "Bank: §6249M" + ), + BITS( + ::getBitsDisplayPair, + ::getBitsShowWhen, + "Bits: §b59,264" + ), + COPPER( + ::getCopperDisplayPair, + ::getCopperShowWhen, + "Copper: §c23,495" + ), + GEMS( + ::getGemsDisplayPair, + ::getGemsShowWhen, + "Gems: §a57,873" + ), + HEAT( + ::getHeatDisplayPair, + ::getHeatShowWhen, + "Heat: §c♨ 0" + ), + NORTH_STARS( + ::getNorthStarsDisplayPair, + ::getNorthStarsShowWhen, + "North Stars: §d756" + ), + EMPTY_LINE( + ::getEmptyLineDisplayPair, + { true }, "" + ), + ISLAND( + ::getIslandDisplayPair, + { true }, + "§7㋖ §aHub" + ), + LOCATION( + ::getLocationDisplayPair, + { true }, + "§7⏣ §bVillage" + ), + VISITING( + ::getVisitDisplayPair, + ::getVisitShowWhen, + " §a✌ §7(§a1§7/6)" + ), + DATE( + ::getDateDisplayPair, + { true }, + "Late Summer 11th" + ), + TIME( + ::getTimeDisplayPair, + { true }, + "§710:40pm §b☽" + ), + LOBBY_CODE( + ::getLobbyDisplayPair, + { true }, + "§8m77CK" + ), + POWER( + ::getPowerDisplayPair, + ::getPowerShowWhen, + "Power: Sighted" + ), + COOKIE( + ::getCookieDisplayPair, + ::getCookieShowWhen, + "§d§lCookie Buff\n §f3days, 17hours" + ), + EMPTY_LINE2( + ::getEmptyLineDisplayPair, + { true }, "" + ), + OBJECTIVE( + ::getObjectiveDisplayPair, + { true }, + "Objective:\n§eUpdate SkyHanni" + ), + SLAYER( + ::getSlayerDisplayPair, + ::getSlayerShowWhen, + "§cSlayer\n §7- §cVoidgloom Seraph III\n §7- §e12§7/§c120 §7Kills" + ), + EMPTY_LINE3( + ::getEmptyLineDisplayPair, + { true }, + "" + ), + QUIVER( + ::getQuiverDisplayPair, + ::getQuiverShowWhen, + "Flint Arrow: §f1,234" + ), + POWDER( + ::getPowderDisplayPair, + ::getPowderShowWhen, + "§9§lPowder\n §7- §fMithril: §254,646\n §7- §fGemstone: §d51,234" + ), + EVENTS( + ::getEventsDisplayPair, + ::getEventsShowWhen, + "§7Wide Range of Events\n§7(too much for this here)" + ), + MAYOR( + ::getMayorDisplayPair, + ::getMayorShowWhen, + "§2Diana:\n §7- §eLucky!\n §7- §eMythological Ritual\n §7- §ePet XP Buff" + ), + PARTY( + ::getPartyDisplayPair, + ::getPartyShowWhen, + "§9§lParty (4):\n §7- §fhannibal2\n §7- §fMoulberry\n §7- §fVahvl\n §7- §fJ10a1n15" + ), + FOOTER( + ::getFooterDisplayPair, + { true }, + "§ewww.hypixel.net" + ), + EXTRA( + ::getExtraDisplayPair, + ::getExtraShowWhen, + "§cUnknown lines the mod is not detecting" + ), + ; + + override fun toString(): String { + return configLine + } + + fun getPair(): List { + return try { + displayPair.get() + } catch (e: NoSuchElementException) { + listOf("" to AlignmentEnum.LEFT) + } + } + + fun isVisible(): Boolean { + if (!informationFilteringConfig.hideIrrelevantLines) return true + return showWhen() + } +} + + +private fun getTitleDisplayPair() = when (displayConfig.titleAndFooter.useHypixelTitleAnimation) { + true -> listOf(ScoreboardData.objectiveTitle to getTitleAndFooterAlignment()) + false -> listOf( + displayConfig.titleAndFooter.customTitle.get().toString() + .replace("&", "§") to getTitleAndFooterAlignment() + ) +} + +private fun getProfileDisplayPair() = + listOf(CustomScoreboardUtils.getProfileTypeSymbol() + HypixelData.profileName.firstLetterUppercase() to AlignmentEnum.LEFT) + +private fun getPurseDisplayPair(): List { + var purse = PurseAPI.currentPurse.toString().formatNum() + + val earned = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, PurseAPI.pursePattern, "earned") + .formatNum() + + if (earned != "0") { + purse += " §7(§e+$earned§7)§6" + } + + return when { + informationFilteringConfig.hideEmptyLines && purse == "0" -> listOf("") + displayConfig.displayNumbersFirst -> listOf("§6$purse Purse") + else -> listOf("Purse: §6$purse") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getPurseShowWhen() = !listOf(IslandType.THE_RIFT).contains(HypixelData.skyBlockIsland) + +private fun getMotesDisplayPair(): List { + val motes = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.motesPattern, "motes") + .formatNum() + + return when { + informationFilteringConfig.hideEmptyLines && motes == "0" -> listOf("") + displayConfig.displayNumbersFirst -> listOf("§d$motes Motes") + else -> listOf("Motes: §d$motes") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getMotesShowWhen() = listOf(IslandType.THE_RIFT).contains(HypixelData.skyBlockIsland) + +private fun getBankDisplayPair(): List { + val bank = getGroupFromPattern(TabListData.getTabList(), ScoreboardPattern.bankPattern, "bank") + + return when { + informationFilteringConfig.hideEmptyLines && bank == "0" -> listOf("") + displayConfig.displayNumbersFirst -> listOf("§6$bank Bank") + else -> listOf("Bank: §6$bank") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getBankShowWhen() = !listOf(IslandType.THE_RIFT).contains(HypixelData.skyBlockIsland) + +private fun getBitsDisplayPair(): List { + val bits = if (BitsAPI.bits == -1) { + "0" + } else { + BitsAPI.bits.formatNum() + } + val bitsToClaim = if (BitsAPI.bitsToClaim == -1) { + "§cOpen Sbmenu§b" + } else if (BitsAPI.bitsToClaim < -1) { + "0" + } else { + BitsAPI.bitsToClaim.formatNum() + } + + val bitsDisplay = when { + informationFilteringConfig.hideEmptyLines && bits == "0" -> listOf("") + displayConfig.displayNumbersFirst -> { + val bitsText = if (displayConfig.showUnclaimedBits) { + "§b$bits§7/${if (bitsToClaim == "0") "§30" else "§b${bitsToClaim}"} §bBits" + } else { + "§b$bits Bits" + } + listOf(bitsText) + } + + else -> { + val bitsText = if (displayConfig.showUnclaimedBits) { + "Bits: §b$bits§7/${if (bitsToClaim == "0") "§30" else "§b${bitsToClaim}"}" + } else { + "Bits: §b$bits" + } + listOf(bitsText) + } + } + return bitsDisplay.map { it to AlignmentEnum.LEFT } +} + +private fun getBitsShowWhen(): Boolean { + if (HypixelData.bingo) return false + + return !listOf(IslandType.CATACOMBS).contains(HypixelData.skyBlockIsland) +} + +private fun getCopperDisplayPair(): List { + val copper = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.copperPattern, "copper") + .formatNum() + + return when { + informationFilteringConfig.hideEmptyLines && copper == "0" -> listOf("") + displayConfig.displayNumbersFirst -> listOf("§c$copper Copper") + else -> listOf("Copper: §c$copper") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getCopperShowWhen() = listOf(IslandType.GARDEN).contains(HypixelData.skyBlockIsland) + +private fun getGemsDisplayPair(): List { + val gems = getGroupFromPattern(TabListData.getTabList(), ScoreboardPattern.gemsPattern, "gems") + + return when { + informationFilteringConfig.hideEmptyLines && gems == "0" -> listOf("") + displayConfig.displayNumbersFirst -> listOf("§a$gems Gems") + else -> listOf("Gems: §a$gems") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getGemsShowWhen() = !listOf(IslandType.THE_RIFT, IslandType.CATACOMBS).contains(HypixelData.skyBlockIsland) + +private fun getHeatDisplayPair(): List { + val heat = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.heatPattern, "heat") + + return when { + informationFilteringConfig.hideEmptyLines && heat == "§c♨ 0" -> listOf("") + displayConfig.displayNumbersFirst -> listOf(if (heat == "0") "§c♨ 0 Heat" else "$heat Heat") + else -> listOf(if (heat == "0") "Heat: §c♨ 0" else "Heat: $heat") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getHeatShowWhen() = listOf(IslandType.CRYSTAL_HOLLOWS).contains(HypixelData.skyBlockIsland) + && ScoreboardData.sidebarLinesFormatted.any { ScoreboardPattern.heatPattern.matches(it) } + +private fun getNorthStarsDisplayPair(): List { + val northStars = + getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.northstarsPattern, "northstars") + .formatNum() + + return when { + informationFilteringConfig.hideEmptyLines && northStars == "0" -> listOf("") + displayConfig.displayNumbersFirst -> listOf("§d$northStars North Stars") + else -> listOf("North Stars: §d$northStars") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getNorthStarsShowWhen() = + ScoreboardData.sidebarLinesFormatted.any { ScoreboardPattern.northstarsPattern.matches(it) } + +private fun getEmptyLineDisplayPair() = listOf("" to AlignmentEnum.LEFT) + +private fun getIslandDisplayPair() = + listOf("§7㋖ §a" + HypixelData.skyBlockIsland.displayName to AlignmentEnum.LEFT) + +private fun getLocationDisplayPair() = + listOf( + (replaceString( + getGroupFromPattern( + ScoreboardData.sidebarLinesFormatted, + ScoreboardPattern.locationPattern, + "location" + ) + )?.trim() + ?: "") to AlignmentEnum.LEFT + ) + + +private fun getVisitDisplayPair() = + listOf( + ScoreboardData.sidebarLinesFormatted.first { ScoreboardPattern.visitingPattern.matches(it) } to AlignmentEnum.LEFT + ) + +private fun getVisitShowWhen() = + ScoreboardData.sidebarLinesFormatted.any { ScoreboardPattern.visitingPattern.matches(it) } + +private fun getDateDisplayPair() = + listOf(SkyBlockTime.now().formatted(yearElement = false, hoursAndMinutesElement = false) to AlignmentEnum.LEFT) + + +private fun getTimeDisplayPair(): List { + var symbol = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.timePattern, "symbol") + if (symbol == "0") symbol = "" + return listOf( + "§7" + SkyBlockTime.now() + .formatted(dayAndMonthElement = false, yearElement = false) + " $symbol" to AlignmentEnum.LEFT + ) +} + +private fun getLobbyDisplayPair(): List { + val lobbyCode = getGroupFromPattern( + ScoreboardData.sidebarLinesFormatted, + ScoreboardPattern.lobbyCodePattern, + "code" + ) + + val displayValue = if (lobbyCode == "0") "" else "§8$lobbyCode" + return listOf(displayValue to AlignmentEnum.LEFT) +} + +private fun getPowerDisplayPair() = when (MaxwellAPI.currentPower) { + MaxwellPowers.UNKNOWN -> listOf("§cOpen \"Your Bags\"!" to AlignmentEnum.LEFT) + null -> listOf("§cOpen \"Your Bags\"!" to AlignmentEnum.LEFT) + else -> + when (displayConfig.displayNumbersFirst) { + true -> listOf( + "${MaxwellAPI.currentPower?.power?.replace("Power","")} Power " + + "§7(§6${MaxwellAPI.magicalPower}§7)" to AlignmentEnum.LEFT + ) + + false -> listOf( + "Power: ${MaxwellAPI.currentPower?.power?.replace("Power","")} " + + "§7(§6${MaxwellAPI.magicalPower}§7)" to AlignmentEnum.LEFT + ) + } +} + +private fun getPowerShowWhen() = !listOf(IslandType.THE_RIFT).contains(HypixelData.skyBlockIsland) + +private fun getCookieDisplayPair(): List { + val timeLine = CustomScoreboardUtils.getTablistFooter().split("\n") + .nextAfter("§d§lCookie Buff") ?: "" + + return listOf( + "§d§lCookie Buff" to AlignmentEnum.LEFT + ) + when (timeLine.contains("Not active")) { + true -> listOf(" §7- §cNot active" to AlignmentEnum.LEFT) + false -> listOf(" §7- §e${timeLine.substringAfter("§d§lCookie Buff").trim()}" to AlignmentEnum.LEFT) + } +} + +private fun getCookieShowWhen(): Boolean { + if (HypixelData.bingo) return false + + return when (informationFilteringConfig.hideEmptyLines) { + true -> CustomScoreboardUtils.getTablistFooter().split("\n").any { + CustomScoreboardUtils.getTablistFooter().split("\n").nextAfter("§d§lCookie Buff")?.contains(it) + ?: false + } + + false -> true + } +} + +private fun getObjectiveDisplayPair(): List { + val objective = mutableListOf() + + objective += ScoreboardData.sidebarLinesFormatted.first { ScoreboardPattern.objectivePattern.matches(it) } + + objective += ScoreboardData.sidebarLinesFormatted.nextAfter(objective[0]) ?: "" + + if (ScoreboardData.sidebarLinesFormatted.any { ScoreboardPattern.thirdObjectiveLinePattern.matches(it) }) { + objective += ScoreboardData.sidebarLinesFormatted.nextAfter(objective[0], 2) ?: "Second objective here" + } + + return objective.map { it to AlignmentEnum.LEFT } +} + +private fun getSlayerDisplayPair(): List { + return listOf( + (if (SlayerAPI.hasActiveSlayerQuest()) "§cSlayer" else "") to AlignmentEnum.LEFT + ) + ( + " §7- §e${SlayerAPI.latestSlayerCategory.trim()}" to AlignmentEnum.LEFT + ) + ( + " §7- §e${SlayerAPI.latestSlayerProgress.trim()}" to AlignmentEnum.LEFT + ) +} + +private fun getSlayerShowWhen() = listOf( + IslandType.HUB, + IslandType.SPIDER_DEN, + IslandType.THE_PARK, + IslandType.THE_END, + IslandType.CRIMSON_ISLE, + IslandType.THE_RIFT +).contains(HypixelData.skyBlockIsland) + +private fun getQuiverDisplayPair(): List { + if (QuiverAPI.currentArrow == null) return listOf("§cChange your Arrow once" to AlignmentEnum.LEFT) + if (QuiverAPI.currentArrow == QuiverArrowType.NONE) return listOf("No Arrows selected" to AlignmentEnum.LEFT) + return when (displayConfig.displayNumbersFirst) { + true -> listOf("${QuiverAPI.currentAmount.addSeparators()} ${QuiverAPI.currentArrow?.arrow} ") + false -> listOf("§f${QuiverAPI.currentArrow?.arrow?.replace("Arrow", "")}: " + + "${QuiverAPI.currentAmount.addSeparators()} Arrows") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getQuiverShowWhen() = !listOf(IslandType.THE_RIFT).contains(HypixelData.skyBlockIsland) + +private fun getPowderDisplayPair(): List { + val mithrilPowder = + getGroupFromPattern(TabListData.getTabList(), ScoreboardPattern.mithrilPowderPattern, "mithrilpowder") + .formatNum() + val gemstonePowder = + getGroupFromPattern(TabListData.getTabList(), ScoreboardPattern.gemstonePowderPattern, "gemstonepowder") + .formatNum() + + return when (displayConfig.displayNumbersFirst) { + true -> listOf("§9§lPowder") + (" §7- §2$mithrilPowder Mithril") + (" §7- §d$gemstonePowder Gemstone") + false -> listOf("§9§lPowder") + (" §7- §fMithril: §2$mithrilPowder") + (" §7- §fGemstone: §d$gemstonePowder") + }.map { it to AlignmentEnum.LEFT } +} + +private fun getPowderShowWhen() = + listOf(IslandType.CRYSTAL_HOLLOWS, IslandType.DWARVEN_MINES).contains(HypixelData.skyBlockIsland) + +private fun getEventsDisplayPair(): List { + if (ScoreboardEvents.getEvent().isEmpty()) return listOf("" to AlignmentEnum.LEFT) + if (ScoreboardEvents.getEvent().flatMap { it.getLines() }.isEmpty()) return listOf("" to AlignmentEnum.LEFT) + return ScoreboardEvents.getEvent().flatMap { it.getLines().map { i -> i to AlignmentEnum.LEFT } } +} + +private fun getEventsShowWhen() = ScoreboardEvents.getEvent().isNotEmpty() + +private fun getMayorDisplayPair(): List { + return listOf( + (MayorAPI.currentMayor?.name?.let { MayorAPI.mayorNameToColorCode(it) } + ?: "") + + (if (config.mayorConfig.showTimeTillNextMayor) { + "§7 (§e${TimeUtils.formatDuration(MayorAPI.timeTillNextMayor, TimeUnit.DAY)}§7)" + } else { + "" + }) to AlignmentEnum.LEFT + ) + (if (config.mayorConfig.showMayorPerks) { + MayorAPI.currentMayor?.perks?.map { " §7- §e${it.name}" to AlignmentEnum.LEFT } ?: emptyList() + } else { + emptyList() + }) +} + +private fun getMayorShowWhen() = + !listOf(IslandType.THE_RIFT).contains(HypixelData.skyBlockIsland) && MayorAPI.currentMayor != null + +private fun getPartyDisplayPair() = + if (PartyAPI.partyMembers.isEmpty() && informationFilteringConfig.hideEmptyLines) { + listOf("" to AlignmentEnum.LEFT) + } else { + val title = + if (PartyAPI.partyMembers.isEmpty()) "§9§lParty" else "§9§lParty (${PartyAPI.partyMembers.size})" + val partyList = PartyAPI.partyMembers + .take(config.partyConfig.maxPartyList.get()) + .map { + " §7- §7$it" + } + .toTypedArray() + listOf(title, *partyList).map { it to AlignmentEnum.LEFT } + } + +private fun getPartyShowWhen() = when (inDungeons) { + true -> false // Hidden bc the scoreboard lines already exist + false -> when (config.partyConfig.showPartyEverywhere) { + true -> true + false -> listOf( + IslandType.DUNGEON_HUB, + IslandType.KUUDRA_ARENA, + IslandType.CRIMSON_ISLE + ).contains(HypixelData.skyBlockIsland) + } +} + +private fun getFooterDisplayPair(): List { + return listOf( + displayConfig.titleAndFooter.customFooter.get().toString() + .replace("&", "§") to getTitleAndFooterAlignment() + ) +} + +private fun getExtraDisplayPair(): List { + if (unknownLines.isEmpty()) return listOf("" to AlignmentEnum.LEFT) + + if (amountOfUnknownLines != unknownLines.size && config.unknownLinesWarning) { + ErrorManager.logErrorWithData( + CustomScoreboardUtils.UndetectedScoreboardLines("CustomScoreboard detected ${unknownLines.size} unknown line${if (unknownLines.size > 1) "s" else ""}"), + "CustomScoreboard detected ${unknownLines.size} unknown line${if (unknownLines.size > 1) "s" else ""}", + "Unknown Lines" to unknownLines, + "Island" to HypixelData.skyBlockIsland, + "Area" to HypixelData.skyBlockArea + ) + amountOfUnknownLines = unknownLines.size + } + + return listOf("§cUndetected Lines:" to AlignmentEnum.LEFT) + unknownLines.map { it to AlignmentEnum.LEFT } +} + +private fun getExtraShowWhen(): Boolean { + if (unknownLines.isEmpty()) { + amountOfUnknownLines = 0 + } + return unknownLines.isNotEmpty() +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardEvents.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardEvents.kt new file mode 100644 index 000000000000..3f5e5c8ae67e --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardEvents.kt @@ -0,0 +1,532 @@ +package at.hannibal2.skyhanni.features.misc.customscoreboard + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.ScoreboardData +import at.hannibal2.skyhanni.features.misc.ServerRestartTitle +import at.hannibal2.skyhanni.features.misc.customscoreboard.ScoreboardEvents.VOTING +import at.hannibal2.skyhanni.features.misc.customscoreboard.ScoreboardPattern +import at.hannibal2.skyhanni.features.rift.area.stillgorechateau.RiftBloodEffigies +import at.hannibal2.skyhanni.utils.LorenzUtils.inDungeons +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.LorenzUtils.nextAfter +import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.TabListData +import java.util.function.Supplier +import at.hannibal2.skyhanni.features.misc.customscoreboard.ScoreboardPattern as SbPattern + +private val config get() = SkyHanniMod.feature.gui.customScoreboard + +/** + * This enum contains all the lines that either are events or other lines that are so rare/not often seen that they + * don't fit in the normal [ScoreboardElements] enum. + * + * We for example have the [VOTING] Event, while this is clearly not an event, I don't consider them as normal lines + * because they are visible for a maximum of like 1 minute every 5 days and ~12 hours. + */ + +private fun getSbLines(): List { + return ScoreboardData.sidebarLinesFormatted +} + +enum class ScoreboardEvents(private val displayLine: Supplier>, private val showWhen: () -> Boolean) { + VOTING( + ::getVotingLines, + ::getVotingShowWhen + ), + SERVER_CLOSE( + ::getServerCloseLines, + ::getServerCloseShowWhen + ), + DUNGEONS( + ::getDungeonsLines, + ::getDungeonsShowWhen + ), + KUUDRA( + ::getKuudraLines, + ::getKuudraShowWhen + ), + DOJO( + ::getDojoLines, + ::getDojoShowWhen + ), + DARK_AUCTION( // This will use regex once PR #823 is merged + ::getDarkAuctionLines, + ::getDarkAuctionShowWhen + ), + JACOB_CONTEST( + ::getJacobContestLines, + ::getJacobContestShowWhen + ), + JACOB_MEDALS( + ::getJacobMedalsLines, + ::getJacobMedalsShowWhen + ), + TRAPPER( + ::getTrapperLines, + ::getTrapperShowWhen + ), + GARDEN_CLEAN_UP( + ::getGardenCleanUpLines, + ::getGardenCleanUpShowWhen + ), + GARDEN_PASTING( + ::getGardenPastingLines, + ::getGardenPastingShowWhen + ), + FLIGHT_DURATION( + ::getFlightDurationLines, + ::getFlightDurationShowWhen + ), + WINTER( + ::getWinterLines, + ::getWinterShowWhen + ), + SPOOKY( + ::getSpookyLines, + ::getSpookyShowWhen + ), + MARINA( + ::getMarinaLines, + ::getMarinaShowWhen + ), + BROODMOTHER( + ::getBroodmotherLines, + ::getBroodmotherShowWhen + ), + NEW_YEAR( + ::getNewYearLines, + ::getNewYearShowWhen + ), + ORINGO( + ::getOringoLines, + ::getOringoShowWhen + ), + MINING_EVENTS( + ::getMiningEventsLines, + ::getMiningEventsShowWhen + ), + DAMAGE( + ::getDamageLines, + ::getDamageShowWhen + ), + MAGMA_BOSS( + ::getMagmaBossLines, + ::getMagmaBossShowWhen + ), + ESSENCE( + ::getEssenceLines, + ::getEssenceShowWhen + ), + EFFIGIES( + ::getEffigiesLines, + ::getEffigiesShowWhen + ), + REDSTONE( + ::getRedstoneLines, + ::getRedstoneShowWhen + ), + + NONE( // maybe use default state tablist: "Events: smth" + ::getNoneLines, + { false } + ); + + fun getLines(): List { + return displayLine.get() + } + + companion object { + fun getEvent(): List { + if (config.displayConfig.showAllActiveEvents) { + return entries.filter { it.showWhen() } + } + return listOf(entries.firstOrNull { it.showWhen() } ?: NONE) + } + } +} + +private fun getVotingLines(): List { + val list = mutableListOf() + + getSbLines().firstOrNull { SbPattern.yearVotesPattern.matches(it) }?.let { list.add(it) } + + if (getSbLines().nextAfter(list[0]) == "§7Waiting for") { + list += "§7Waiting for" + list += "§7your vote..." + } else { + if (getSbLines().any { SbPattern.votesPattern.matches(it) }) { + list += getSbLines().filter { SbPattern.votesPattern.matches(it) } + } + } + + return list +} + +private fun getVotingShowWhen(): Boolean { + return getSbLines().any { SbPattern.yearVotesPattern.matches(it) } +} + +private fun getServerCloseLines(): List { + return listOf(getSbLines().first { ServerRestartTitle.restartingPattern.matches(it) }.split("§8")[0]) +} + +private fun getServerCloseShowWhen(): Boolean { + return getSbLines().any { ServerRestartTitle.restartingPattern.matches(it) } +} + +private fun getDungeonsLines(): List { + val list = mutableListOf() + + getSbLines().firstOrNull { SbPattern.autoClosingPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.startingInPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.keysPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.timeElapsedPattern.matches(it) }?.let { list.add(it) } + // BetterMap adds a random §r at the start, making it go black + getSbLines().firstOrNull { SbPattern.clearedPattern.matches(it) }?.let { list.add(it.replace("§r", "")) } + getSbLines().firstOrNull { SbPattern.soloPattern.matches(it) }?.let { list.add(it) } + list.addAll(getSbLines().filter { SbPattern.teammatesPattern.matches(it) }) + list.addAll(getSbLines().filter { SbPattern.floor3GuardiansPattern.matches(it) }) + + return list +} + +private fun getDungeonsShowWhen(): Boolean { + return IslandType.CATACOMBS.isInIsland() || inDungeons +} + +private fun getKuudraLines(): List { + val list = mutableListOf() + + getSbLines().firstOrNull { SbPattern.autoClosingPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.startingInPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.timeElapsedPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.instanceShutdownPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.wavePattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.tokensPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.submergesPattern.matches(it) }?.let { list.add(it) } + list += "" + + if (getSbLines().any { ScoreboardPattern.thirdObjectiveLinePattern.matches(it) }) { + val objectiveLine = getSbLines().first { ScoreboardPattern.thirdObjectiveLinePattern.matches(it) } + list += objectiveLine + list += getSbLines().nextAfter(objectiveLine) ?: "§cNo Objective" + if (getSbLines().any { ScoreboardPattern.thirdObjectiveLinePattern.matches(it) }) { + list += getSbLines().nextAfter(objectiveLine, 2) ?: "§cNo Objective" + } + } + + return list +} + +private fun getKuudraShowWhen(): Boolean { + return IslandType.KUUDRA_ARENA.isInIsland() +} + +private fun getDojoLines(): List { + val list = mutableListOf() + + getSbLines().firstOrNull { SbPattern.dojoChallengePattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.dojoDifficultyPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.dojoPointsPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.dojoTimePattern.matches(it) }?.let { list.add(it) } + + return list +} + +private fun getDojoShowWhen(): Boolean { + return getSbLines().any { ScoreboardPattern.dojoChallengePattern.matches(it) } +} + +private fun getDarkAuctionLines(): List { + val list = mutableListOf() + + getSbLines().firstOrNull { SbPattern.startingInPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.timeLeftPattern.matches(it) }?.let { list.add(it) } + if (getSbLines().any { SbPattern.daCurrentItemPattern.matches(it) }) { + list += getSbLines().first { SbPattern.daCurrentItemPattern.matches(it) } + list += getSbLines().nextAfter( + getSbLines().first { SbPattern.daCurrentItemPattern.matches(it) } + ) ?: "§7No Item" + } + + return list +} + +private fun getDarkAuctionShowWhen(): Boolean { + return IslandType.DARK_AUCTION.isInIsland() +} + +private fun getJacobContestLines(): List { + val list = mutableListOf() + + list += "§eJacob's Contest" + list += getSbLines().nextAfter("§eJacob's Contest") ?: "§7No Event" + list += getSbLines().nextAfter("§eJacob's Contest", 2) ?: "§7No Ranking" + list += getSbLines().nextAfter("§eJacob's Contest", 3) ?: "§7No Amount for next" + + return list +} + +private fun getJacobContestShowWhen(): Boolean { + return getSbLines().any { it.startsWith("§e○ §f") || it.startsWith("§6☘ §f") } +} + +private fun getJacobMedalsLines(): List { + return getSbLines().filter { SbPattern.medalsPattern.matches(it) } +} + +private fun getJacobMedalsShowWhen(): Boolean { + return getSbLines().any { SbPattern.medalsPattern.matches(it) } +} + +private fun getTrapperLines(): List { + val list = mutableListOf() + + getSbLines().firstOrNull { SbPattern.peltsPattern.matches(it) }?.let { list.add(it) } + + if (getSbLines().any { SbPattern.mobLocationPattern.matches(it) }) { + list += "Tracker Mob Location:" + list += getSbLines().nextAfter(ScoreboardData.sidebarLinesFormatted.first { + ScoreboardPattern.mobLocationPattern.matches(it) + }) ?: "" + } + + return list +} + +private fun getTrapperShowWhen(): Boolean { + return getSbLines().any { + ScoreboardPattern.peltsPattern.matches(it) || ScoreboardPattern.mobLocationPattern.matches(it) + } +} + +private fun getGardenCleanUpLines(): List { + return listOf(getSbLines().first { SbPattern.cleanUpPattern.matches(it) }.trim()) +} + +private fun getGardenCleanUpShowWhen(): Boolean { + return getSbLines().any { SbPattern.cleanUpPattern.matches(it) } +} + +private fun getGardenPastingLines(): List { + return listOf(getSbLines().first { SbPattern.pastingPattern.matches(it) }.trim()) +} + +private fun getGardenPastingShowWhen(): Boolean { + return getSbLines().any { SbPattern.pastingPattern.matches(it) } +} + +private fun getFlightDurationLines(): List { + return listOf(getSbLines().first { SbPattern.flightDurationPattern.matches(it) }.trim()) +} + +private fun getFlightDurationShowWhen(): Boolean { + return getSbLines().any { SbPattern.flightDurationPattern.matches(it) } +} + +private fun getWinterLines(): List { + val list = mutableListOf() + + getSbLines().firstOrNull { SbPattern.winterEventStartPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.winterNextWavePattern.matches(it) && !it.endsWith("Soon!") } + ?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.winterWavePattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.winterMagmaLeftPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.winterTotalDmgPattern.matches(it) }?.let { list.add(it) } + getSbLines().firstOrNull { SbPattern.winterCubeDmgPattern.matches(it) }?.let { list.add(it) } + + return list +} + +private fun getWinterShowWhen(): Boolean { + return getSbLines().any { ScoreboardPattern.winterEventStartPattern.matches(it) } + || getSbLines().any { ScoreboardPattern.winterNextWavePattern.matches(it) && !it.endsWith("Soon!") } + || getSbLines().any { ScoreboardPattern.winterWavePattern.matches(it) } +} + +private fun getSpookyLines(): List { + return listOf(getSbLines().first { ScoreboardPattern.spookyPattern.matches(it) }) + // Time + ("§7Your Candy: ") + + (CustomScoreboardUtils.getTablistFooter().split("\n").firstOrNull { it.startsWith("§7Your Candy:") } + ?.removePrefix("§7Your Candy:") ?: "§cCandy not found") // Candy +} + +private fun getSpookyShowWhen(): Boolean { + return getSbLines().any { ScoreboardPattern.spookyPattern.matches(it) } +} + +private fun getMarinaLines(): List { + return listOf( + "§bFishing Festival: " + TabListData.getTabList().nextAfter("§e§lEvent: §r§bFishing Festival") + ?.removePrefix(" Ends In: ") + ) +} + +private fun getMarinaShowWhen(): Boolean { + return TabListData.getTabList() + .any { it.startsWith("§e§lEvent: §r§bFishing Festival") } && TabListData.getTabList() + .nextAfter("§e§lEvent: §r§bFishing Festival")?.startsWith(" Ends In: ") == true +} + +private fun getBroodmotherLines(): List { + return listOf(getSbLines().first { SbPattern.broodmotherPattern.matches(it) }) +} + +private fun getBroodmotherShowWhen(): Boolean { + return getSbLines().any { SbPattern.broodmotherPattern.matches(it) } +} + +private fun getNewYearLines(): List { + return listOf(getSbLines().first { SbPattern.newYearPattern.matches(it) }) +} + +private fun getNewYearShowWhen(): Boolean { + return getSbLines().any { SbPattern.newYearPattern.matches(it) } +} + +private fun getOringoLines(): List { + return listOf(getSbLines().first { SbPattern.travelingZooPattern.matches(it) }) +} + +private fun getOringoShowWhen(): Boolean { + return getSbLines().any { SbPattern.travelingZooPattern.matches(it) } +} + +private fun getMiningEventsLines(): List { + val list = mutableListOf() + + // Mining Fiesta + if (TabListData.getTabList().any { it == "§e§lEvent: §r§6Mining Fiesta" } + && TabListData.getTabList().nextAfter("§e§lEvent: §r§6Mining Fiesta") + ?.startsWith(" Ends In:") == true) { + list += "§6Mining Fiesta: " + TabListData.getTabList().nextAfter("§e§lEvent: §r§6Mining Fiesta") + ?.removePrefix(" Ends In: ") + } + + // Wind + if (getSbLines().any { SbPattern.windCompassPattern.matches(it) }) { + list += getSbLines().first { SbPattern.windCompassPattern.matches(it) } + list += ("| ${getSbLines().first { SbPattern.windCompassArrowPattern.matches(it) }} §f|") + } + + // Better Together + if (getSbLines().any { ScoreboardPattern.nearbyPlayersPattern.matches(it) }) { + list += "§dBetter Together" + list += (" " + getSbLines().first { ScoreboardPattern.nearbyPlayersPattern.matches(it) }) + } + + // Zone Events + if ( + getSbLines().any { ScoreboardPattern.miningEventPattern.matches(it) } + && getSbLines().any { ScoreboardPattern.miningEventZonePattern.matches(it) } + ) { + list += getSbLines().first { ScoreboardPattern.miningEventPattern.matches(it) } + .removePrefix("Event: ") + list += "in " + getSbLines().first { ScoreboardPattern.miningEventZonePattern.matches(it) } + .removePrefix("Zone: ") + } + + // Zone Events but no Zone Line + if ( + getSbLines().any { ScoreboardPattern.miningEventPattern.matches(it) } + && getSbLines().none { ScoreboardPattern.miningEventZonePattern.matches(it) } + ) { + list += getSbLines().first { ScoreboardPattern.miningEventPattern.matches(it) } + .removePrefix("Event: ") + } + + // Mithril Gourmand + if ( + getSbLines().any { ScoreboardPattern.mithrilRemainingPattern.matches(it) } + && getSbLines().any { ScoreboardPattern.mithrilYourMithrilPattern.matches(it) } + ) { + list += getSbLines().first { ScoreboardPattern.mithrilRemainingPattern.matches(it) } + list += getSbLines().first { ScoreboardPattern.mithrilYourMithrilPattern.matches(it) } + } + + // raffle + if ( + getSbLines().any { ScoreboardPattern.raffleTicketsPattern.matches(it) } + && getSbLines().any { ScoreboardPattern.rafflePoolPattern.matches(it) } + ) { + list += getSbLines().first { ScoreboardPattern.raffleTicketsPattern.matches(it) } + list += getSbLines().first { ScoreboardPattern.rafflePoolPattern.matches(it) } + } + + // raid + if ( + getSbLines().any { ScoreboardPattern.yourGoblinKillsPattern.matches(it) } + && getSbLines().any { ScoreboardPattern.remainingGoblinPattern.matches(it) } + ) { + list += getSbLines().first { ScoreboardPattern.yourGoblinKillsPattern.matches(it) } + list += getSbLines().first { ScoreboardPattern.remainingGoblinPattern.matches(it) } + } + + return list +} + +private fun getMiningEventsShowWhen(): Boolean { + return IslandType.DWARVEN_MINES.isInIsland() || IslandType.CRYSTAL_HOLLOWS.isInIsland() +} + +private fun getDamageLines(): List { + return listOf(getSbLines().first { SbPattern.bossHPPattern.matches(it) }) + + (getSbLines().first { SbPattern.bossDamagePattern.matches(it) }) +} + +private fun getDamageShowWhen(): Boolean { + return getSbLines().any { SbPattern.bossHPPattern.matches(it) || SbPattern.bossDamagePattern.matches(it) } +} + +private fun getMagmaBossLines(): List { + val list = listOf( + SbPattern.magmaBossPattern, + SbPattern.damageSoakedPattern, + SbPattern.killMagmasPattern, + SbPattern.killMagmasDamagedSoakedBarPattern, + SbPattern.reformingPattern, + SbPattern.bossHealthPattern, + SbPattern.bossHealthBarPattern + ) + .mapNotNull { pattern -> + getSbLines().firstOrNull { pattern.matches(it) } + } + + return list +} + +private fun getMagmaBossShowWhen(): Boolean { + return at.hannibal2.skyhanni.data.HypixelData.skyBlockArea == "Magma Chamber" +} + +private fun getEssenceLines(): List { + return listOf(getSbLines().first { SbPattern.essencePattern.matches(it) }) +} + +private fun getEssenceShowWhen(): Boolean { + return getSbLines().any { SbPattern.essencePattern.matches(it) } +} + +private fun getEffigiesLines(): List { + return listOf(getSbLines().first { RiftBloodEffigies.heartsPattern.matches(it) }) +} + +private fun getEffigiesShowWhen(): Boolean { + return getSbLines().any { RiftBloodEffigies.heartsPattern.matches(it) } +} + +private fun getRedstoneLines(): List { + return listOf(getSbLines().first { SbPattern.redstonePattern.matches(it) }) +} + +private fun getRedstoneShowWhen(): Boolean { + return getSbLines().any { SbPattern.redstonePattern.matches(it) } +} + +private fun getNoneLines(): List { + return when { + config.informationFilteringConfig.hideEmptyLines -> listOf("") + else -> listOf("§cNo Event") + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardPattern.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardPattern.kt new file mode 100644 index 000000000000..8bda2abbd054 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/customscoreboard/ScoreboardPattern.kt @@ -0,0 +1,125 @@ +package at.hannibal2.skyhanni.features.misc.customscoreboard + +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern + +// "Regex is torture" ~J10a1n15 (26.12.2023) +// Thank you Ery for helping with some regexes! + +object ScoreboardPattern { + val group = RepoPattern.group("features.misc.customscoreboard") + + // Stats from the scoreboard + private val scoreboardGroup = group.group("scoreboard") + // main scoreboard + private val mainSb = scoreboardGroup.group("main") + val motesPattern by mainSb.pattern("motes", "^(§.)*Motes: (§.)*(?[\\d,]+).*$") + val heatPattern by mainSb.pattern("heat", "^Heat: (?.*)$") // this line is weird (either text or number), ill leave it as is; it even has different colors? + val copperPattern by mainSb.pattern("copper", "^(§.)*Copper: (§.)*(?[\\d,]+).*$") + val locationPattern by mainSb.pattern("location", "^\\s*(?(§7⏣|§5ф) .*)$") + val lobbyCodePattern by mainSb.pattern ("lobbycode", "^\\s*§.((\\d{2}/\\d{2}/\\d{2})|Server closing: [\\d:]+) §8(?.*)\$") + val datePattern by mainSb.pattern("date", "^\\s*(Late |Early )?(Spring|Summer|Autumn|Winter) \\d{1,2}(st|nd|rd|th)?") + val timePattern by mainSb.pattern("time", "^\\s*§7\\d{1,2}:\\d{2}(?:am|pm) (?(§b☽|§e☀|§.⚡|§.☔)).*$") + val footerPattern by mainSb.pattern("footer", "§e(www|alpha).hypixel.net\$") + val yearVotesPattern by mainSb.pattern("yearvotes", "(?^§6Year \\d+ Votes\$)") + val votesPattern by mainSb.pattern("votes", "(?§[caebd]\\|+§f\\|+ §(.+)\$)") + val waitingForVotePattern by mainSb.pattern("waitingforvote", "(§7Waiting for|§7your vote\\.\\.\\.)$") + val northstarsPattern by mainSb.pattern("northstars", "North Stars: §d(?[\\w,]+).*$") + val profileTypePattern by mainSb.pattern("profiletype", "^\\s*(§7♲ §7Ironman|§a☀ §aStranded|§.Ⓑ §.Bingo).*$") + // multi use + private val multiUseSb = scoreboardGroup.group("multiuse") + val autoClosingPattern by multiUseSb.pattern("autoclosing", "(§.)*Auto-closing in: §c(\\d{1,2}:)?\\d{1,2}$") + val startingInPattern by multiUseSb.pattern("startingin", "(§.)*Starting in: §.(\\d{1,2}:)?\\d{1,2}$") + val timeElapsedPattern by multiUseSb.pattern("timeelapsed", "(§.)*Time Elapsed: (§.)*(?