diff options
author | J10a1n15 <45315647+j10a1n15@users.noreply.github.com> | 2024-03-12 19:59:55 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-12 19:59:55 +0100 |
commit | 4352ffb08d4bfffc06adad2a068f375ab9874333 (patch) | |
tree | b8ce745fca8ab82ffa04f6ab87334139a6b7192b /src | |
parent | eb863d60e82b5541a9f42d2608f61cb97ada209b (diff) | |
download | skyhanni-4352ffb08d4bfffc06adad2a068f375ab9874333.tar.gz skyhanni-4352ffb08d4bfffc06adad2a068f375ab9874333.tar.bz2 skyhanni-4352ffb08d4bfffc06adad2a068f375ab9874333.zip |
Feature: CustomScoreboard (#893)
Co-authored-by: Thunderblade73 <85900443+Thunderblade73@users.noreply.github.com>
Co-authored-by: Thunderblade73 <gaidermarkus@gmail.com>
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Diffstat (limited to 'src')
63 files changed, 3524 insertions, 154 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 6c05aadbd..d7d68ba78 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -11,12 +11,14 @@ import at.hannibal2.skyhanni.config.SackData import at.hannibal2.skyhanni.config.commands.Commands.init import at.hannibal2.skyhanni.data.ActionBarData import at.hannibal2.skyhanni.data.ActionBarStatsData +import at.hannibal2.skyhanni.data.BitsAPI import at.hannibal2.skyhanni.data.BlockData import at.hannibal2.skyhanni.data.BossbarData import at.hannibal2.skyhanni.data.ChatManager import at.hannibal2.skyhanni.data.CropAccessoryData import at.hannibal2.skyhanni.data.EntityData import at.hannibal2.skyhanni.data.EntityMovementData +import at.hannibal2.skyhanni.data.FameRanks import at.hannibal2.skyhanni.data.FriendAPI import at.hannibal2.skyhanni.data.GardenComposterUpgradesData import at.hannibal2.skyhanni.data.GardenCropMilestones @@ -30,7 +32,8 @@ 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 @@ -38,6 +41,7 @@ import at.hannibal2.skyhanni.data.PartyAPI import at.hannibal2.skyhanni.data.PetAPI 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 @@ -284,6 +288,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.gui.customscoreboard.CustomScoreboard +import at.hannibal2.skyhanni.features.gui.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 @@ -445,7 +451,6 @@ class SkyHanniMod { loadModule(GetFromSackAPI) loadModule(UpdateManager) loadModule(CropAccessoryData()) - loadModule(MayorElection()) loadModule(GardenComposterUpgradesData()) loadModule(ActionBarStatsData) loadModule(GardenCropMilestoneInventory()) @@ -464,6 +469,7 @@ class SkyHanniMod { loadModule(GardenBestCropTime()) loadModule(ActionBarData) loadModule(TrackerManager) + loadModule(ScoreboardPattern) loadModule(UtilsPatterns) loadModule(PetAPI) loadModule(BossbarData) @@ -485,7 +491,12 @@ class SkyHanniMod { loadModule(RiftAPI) loadModule(SackAPI) loadModule(BingoAPI) + loadModule(FameRanks) loadModule(FishingAPI) + loadModule(MaxwellAPI) + loadModule(QuiverAPI) + loadModule(BitsAPI) + loadModule(MayorAPI) loadModule(SkillAPI) loadModule(IsFishingDetection) loadModule(LorenzUtils) @@ -748,6 +759,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/Storage.java b/src/main/java/at/hannibal2/skyhanni/config/Storage.java index 597d3a7eb..95286550c 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/Storage.java +++ b/src/main/java/at/hannibal2/skyhanni/config/Storage.java @@ -1,5 +1,6 @@ package at.hannibal2.skyhanni.config; +import at.hannibal2.skyhanni.data.FameRank; import at.hannibal2.skyhanni.api.SkillAPI; import at.hannibal2.skyhanni.data.model.ComposterUpgrade; import at.hannibal2.skyhanni.features.bingo.card.goals.BingoGoal; @@ -78,6 +79,9 @@ public class Storage { @Expose public Map<UUID, PlayerSpecific> players = new HashMap<>(); + @Expose + public String currentFameRank = null; + public static class PlayerSpecific { @Expose @@ -147,6 +151,39 @@ public class Storage { public String currentPet = ""; @Expose + public MaxwellPowerStorage maxwell = new MaxwellPowerStorage(); + + public static class MaxwellPowerStorage { + @Expose + public String currentPower = null; + + @Expose + public int magicalPower = -1; + } + + @Expose + public ArrowsStorage arrows = new ArrowsStorage(); + + public static class ArrowsStorage { + @Expose + public String currentArrow = null; + + @Expose + public Map<NEUInternalName, Float> arrowAmount = new HashMap<>(); + } + + @Expose + public BitsStorage bits = new BitsStorage(); + + public static class BitsStorage { + @Expose + public int bits = -1; + + @Expose + public int bitsToClaim = -1; + } + + @Expose public Map<LorenzVec, MinionConfig> minions = new HashMap<>(); public static class MinionConfig { diff --git a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt index f6765ca2b..88264e994 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt @@ -19,6 +19,7 @@ package at.hannibal2.skyhanni.config.core.config.gui 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 @@ -216,6 +217,7 @@ class GuiPositionEditor(private val positions: List<Position>, private val borde val elementHeight = position.getDummySize(true).y grabbedX += position.moveX(mouseX - grabbedX, elementWidth) grabbedY += position.moveY(mouseY - grabbedY, elementHeight) + GuiEditManager.handleGuiPositionMoved(position.internalName) } } 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 89170c027..3b5200c39 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 4eeadf221..10b6cdde1 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 @@ package at.hannibal2.skyhanni.config.features.gui; 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; @@ -29,6 +31,10 @@ public class GUIConfig { 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 public ModifyWordsConfig modifyWords = new ModifyWordsConfig(); 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 000000000..a5a8170f3 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/AlignmentConfig.java @@ -0,0 +1,20 @@ +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 { + // TODO: Switch to Dropdowns with multiple different alignment ways in the future + // Horizontal: Left, Center, Right + // Vertical: Top, Center, Bottom + @Expose + @ConfigOption(name = "Align to the right", desc = "Align the scoreboard to the right side of the screen.") + @ConfigEditorBoolean + public boolean alignRight = true; + + @Expose + @ConfigOption(name = "Align to the center vertically", desc = "Align the scoreboard to the center of the screen vertically.") + @ConfigEditorBoolean + public boolean alignCenterVertically = true; +} 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 000000000..3b4924511 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/BackgroundConfig.java @@ -0,0 +1,66 @@ +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.ConfigEditorSlider; +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 = "Background Border Size", + desc = "The size of the border around the background." + ) + @ConfigEditorSlider( + minValue = 0, + maxValue = 20, + minStep = 1 + ) + public int borderSize = 5; + + @Expose + @ConfigOption( + name = "Rounded Corner Smoothness", + desc = "The smoothness of the rounded corners." + ) + @ConfigEditorSlider( + minValue = 0, + maxValue = 30, + minStep = 1 + ) + public int roundedCornerSmoothness = 10; + + @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 000000000..4941caf5c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/CustomScoreboardConfig.java @@ -0,0 +1,65 @@ +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.gui.customscoreboard.ScoreboardElement; +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.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<ScoreboardElement> scoreboardEntries = new ArrayList<>(ScoreboardElement.getEntries()); + + @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 000000000..f009533ba --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/DisplayConfig.java @@ -0,0 +1,96 @@ +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 = true; + + @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 = "Arrow Amount Display", desc = "Determines how the arrow amount is displayed.") + @ConfigEditorDropdown + public ArrowAmountDisplay arrowAmountDisplay = ArrowAmountDisplay.NUMBER; + + public enum ArrowAmountDisplay { + NUMBER("Number"), + PERCENTAGE("Percentage"), + ; + + private final String str; + + ArrowAmountDisplay(String str) { + this.str = str; + } + + @Override + public String toString() { + return str; + } + } + + @Expose + @ConfigOption(name = "Color Arrow Amount", desc = "Color the arrow amount based on the percentage.") + @ConfigEditorBoolean + public boolean colorArrowAmount = false; + + @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 000000000..02a8f83a0 --- /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 000000000..5a47e5a48 --- /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 000000000..2850882b7 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/PartyConfig.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.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 = 1, + maxValue = 25, // why do I even set it so high + minStep = 1 + ) + public Property<Integer> 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 000000000..d88115dff --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/TitleAndFooterConfig.java @@ -0,0 +1,32 @@ +package at.hannibal2.skyhanni.config.features.gui.customscoreboard; + +import at.hannibal2.skyhanni.utils.RenderUtils; +import com.google.gson.annotations.Expose; +import io.github.moulberry.moulconfig.annotations.ConfigEditorBoolean; +import io.github.moulberry.moulconfig.annotations.ConfigEditorDropdown; +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 = "Title and Footer Alignment", desc = "Align the title and footer in the scoreboard.") + @ConfigEditorDropdown + public RenderUtils.HorizontalAlignment alignTitleAndFooter = RenderUtils.HorizontalAlignment.CENTER; + + @Expose + @ConfigOption(name = "Custom Title", desc = "What should be displayed as the title of the scoreboard.\nUse & for colors.") + @ConfigEditorText + public Property<String> customTitle = Property.of("&6&lSKYBLOCK"); + + @Expose + @ConfigOption(name = "Hypixel's Title Animation", desc = "Will overwrite the custom title with Hypixel's title animation." + + "\nWill also include \"COOP\" if you are in a coop.") + @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<String> customFooter = Property.of("&ewww.hypixel.net"); +} 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 077bd8303..40b078961 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 @@ -137,7 +137,7 @@ public class MiscConfig { public boolean hidePiggyScoreboard = true; @Expose - @ConfigOption(name = "Color Month Names", desc = "Color the month names in the Scoreboard.") + @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; diff --git a/src/main/java/at/hannibal2/skyhanni/data/ArrowType.kt b/src/main/java/at/hannibal2/skyhanni/data/ArrowType.kt new file mode 100644 index 000000000..5ce7dd33f --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/ArrowType.kt @@ -0,0 +1,9 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.utils.NEUInternalName + +data class ArrowType(val arrow: String, val internalName: NEUInternalName) { + override fun toString(): String { + return internalName.asString() + } +} 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 000000000..2802399bf --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/BitsAPI.kt @@ -0,0 +1,200 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.FameRanks.getFameRankByNameOrNull +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.ScoreboardChangeEvent +import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.ItemUtils.getLore +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.NumberUtil.formatInt +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpace +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object BitsAPI { + private val profileStorage get() = ProfileStorageData.profileSpecific?.bits + private val playerStorage get() = SkyHanniMod.feature.storage + + var bits: Int + get() = profileStorage?.bits ?: 0 + private set(value) { + profileStorage?.bits = value + } + var currentFameRank: FameRank? + get() = playerStorage?.currentFameRank?.let { getFameRankByNameOrNull(it) } + private set(value) { + if (value != null) { + playerStorage?.currentFameRank = value.name + } + } + var bitsToClaim: Int + get() = profileStorage?.bitsToClaim ?: 0 + private set(value) { + profileStorage?.bitsToClaim = value + } + + private const val defaultcookiebits = 4800 + + private val bitsDataGroup = RepoPattern.group("data.bits") + + // Scoreboard patterns + val bitsScoreboardPattern by bitsDataGroup.pattern( + "scoreboard", + "^Bits: §b(?<amount>[\\d,.]+).*$" + ) + + // Chat patterns + private val bitsChatGroup = bitsDataGroup.group("chat") + + private val bitsFromFameRankUpChatPattern by bitsChatGroup.pattern( + "famerankup", + "§eYou gained §3(?<amount>.*) Bits Available §ecompounded from all your §epreviously eaten §6cookies§e! Click here to open §6cookie menu§e!" + ) + + private val boosterCookieAte by bitsChatGroup.pattern( + "boostercookieate", + "§eYou consumed a §6Booster Cookie§e!.*" + ) + + // GUI patterns + private val bitsGuiGroup = bitsDataGroup.group("gui") + + private val bitsAvailableMenuPattern by bitsGuiGroup.pattern( + "availablemenu", + "§7Bits Available: §b(?<toClaim>[\\d,]+)(§3.+)?" + ) + + private val fameRankSbMenuPattern by bitsGuiGroup.pattern( + "sbmenufamerank", + "§7Your rank: §e(?<rank>.*)" + ) + + private val fameRankCommunityShopPattern by bitsGuiGroup.pattern( + "communityshopfamerank", + "§7Fame Rank: §e(?<rank>.*)" + ) + + private val bitsGuiNamePattern by bitsGuiGroup.pattern( + "mainmenuname", + "^SkyBlock Menu$" + ) + + private val bitsGuiStackPattern by bitsGuiGroup.pattern( + "mainmenustack", + "^§6Booster Cookie$" + ) + + private val fameRankGuiNamePattern by bitsGuiGroup.pattern( + "famerankmenuname", + "^(Community Shop|Booster Cookie)$" + ) + + private val fameRankGuiStackPattern by bitsGuiGroup.pattern( + "famerankmenustack", + "^(§aCommunity Shop|§eFame Rank)$" + ) + + @SubscribeEvent + fun onScoreboardChange(event: ScoreboardChangeEvent) { + if (!isEnabled()) return + for (line in event.newList) { + val message = line.trimWhiteSpace().removeResets() + + bitsScoreboardPattern.matchMatcher(message) { + val amount = group("amount").formatInt() + + if (amount > bits) { + bitsToClaim -= amount - bits + ChatUtils.debug("You have gained §3${amount - bits} Bits §7according to the scoreboard!") + } + bits = amount + + return + } + } + } + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!isEnabled()) return + val message = event.message.trimWhiteSpace().removeResets() + + bitsFromFameRankUpChatPattern.matchMatcher(message) { + val amount = group("amount").formatInt() + bitsToClaim += amount + + return + } + + boosterCookieAte.matchMatcher(message) { + bitsToClaim += (defaultcookiebits * (currentFameRank?.bitsMultiplier ?: return)).toInt() + + return + } + } + + @SubscribeEvent + fun onInventoryFullyLoaded(event: InventoryFullyOpenedEvent) { + if (!isEnabled()) return + + val stacks = event.inventoryItems + + if (bitsGuiNamePattern.matches(event.inventoryName)) { + val cookieStack = stacks.values.lastOrNull { bitsGuiStackPattern.matches(it.displayName) } ?: return + for (line in cookieStack.getLore()) { + bitsAvailableMenuPattern.matchMatcher(line) { + bitsToClaim = group("toClaim").formatInt() + + return + } + } + return + } + + if (fameRankGuiNamePattern.matches(event.inventoryName)) { + val fameRankStack = stacks.values.lastOrNull { fameRankGuiStackPattern.matches(it.displayName) } ?: return + + line@ for (line in fameRankStack.getLore()) { + fameRankCommunityShopPattern.matchMatcher(line) { + val rank = group("rank") + + currentFameRank = getFameRankByNameOrNull(rank) + ?: return ErrorManager.logErrorWithData( + FameRankNotFoundException(rank), + "FameRank $rank not found", + "Rank" to rank, + "Lore" to fameRankStack.getLore(), + "FameRanks" to FameRanks.fameRanks + ) + + continue@line + } + + fameRankSbMenuPattern.matchMatcher(line) { + val rank = group("rank") + + currentFameRank = getFameRankByNameOrNull(rank) + ?: return ErrorManager.logErrorWithData( + FameRankNotFoundException(rank), + "FameRank $rank not found", + "Rank" to rank, + "Lore" to fameRankStack.getLore(), + "FameRanks" to FameRanks.fameRanks + ) + + continue@line + } + } + } + } + + fun isEnabled() = LorenzUtils.inSkyBlock && profileStorage != null + + class FameRankNotFoundException(rank: String) : Exception("FameRank not found: $rank") +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/FameRanks.kt b/src/main/java/at/hannibal2/skyhanni/data/FameRanks.kt new file mode 100644 index 000000000..663e2b970 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/FameRanks.kt @@ -0,0 +1,26 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.data.jsonobjects.repo.FameRankJson +import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object FameRanks { + var fameRanks = emptyMap<String, FameRank>() + private set + + fun getFameRankByNameOrNull(name: String) = fameRanks[name] + + @SubscribeEvent + fun onRepoReload(event: RepositoryReloadEvent) { + val ranks = event.getConstant<FameRankJson>("FameRank") + fameRanks = ranks.fame_rank.values.map { FameRank(it.name, it.fame_required, it.bits_multiplier, it.votes) } + .associateBy { it.name } + } +} + +data class FameRank( + val name: String, + val fameRequired: Int, + val bitsMultiplier: Double, + val electionVotes: Int +) diff --git a/src/main/java/at/hannibal2/skyhanni/data/GuiEditManager.kt b/src/main/java/at/hannibal2/skyhanni/data/GuiEditManager.kt index 4fbebe958..ee9a6a23a 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/GuiEditManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/GuiEditManager.kt @@ -3,8 +3,10 @@ package at.hannibal2.skyhanni.data import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.config.core.config.Position import at.hannibal2.skyhanni.config.core.config.gui.GuiPositionEditor +import at.hannibal2.skyhanni.events.GuiPositionMovedEvent import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.events.LorenzKeyPressEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent import at.hannibal2.skyhanni.test.SkyHanniDebugsAndTests import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.LorenzUtils.isRancherSign @@ -51,11 +53,20 @@ class GuiEditManager { currentPositions.clear() } + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + lastMovedGui?.let { + GuiPositionMovedEvent(it).postAndCatch() + lastMovedGui = null + } + } + companion object { var currentPositions = mutableMapOf<String, Position>() private var latestPositions = mapOf<String, Position>() private var currentBorderSize = mutableMapOf<String, Pair<Int, Int>>() + private var lastMovedGui: String? = null @JvmStatic fun add(position: Position, posLabel: String, x: Int, y: Int) { @@ -116,6 +127,10 @@ class GuiEditManager { fun GuiProfileViewer.anyTextBoxFocused() = this.getPropertiesWithType<GuiElementTextField>().any { it.focus } + + fun handleGuiPositionMoved(guiName: String) { + lastMovedGui = guiName + } } } diff --git a/src/main/java/at/hannibal2/skyhanni/data/HypixelData.kt b/src/main/java/at/hannibal2/skyhanni/data/HypixelData.kt index b206eb565..c347fcdba 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/HypixelData.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/HypixelData.kt @@ -300,9 +300,12 @@ class HypixelData { private fun checkIsland() { var newIsland = "" var guesting = false + TabListData.fullyLoaded = false + for (line in TabListData.getTabList()) { islandNamePattern.matchMatcher(line) { newIsland = group("island").removeColor() + TabListData.fullyLoaded = true } if (line == " Status: §r§9Guest") { guesting = true 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 000000000..0f19854f7 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/MaxwellAPI.kt @@ -0,0 +1,152 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.data.jsonobjects.repo.MaxwellPowersJson +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.test.command.ErrorManager +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.NumberUtil.formatInt +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpace +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern +import net.minecraft.enchantment.Enchantment +import net.minecraft.enchantment.Enchantment.power +import net.minecraft.item.ItemStack +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +object MaxwellAPI { + + private val storage get() = ProfileStorageData.profileSpecific + + var currentPower: String? + get() = storage?.maxwell?.currentPower + set(value) { + storage?.maxwell?.currentPower = value ?: return + } + var magicalPower: Int? + get() = storage?.maxwell?.magicalPower + set(value) { + storage?.maxwell?.magicalPower = value ?: return + } + + private var powers = mutableListOf<String>() + + private val group = RepoPattern.group("data.maxwell") + private val chatPowerpattern by group.pattern( + "chat.power", + "§eYou selected the §a(?<power>.*) §e(power )?for your §aAccessory Bag§e!" + ) + private val inventoryPowerPattern by group.pattern( + "inventory.power", + "§7Selected Power: §a(?<power>.*)" + ) + private val inventoryMPPattern by group.pattern( + "inventory.magicalpower", + "§7Magical Power: §6(?<mp>[\\d,]+)" + ) + private val thaumaturgyGuiPattern by group.pattern( + "gui.thaumaturgy", + "Accessory Bag Thaumaturgy" + ) + private val yourBagsGuiPattern by group.pattern( + "gui.yourbags", + "Your Bags" + ) + private val powerSelectedPattern by group.pattern( + "gui.selectedpower", + "§aPower is selected!" + ) + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!isEnabled()) return + val message = event.message.trimWhiteSpace().removeResets() + + chatPowerpattern.matchMatcher(message) { + val power = group("power") + currentPower = getPowerByNameOrNull(power) + ?: return ErrorManager.logErrorWithData( + UnknownMaxwellPower("Unknown power: $power"), + "Unknown power: $power", + "power" to power, + "message" to message + ) + } + } + + @SubscribeEvent + fun onInventoryFullyLoaded(event: InventoryFullyOpenedEvent) { + if (!isEnabled()) return + + if (thaumaturgyGuiPattern.matches(event.inventoryName)) { + val selectedPowerStack = + event.inventoryItems.values.find { + powerSelectedPattern.matches(it.getLore().lastOrNull()) + } ?: return + val displayName = selectedPowerStack.displayName.removeColor() + + currentPower = getPowerByNameOrNull(displayName) + ?: return ErrorManager.logErrorWithData( + UnknownMaxwellPower("Unknown power: $power"), + "Unknown power: $power", + "power" to power, + "displayName" to displayName, + "lore" to selectedPowerStack.getLore() + ) + return + } + + if (yourBagsGuiPattern.matches(event.inventoryName)) { + val stacks = event.inventoryItems + + for (stack in stacks.values) { + processStack(stack) + } + } + } + + private fun processStack(stack: ItemStack) { + for (line in stack.getLore()) { + inventoryMPPattern.matchMatcher(line) { + // MagicalPower is boosted in catacombs + if (IslandType.CATACOMBS.isInIsland()) return@matchMatcher + + val mp = group("mp") + magicalPower = mp.formatInt() + return@matchMatcher + } + + inventoryPowerPattern.matchMatcher(line) { + val power = group("power") + currentPower = getPowerByNameOrNull(power) + ?: return@matchMatcher ErrorManager.logErrorWithData( + UnknownMaxwellPower("Unknown power: ${Enchantment.power}"), + "Unknown power: ${Enchantment.power}", + "power" to Enchantment.power, + "displayName" to stack.displayName, + "lore" to stack.getLore() + ) + return@matchMatcher + } + } + } + + private fun getPowerByNameOrNull(name: String) = powers.find { it == name } + + fun isEnabled() = LorenzUtils.inSkyBlock && storage != null + + // Load powers from repo + @SubscribeEvent + fun onRepoLoad(event: RepositoryReloadEvent) { + val data = event.getConstant<MaxwellPowersJson>("MaxwellPowers") + powers = data.powers + } + + class UnknownMaxwellPower(message: String) : Exception(message) +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt new file mode 100644 index 000000000..bc937a5a2 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt @@ -0,0 +1,121 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.config.ConfigManager +import at.hannibal2.skyhanni.data.Mayor.Companion.setMayorWithActivePerks +import at.hannibal2.skyhanni.data.jsonobjects.local.MayorJson +import at.hannibal2.skyhanni.events.DebugDataCollectEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.utils.APIUtil +import at.hannibal2.skyhanni.utils.CollectionUtils.put +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.SimpleTimeMark +import at.hannibal2.skyhanni.utils.SimpleTimeMark.Companion.asTimeMark +import io.github.moulberry.notenoughupdates.util.SkyBlockTime +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes + +object MayorAPI { + var lastUpdate = SimpleTimeMark.farPast() + private var dispatcher = Dispatchers.IO + + private var rawMayorData: MayorJson? = null + var candidates = mapOf<Int, MayorJson.Candidate>() + private set + var currentMayor: Mayor? = null + private set + var timeTillNextMayor = Duration.ZERO + private set + + private const val ELECTION_END_MONTH = 3 //Late Spring + private const val ELECTION_END_DAY = 27 + + /** + * @param input: The name of the mayor + * @return: The neu color of the mayor; If no mayor was found, it will return "§cUnknown: §7" + */ + fun mayorNameToColorCode(input: String): String { + return Mayor.getMayorFromName(input).color + } + + /** + * @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: §7[input]" + */ + fun mayorNameWithColorCode(input: String) = mayorNameToColorCode(input) + input + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!LorenzUtils.onHypixel) return + + if (event.repeatSeconds(2)) { + checkHypixelAPI() + getTimeTillNextMayor() + } + } + + private fun calculateNextMayorTime(): SimpleTimeMark { + var mayorYear = SkyBlockTime.now().year + + // Check if either the month is already over or the day is after 27th in the third month + if (SkyBlockTime.now().month > ELECTION_END_MONTH || (SkyBlockTime.now().day >= ELECTION_END_DAY && SkyBlockTime.now().month == ELECTION_END_MONTH)) { + // If so, the next mayor will be in the next year + mayorYear++ + } + + return SkyBlockTime(mayorYear, ELECTION_END_MONTH, day = ELECTION_END_DAY).asTimeMark() + } + + private fun getTimeTillNextMayor() { + val nextMayorTime = calculateNextMayorTime() + timeTillNextMayor = nextMayorTime - SimpleTimeMark.now() + } + + private fun checkCurrentMayor() { + val nextMayorTime = calculateNextMayorTime() + + // Check if it is still the mayor from the old SkyBlock year + currentMayor = candidates[nextMayorTime.toSkyBlockTime().year - 1]?.let { + // TODO: Once Jerry is active, add the sub mayor perks in here + setMayorWithActivePerks(it.name, it.perks) + } + } + + private fun checkHypixelAPI() { + if (lastUpdate.passedSince() < 20.minutes) return + lastUpdate = SimpleTimeMark.now() + + SkyHanniMod.coroutineScope.launch { + val url = "https://api.hypixel.net/v2/resources/skyblock/election" + val jsonObject = withContext(dispatcher) { APIUtil.getJSONResponse(url) } + rawMayorData = ConfigManager.gson.fromJson(jsonObject, MayorJson::class.java) + val data = rawMayorData ?: return@launch + val map = mutableMapOf<Int, MayorJson.Candidate>() + map put data.mayor.election.getPairs() + data.current?.let { + map put data.current.getPairs() + } + candidates = map + checkCurrentMayor() + } + } + + private fun MayorJson.Election.getPairs() = year + 1 to candidates.bestCandidate() + + private fun List<MayorJson.Candidate>.bestCandidate() = maxBy { it.votes } + + @SubscribeEvent + fun onDebugDataCollect(event: DebugDataCollectEvent) { + event.title("Mayor") + event.addIrrelevant { + add("Current Mayor: ${currentMayor?.name ?: "Unknown"}") + add("Active Perks: ${currentMayor?.activePerks}") + add("Last Update: $lastUpdate (${lastUpdate.passedSince()} ago)") + add("Time Till Next Mayor: $timeTillNextMayor") + } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/MayorElection.kt b/src/main/java/at/hannibal2/skyhanni/data/MayorElection.kt deleted file mode 100644 index e73a6bf14..000000000 --- a/src/main/java/at/hannibal2/skyhanni/data/MayorElection.kt +++ /dev/null @@ -1,79 +0,0 @@ -package at.hannibal2.skyhanni.data - -import at.hannibal2.skyhanni.SkyHanniMod -import at.hannibal2.skyhanni.config.ConfigManager -import at.hannibal2.skyhanni.data.jsonobjects.local.MayorJson -import at.hannibal2.skyhanni.events.LorenzTickEvent -import at.hannibal2.skyhanni.utils.APIUtil -import at.hannibal2.skyhanni.utils.CollectionUtils.put -import at.hannibal2.skyhanni.utils.LorenzUtils -import at.hannibal2.skyhanni.utils.SimpleTimeMark -import io.github.moulberry.notenoughupdates.util.SkyBlockTime -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import kotlin.time.Duration.Companion.minutes - -class MayorElection { - - private var lastUpdate = SimpleTimeMark.farPast() - private var dispatcher = Dispatchers.IO - - companion object { - - var rawMayorData: MayorJson? = null - var candidates = mapOf<Int, MayorJson.Candidate>() - var currentCandidate: MayorJson.Candidate? = null - - fun isPerkActive(mayor: String, perk: String) = currentCandidate?.let { currentCandidate -> - currentCandidate.name == mayor && currentCandidate.perks.any { it.name == perk } - } ?: false - } - - @SubscribeEvent - fun onTick(event: LorenzTickEvent) { - if (!LorenzUtils.onHypixel) return - - if (event.repeatSeconds(3)) { - check() - } - } - - private fun check() { - if (lastUpdate.passedSince() < 20.minutes) return - lastUpdate = SimpleTimeMark.now() - - SkyHanniMod.coroutineScope.launch { - val url = "https://api.hypixel.net/v2/resources/skyblock/election" - val jsonObject = withContext(dispatcher) { APIUtil.getJSONResponse(url) } - rawMayorData = ConfigManager.gson.fromJson(jsonObject, MayorJson::class.java) - val data = rawMayorData ?: return@launch - val map = mutableMapOf<Int, MayorJson.Candidate>() - map put data.mayor.election.getPairs() - data.current?.let { - map put data.current.getPairs() - } - candidates = map - checkCurrentMayor() - } - } - - private fun checkCurrentMayor() { - var currentYear = SkyBlockTime.now().year - - // The time in the current SkyBlock year when the election circle will restart - val month = 3 // Late Spring - val nextMayorTime = SkyBlockTime(currentYear, month, day = 27).toMillis() - - // Is it still the mayor from old sb year? - if (nextMayorTime > System.currentTimeMillis()) { - currentYear-- - } - currentCandidate = candidates[currentYear] - } - - private fun MayorJson.Election.getPairs() = year + 1 to candidates.bestCandidate() - - private fun List<MayorJson.Candidate>.bestCandidate() = maxBy { it.votes } -} diff --git a/src/main/java/at/hannibal2/skyhanni/data/Mayors.kt b/src/main/java/at/hannibal2/skyhanni/data/Mayors.kt new file mode 100644 index 000000000..cd6205c7a --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/Mayors.kt @@ -0,0 +1,105 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.data.jsonobjects.local.MayorJson + +enum class Mayor( + val mayorName: String, + val color: String, + private vararg val perks: Perk, +) { + AATROX("Aatrox", "§3", Perk.SLASHED_PRICING, Perk.SLAYER_XP_BUFF, Perk.PATHFINDER), + COLE("Cole", "§e", Perk.PROSPECTION, Perk.MINING_XP_BUFF, Perk.MINING_FIESTA), + DIANA("Diana", "§2", Perk.LUCKY, Perk.MYTHOLOGICAL_RITUAL, Perk.PET_XP_BUFF), + DIAZ("Diaz", "§6", Perk.BARRIER_STREET, Perk.SHOPPING_SPREE), + FINNEGAN("Finnegan", "§c", Perk.FARMING_SIMULATOR, Perk.PELT_POCALYPSE, Perk.GOATED), + FOXY("Foxy", "§d", Perk.SWEET_TOOTH, Perk.BENEVOLENCE, Perk.EXTRA_EVENT), + MARINA("Marina", "§b", Perk.FISHING_XP_BUFF, Perk.LUCK_OF_THE_SEA, Perk.FISHING_FESTIVAL), + PAUL("Paul", "§c", Perk.MARAUDER, Perk.EZPZ, Perk.BENEDICTION), + + SCORPIUS("Scorpius", "§d", Perk.BRIBE, Perk.DARKER_AUCTIONS), + JERRY("Jerry", "§d", Perk.PERKPOCALYPSE, Perk.STATSPOCALYPSE, Perk.JERRYPOCALYPSE), + DERPY("Derpy", "§d", Perk.TURBO_MINIONS, Perk.AH_CLOSED, Perk.DOUBLE_MOBS_HP, Perk.MOAR_SKILLZ), + + UNKNOWN("Unknown", "§c"), + ; + + val activePerks: MutableList<Perk> = mutableListOf() + + companion object { + fun getMayorFromName(name: String) = entries.firstOrNull { it.mayorName == name } ?: UNKNOWN + + fun setMayorWithActivePerks(name: String, perks: ArrayList<MayorJson.Perk>): Mayor { + val mayor = getMayorFromName(name) + + mayor.perks.forEach { it.isActive = false } + mayor.activePerks.clear() + perks.mapNotNull { perk -> Perk.entries.firstOrNull { it.perkName == perk.name } } + .filter { mayor.perks.contains(it) }.forEach { + it.isActive = true + mayor.activePerks.add(it) + } + + return mayor + } + } +} + +enum class Perk(val perkName: String) { + // Aatrox + SLASHED_PRICING("SLASHED Pricing"), + SLAYER_XP_BUFF("Slayer XP Buff"), + PATHFINDER("Pathfinder"), + + // Cole + PROSPECTION("Prospection"), + MINING_XP_BUFF("Mining XP Buff"), + MINING_FIESTA("Mining Fiesta"), + + // Diana + LUCKY("Lucky!"), + MYTHOLOGICAL_RITUAL("Mythological Ritual"), + PET_XP_BUFF("Pet XP Buff"), + + // Diaz + BARRIER_STREET("Barrier Street"), + SHOPPING_SPREE("Shopping Spree"), + + // Finnegan + FARMING_SIMULATOR("Farming Simulator"), + PELT_POCALYPSE("Pelt-pocalypse"), + GOATED("GOATed"), + + // Foxy + SWEET_TOOTH("Sweet Tooth"), + BENEVOLENCE("Benevolence"), + EXTRA_EVENT("Extra Event"), + + // Marina + FISHING_XP_BUFF("Fishing XP Buff"), + LUCK_OF_THE_SEA("Luck of the Sea 2.0"), + FISHING_FESTIVAL("Fishing Festival"), + + // Paul + MARAUDER("Marauder"), + EZPZ("EZPZ"), + BENEDICTION("Benediction"), + + + // Scorpius + BRIBE("Bribe"), + DARKER_AUCTIONS("Darker Auctions"), + + // Jerry + PERKPOCALYPSE("Perkpocalypse"), + STATSPOCALYPSE("Statspocalypse"), + JERRYPOCALYPSE("Jerrypocalypse"), + + // Derpy + TURBO_MINIONS("TURBO MINIONS!!!"), + AH_CLOSED("AH CLOSED!!!"), + DOUBLE_MOBS_HP("DOUBLE MOBS HP!!!"), + MOAR_SKILLZ("MOAR SKILLZ!!!"), + ; + + var isActive = false +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt index 22fb8e4f8..ace56f67f 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt @@ -8,7 +8,7 @@ import at.hannibal2.skyhanni.utils.StringUtils.cleanPlayerName import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import at.hannibal2.skyhanni.utils.StringUtils.removeColor import at.hannibal2.skyhanni.utils.StringUtils.removeResets -import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpaceAndResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpace import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import kotlin.random.Random @@ -94,7 +94,7 @@ object PartyAPI { @SubscribeEvent fun onChat(event: LorenzChatEvent) { - val message = event.message.trimWhiteSpaceAndResets().removeResets() + val message = event.message.trimWhiteSpace().removeResets() // new member joined youJoinedPartyPattern.matchMatcher(message) { diff --git a/src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt index 90ea15627..f4266f800 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt @@ -4,8 +4,8 @@ import at.hannibal2.skyhanni.events.InventoryCloseEvent 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.formatDouble +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 @@ -13,7 +13,7 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent object PurseAPI { private val patternGroup = RepoPattern.group("data.purse") - private val coinsPattern by patternGroup.pattern( + val coinsPattern by patternGroup.pattern( "coins", "(§.)*(Piggy|Purse): §6(?<coins>[\\d,.]+)( ?(§.)*\\([+-](?<earned>[\\d,.]+)\\)?|.*)?$" ) @@ -22,8 +22,9 @@ object PurseAPI { "Piggy: (?<coins>.*)" ) - private var currentPurse = 0.0 private var inventoryCloseTime = 0L + var currentPurse = 0.0 + private set @SubscribeEvent fun onInventoryClose(event: InventoryCloseEvent) { @@ -32,10 +33,9 @@ object PurseAPI { @SubscribeEvent fun onTick(event: LorenzTickEvent) { - for (line in ScoreboardData.sidebarLinesFormatted) { val newPurse = coinsPattern.matchMatcher(line) { - group("coins").formatNumber().toDouble() + group("coins").formatDouble() } ?: continue val diff = newPurse - currentPurse if (diff == 0.0) continue @@ -52,7 +52,7 @@ object 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 000000000..603b9963b --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt @@ -0,0 +1,256 @@ +package at.hannibal2.skyhanni.data + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.data.jsonobjects.repo.ArrowTypeJson +import at.hannibal2.skyhanni.data.jsonobjects.repo.ItemsJson +import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent +import at.hannibal2.skyhanni.events.LorenzChatEvent +import at.hannibal2.skyhanni.events.PlaySoundEvent +import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.CollectionUtils.addOrPut +import at.hannibal2.skyhanni.utils.InventoryUtils +import at.hannibal2.skyhanni.utils.ItemCategory +import at.hannibal2.skyhanni.utils.ItemUtils.getInternalNameOrNull +import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.LorenzUtils.round +import at.hannibal2.skyhanni.utils.NEUInternalName +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.matches +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpace +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 { + private val storage get() = ProfileStorageData.profileSpecific + var currentArrow: ArrowType? + get() = storage?.arrows?.currentArrow?.asInternalName()?.let { getArrowByNameOrNull(it) } ?: NONE_ARROW_TYPE + set(value) { + storage?.arrows?.currentArrow = value?.toString() ?: return + } + var arrowAmount: MutableMap<NEUInternalName, Float> + get() = storage?.arrows?.arrowAmount ?: mutableMapOf() + set(value) { + storage?.arrows?.arrowAmount = value + } + var currentAmount: Int + get() = arrowAmount[currentArrow?.internalName]?.toInt() ?: 0 + set(value) { + arrowAmount[currentArrow?.internalName ?: return] = value.toFloat() + } + + private var arrows: List<ArrowType> = listOf() + + const val MAX_ARROW_AMOUNT = 2880 + private val SKELETON_MASTER_CHESTPLATE = "SKELETON_MASTER_CHESTPLATE".asInternalName() + + var NONE_ARROW_TYPE: ArrowType? = null + private var FLINT_ARROW_TYPE: ArrowType? = null + + private val group = RepoPattern.group("data.quiver") + private val chatGroup = group.group("chat") + private val selectPattern by chatGroup.pattern("select", "§aYou set your selected arrow type to §f(?<arrow>.*)§a!") + private val fillUpJaxPattern by chatGroup.pattern( + "fillupjax", + "(§.)*Jax forged (§.)*(?<type>.*?)(§.)* x(?<amount>[\\d,]+)( (§.)*for (§.)*(?<coins>[\\d,]+) Coins)?(§.)*!" + ) + private val fillUpPattern by chatGroup.pattern( + "fillup", + "§aYou filled your quiver with §f(?<flintAmount>.*) §aextra arrows!" + ) + private val clearedPattern by chatGroup.pattern("cleared", "§aCleared your quiver!") + private val arrowResetPattern by chatGroup.pattern("arrowreset", "§cYour favorite arrow has been reset!") + private val addedToQuiverPattern by chatGroup.pattern( + "addedtoquiver", + "(§.)*You've added (§.)*(?<type>.*) x(?<amount>.*) (§.)*to your quiver!" + ) + + // Bows that don't use the players arrows, checked using the SkyBlock Id + private val fakeBowsPattern by group.pattern("fakebows", "^(BOSS_SPIRIT_BOW|CRYPT_BOW)$") + private val quiverInventoryNamePattern by group.pattern("quivername", "^Quiver$") + + @SubscribeEvent + fun onChat(event: LorenzChatEvent) { + if (!isEnabled()) return + val message = event.message.trimWhiteSpace().removeResets() + + selectPattern.matchMatcher(message) { + val type = group("arrow") + currentArrow = getArrowByNameOrNull(type) + ?: return ErrorManager.logErrorWithData( + UnknownArrowType("Unknown arrow type: $type"), + "Unknown arrow type: $type", + "message" to message, + ) + return + } + + fillUpJaxPattern.matchMatcher(message) { + val type = group("type") + val amount = group("amount").formatNumber().toFloat() + val filledUpType = getArrowByNameOrNull(type) + ?: return ErrorManager.logErrorWithData( + UnknownArrowType("Unknown arrow type: $type"), + "Unknown arrow type: $type", + "message" to message, + ) + + arrowAmount.addOrPut(filledUpType.internalName, amount) + return + } + + fillUpPattern.matchMatcher(message) { + val flintAmount = group("flintAmount").formatNumber().toFloat() + + FLINT_ARROW_TYPE?.let { arrowAmount.addOrPut(it.internalName, flintAmount) } + return + } + + addedToQuiverPattern.matchMatcher(message) { + val type = group("type") + val amount = group("amount").formatNumber().toFloat() + + val filledUpType = getArrowByNameOrNull(type) + ?: return ErrorManager.logErrorWithData( + UnknownArrowType("Unknown arrow type: $type"), + "Unknown arrow type: $type", + "message" to message, + ) + + arrowAmount.addOrPut(filledUpType.internalName, amount) + return + } + + clearedPattern.matchMatcher(message) { + currentAmount = 0 + arrowAmount.clear() + + return + } + + arrowResetPattern.matchMatcher(message) { + currentArrow = NONE_ARROW_TYPE + currentAmount = 0 + + return + } + } + + @SubscribeEvent + fun onInventoryFullyLoaded(event: InventoryFullyOpenedEvent) { + if (!isEnabled()) return + if (!quiverInventoryNamePattern.matches(event.inventoryName)) return + + // clear to prevent duplicates + currentAmount = 0 + arrowAmount.clear() + + val stacks = event.inventoryItems + for (stack in stacks.values) { + if (stack.getItemCategoryOrNull() != ItemCategory.ARROW) continue + + val arrow = stack.getInternalNameOrNull() ?: continue + + val arrowType = getArrowByNameOrNull(arrow) ?: continue + + arrowAmount.addOrPut(arrowType.internalName, stack.stackSize.toFloat()) + } + } + + /* + Modified method to remove arrows from SkyblockFeatures QuiverOverlay + Original method source: + https://github.com/MrFast-js/SkyblockFeatures/blob/ae4bf0b91ed0fb17114d9cdaccaa9aef9a6c8d01/src/main/java/mrfast/sbf/features/overlays/QuiverOverlay.java#L127 + + Changes made: + - Added "fake bows" check + - Added "infinite quiver" check + - Added "sneaking" check + - Added "bow sound distance" check + - Added "skeleton master chestplate" check + */ + @SubscribeEvent(priority = EventPriority.HIGHEST) + fun onPlaySound(event: PlaySoundEvent) { + if (!isEnabled()) return + if (event.soundName != "random.bow") return + + val holdingBow = InventoryUtils.getItemInHand()?.item is ItemBow + && !fakeBowsPattern.matches(InventoryUtils.getItemInHand()?.getInternalNameOrNull()?.asString() ?: "") + + if (!holdingBow) return + + // check if sound location is more than configAmount block away from player + val soundLocation = event.distanceToPlayer + if (soundLocation > SkyHanniMod.feature.dev.bowSoundDistance) return + + val arrowType = currentArrow?.internalName ?: return + val amount = arrowAmount[arrowType] ?: return + if (amount <= 0) return + + if (InventoryUtils.getChestplate() + // The chestplate has the ability to not use arrows + // https://hypixel-skyblock.fandom.com/wiki/Skeleton_Master_Armor + ?.getInternalNameOrNull() == SKELETON_MASTER_CHESTPLATE + ) return + + val infiniteQuiverLevel = InventoryUtils.getItemInHand()?.getEnchantments()?.get("infinite_quiver") ?: 0 + + val amountToRemove = { + when (Minecraft.getMinecraft().thePlayer.isSneaking) { + true -> 1.0f + false -> { + when (infiniteQuiverLevel) { + in 1..10 -> 1 - (infinityQuiverLevelMultiplier * infiniteQuiverLevel) + else -> 1.0f + } + } + } + } + + arrowAmount[arrowType] = amount - amountToRemove() + } + + fun Int.asArrowPercentage() = ((this.toFloat() / MAX_ARROW_AMOUNT) * 100).round(1) + + fun hasBowInInventory(): Boolean { + return InventoryUtils.getItemsInOwnInventory().any { it.item is ItemBow } + } + + fun getArrowByNameOrNull(name: String): ArrowType? { + return arrows.firstOrNull { it.arrow == name } + } + + fun getArrowByNameOrNull(internalName: NEUInternalName): ArrowType? { + return arrows.firstOrNull { it.internalName == internalName } + } + + private fun NEUInternalName.asArrowTypeOrNull() = getArrowByNameOrNull(this) + + fun isEnabled() = LorenzUtils.inSkyBlock && storage != null + + + // Load arrows from repo + @SubscribeEvent + fun onRepoReload(event: RepositoryReloadEvent) { + val itemData = event.getConstant<ItemsJson>("Items") + infinityQuiverLevelMultiplier = itemData.enchant_multiplier["infinite_quiver"] ?: 0.03f + + val arrowData = event.getConstant<ArrowTypeJson>("ArrowTypes") + arrows = arrowData.arrows.map { ArrowType(it.value.arrow, it.key.asInternalName()) } + + NONE_ARROW_TYPE = getArrowByNameOrNull("NONE".asInternalName()) + FLINT_ARROW_TYPE = getArrowByNameOrNull("FLINT".asInternalName()) + } + + class UnknownArrowType(message: String) : Exception(message) +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt b/src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt index 661330f92..b2cb66ef0 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt @@ -35,13 +35,23 @@ class ScoreboardData { fun formatLines(rawList: List<String>): List<String> { val list = mutableListOf<String>() 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) { - end = end.substring(2) + // get last color code in start + val lastColorIndex = start.lastIndexOf('§') + val lastColor = when { + lastColorIndex != -1 && lastColorIndex + 1 < start.length && (start[lastColorIndex + 1] in '0'..'9' || start[lastColorIndex + 1] in 'a'..'f') -> start.substring( + lastColorIndex, + lastColorIndex + 2 + ) + else -> "" } + + // remove first color code from end, when it is the same as the last color code in start + end = end.removePrefix(lastColor) + list.add(start + end) } diff --git a/src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt index 296e2db65..6ecf6935c 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt @@ -30,7 +30,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/ArrowTypeJson.java b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/ArrowTypeJson.java new file mode 100644 index 000000000..3388cb62c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/ArrowTypeJson.java @@ -0,0 +1,14 @@ +package at.hannibal2.skyhanni.data.jsonobjects.repo; + +import com.google.gson.annotations.Expose; +import java.util.Map; + +public class ArrowTypeJson { + @Expose + public Map<String, ArrowAttributes> arrows; + + public static class ArrowAttributes { + @Expose + public String arrow; + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/FameRankJson.java b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/FameRankJson.java new file mode 100644 index 000000000..e060d55d1 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/FameRankJson.java @@ -0,0 +1,24 @@ +package at.hannibal2.skyhanni.data.jsonobjects.repo; + +import com.google.gson.annotations.Expose; + +import java.util.Map; + +public class FameRankJson { + @Expose + public Map<String, FameRank> fame_rank; + + public static class FameRank { + @Expose + public String name; + + @Expose + public int fame_required; + + @Expose + public double bits_multiplier; + + @Expose + public int votes; + } +} 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 ddd814159..0efeeb9fd 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 @@ -14,6 +14,9 @@ public class ItemsJson { public Map<String, Integer> crimson_tiers; @Expose + public Map<String, Float> enchant_multiplier; + + @Expose public List<NEUInternalName> lava_fishing_rods; @Expose diff --git a/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/MaxwellPowersJson.java b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/MaxwellPowersJson.java new file mode 100644 index 000000000..6ac70abb6 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/MaxwellPowersJson.java @@ -0,0 +1,10 @@ +package at.hannibal2.skyhanni.data.jsonobjects.repo; + +import com.google.gson.annotations.Expose; + +import java.util.List; + +public class MaxwellPowersJson { + @Expose + public List<String> powers; +} diff --git a/src/main/java/at/hannibal2/skyhanni/events/GuiPositionMovedEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/GuiPositionMovedEvent.kt new file mode 100644 index 000000000..3d8fe02f9 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/events/GuiPositionMovedEvent.kt @@ -0,0 +1,3 @@ +package at.hannibal2.skyhanni.events + +class GuiPositionMovedEvent(val guiName: String) : LorenzEvent() diff --git a/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoAPI.kt index fc884c91d..bee916507 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/bingo/BingoAPI.kt @@ -72,6 +72,8 @@ object BingoAPI { fun getRankFromScoreboard(text: String) = if (detectionPattern.matches(text)) getRank(text) else null + fun getIconFromScoreboard(text: String) = getRankFromScoreboard(text)?.let { getIcon(it) } + fun getRank(text: String) = ranks.entries.find { text.contains(it.key) }?.value fun getIcon(searchRank: Int) = ranks.entries.find { it.value == searchRank }?.key 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 7a6fa1d17..e1e420ab4 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.Perk 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() = Perk.MYTHOLOGICAL_RITUAL.isActive || + Perk.PERKPOCALYPSE.isActive || SkyHanniMod.feature.event.diana.alwaysDiana fun hasGriffinPet() = PetAPI.isCurrentPet("Griffin") diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt index 399967809..f9999fe2c 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt @@ -33,7 +33,7 @@ object FarmingContestAPI { "crop", "§8(?<crop>.*) Contest" ) - private val sidebarCropPattern by patternGroup.pattern( + val sidebarCropPattern by patternGroup.pattern( "sidebarcrop", "(?:§e○|§6☘) §f(?<crop>.*) §a.*" ) 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 2da5a21eb..7dc92b8d9 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.Perk import at.hannibal2.skyhanni.data.jsonobjects.repo.DicerDropsJson import at.hannibal2.skyhanni.data.jsonobjects.repo.DicerDropsJson.DicerType import at.hannibal2.skyhanni.events.CropClickEvent @@ -175,7 +175,7 @@ object GardenCropSpeed { fun finneganPerkActive(): Boolean { val forcefullyEnabledAlwaysFinnegan = config.forcefullyEnabledAlwaysFinnegan - val perkActive = MayorElection.isPerkActive("Finnegan", "Farming Simulator") + val perkActive = Perk.FARMING_SIMULATOR.isActive return forcefullyEnabledAlwaysFinnegan || perkActive } diff --git a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/CustomScoreboard.kt b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/CustomScoreboard.kt new file mode 100644 index 000000000..5e15166c0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/CustomScoreboard.kt @@ -0,0 +1,130 @@ +// +// 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 +// - CookieAPI https://discord.com/channels/997079228510117908/1162844830360146080/1195695210433351821 +// - Rng meter display +// - shorten time till next mayor https://discord.com/channels/997079228510117908/1162844830360146080/1216440046320746596 +// - option to hide coins earned +// - color options in the purse etc lines +// - choose the amount of decimal places in shorten nums +// - very important bug fix: duplex is weird :( +// + +package at.hannibal2.skyhanni.features.gui.customscoreboard + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.GuiPositionMovedEvent +import at.hannibal2.skyhanni.events.GuiRenderEvent +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.utils.ChatUtils +import at.hannibal2.skyhanni.utils.LorenzUtils +import at.hannibal2.skyhanni.utils.RenderUtils.HorizontalAlignment +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 ScoreboardElementType = Pair<String, HorizontalAlignment> + +class CustomScoreboard { + + private var display = emptyList<ScoreboardElementType>() + private var cache = emptyList<ScoreboardElementType>() + private val guiName = "Custom Scoreboard" + + @SubscribeEvent + fun onRenderOverlay(event: GuiRenderEvent.GuiOverlayRenderEvent) { + if (!isEnabled()) return + if (display.isEmpty()) return + + RenderBackground().renderBackground() + + val render = + if (!TabListData.fullyLoaded && config.displayConfig.cacheScoreboardOnIslandSwitch && cache.isNotEmpty()) { + cache + } else { + display + } + config.position.renderStringsAlignedWidth(render, posLabel = guiName) + } + + @SubscribeEvent + fun onGuiPositionMoved(event: GuiPositionMovedEvent) { + if (event.guiName == guiName) { + val alignmentConfig = config.displayConfig.alignment + if (alignmentConfig.alignRight || alignmentConfig.alignCenterVertically) { + alignmentConfig.alignRight = false + alignmentConfig.alignCenterVertically = false + ChatUtils.chat("Disabled Custom Scoreboard auto-alignment.") + } + } + } + + @SubscribeEvent + fun onTick(event: LorenzTickEvent) { + if (!isEnabled()) return + + // Creating the lines + if (event.isMod(5)) { + display = createLines() + if (TabListData.fullyLoaded) { + cache = display.toList() + } + } + + // Remove Known Lines, so we can get the unknown ones + UnknownLinesHandler.handleUnknownLines() + } + + companion object { + internal val config get() = SkyHanniMod.feature.gui.customScoreboard + internal val displayConfig get() = config.displayConfig + internal val informationFilteringConfig get() = config.informationFilteringConfig + internal val backgroundConfig get() = config.backgroundConfig + } + + private fun createLines() = buildList<ScoreboardElementType> { + for (element in config.scoreboardEntries) { + val line = element.getVisiblePair() + + // Hide consecutive empty lines + if ( + config.informationFilteringConfig.hideConsecutiveEmptyLines && + line.isNotEmpty() && line[0].first == "<empty>" && lastOrNull()?.first?.isEmpty() == true + ) { + continue + } + + // Adds empty lines + if (line[0].first == "<empty>") { + add("" to HorizontalAlignment.LEFT) + continue + } + + // Does not display this line + if (line.any { it.first == "<hidden>" }) { + continue + } + + addAll(line) + } + } + + // 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 isEnabled() = LorenzUtils.inSkyBlock && config.enabled + private fun isHideVanillaScoreboardEnabled() = isEnabled() && config.displayConfig.hideVanillaScoreboard +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/CustomScoreboardUtils.kt b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/CustomScoreboardUtils.kt new file mode 100644 index 000000000..74ba89dd6 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/CustomScoreboardUtils.kt @@ -0,0 +1,53 @@ +package at.hannibal2.skyhanni.features.gui.customscoreboard + +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.features.bingo.BingoAPI +import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboard.Companion.config +import at.hannibal2.skyhanni.utils.NumberUtil +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.formatDouble +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpace +import at.hannibal2.skyhanni.utils.TabListData +import java.util.regex.Pattern + +object CustomScoreboardUtils { + private val numberFormat get() = config.displayConfig.numberFormat + + internal fun getGroupFromPattern(list: List<String>, pattern: Pattern, group: String) = list.map { + it.removeResets().trimWhiteSpace() + }.firstNotNullOfOrNull { line -> + pattern.matchMatcher(line) { + group(group) + } + } ?: "0" + + fun getProfileTypeSymbol() = when { + HypixelData.ironman -> "§7♲ " + HypixelData.stranded -> "§a☀ " + HypixelData.bingo -> ScoreboardData.sidebarLinesFormatted.firstNotNullOfOrNull { + BingoAPI.getIconFromScoreboard(it)?.plus(" ") + } ?: "§e❤ " + + else -> "§e" + } + + fun getTablistFooter(): String { + val tabList = TabListData.getPlayerTabOverlay() + if (tabList.footer_skyhanni == null) return "" + return tabList.footer_skyhanni.formattedText.replace("§r", "") + } + + internal fun Number.formatNum(): String = when (numberFormat) { + DisplayConfig.NumberFormat.SHORT -> NumberUtil.format(this) + DisplayConfig.NumberFormat.LONG -> this.addSeparators() + else -> "0" + } + + internal fun String.formatNum() = this.formatDouble().formatNum() + + class UndetectedScoreboardLines(message: String) : Exception(message) +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/RenderBackground.kt b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/RenderBackground.kt new file mode 100644 index 000000000..2a2f009ac --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/RenderBackground.kt @@ -0,0 +1,87 @@ +package at.hannibal2.skyhanni.features.gui.customscoreboard + +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.features.gui.customscoreboard.CustomScoreboard.Companion.backgroundConfig +import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboard.Companion.config +import at.hannibal2.skyhanni.utils.RenderUtils +import at.hannibal2.skyhanni.utils.SpecialColour +import io.github.moulberry.notenoughupdates.util.Utils +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.ScaledResolution +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.util.ResourceLocation +import org.lwjgl.opengl.GL11 + +class RenderBackground { + 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 + + // Update the position to the alignment options + if ( + config.displayConfig.alignment.alignRight + || config.displayConfig.alignment.alignCenterVertically + ) { + position.set( + Position( + if (config.displayConfig.alignment.alignRight) + scaledWidth - elementWidth - (backgroundConfig.borderSize * 2) + else x, + if (config.displayConfig.alignment.alignCenterVertically) + scaledHeight / 2 - elementHeight / 2 + else y, + position.getScale(), + position.isCenter + ) + ) + } + + if (GuiEditManager.isInGui()) return + + GlStateManager.pushMatrix() + GlStateManager.pushAttrib() + + GlStateManager.color(1f, 1f, 1f, 1f) + + + if (backgroundConfig.enabled) { + if (backgroundConfig.useCustomBackgroundImage) { + val textureLocation = ResourceLocation("skyhanni", "scoreboard.png") + Minecraft.getMinecraft().textureManager.bindTexture(textureLocation) + + Utils.drawTexturedRect( + (x - backgroundConfig.borderSize).toFloat(), + (y - backgroundConfig.borderSize).toFloat(), + (elementWidth + backgroundConfig.borderSize * 3).toFloat(), + (elementHeight + border * 2).toFloat(), + GL11.GL_NEAREST + ) + } else { + RenderUtils.drawRoundRect( + x - backgroundConfig.borderSize, + y - backgroundConfig.borderSize, + elementWidth + backgroundConfig.borderSize * 3, + elementHeight + backgroundConfig.borderSize * 2, + SpecialColour.specialToChromaRGB(backgroundConfig.color), + backgroundConfig.roundedCornerSmoothness + ) + } + } + + GlStateManager.popMatrix() + GlStateManager.popAttrib() + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardElements.kt b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardElements.kt new file mode 100644 index 000000000..801ba3249 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardElements.kt @@ -0,0 +1,635 @@ +package at.hannibal2.skyhanni.features.gui.customscoreboard + +import at.hannibal2.skyhanni.config.features.gui.customscoreboard.DisplayConfig.ArrowAmountDisplay +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.MayorAPI +import at.hannibal2.skyhanni.data.PartyAPI +import at.hannibal2.skyhanni.data.PurseAPI +import at.hannibal2.skyhanni.data.QuiverAPI +import at.hannibal2.skyhanni.data.QuiverAPI.NONE_ARROW_TYPE +import at.hannibal2.skyhanni.data.QuiverAPI.asArrowPercentage +import at.hannibal2.skyhanni.data.ScoreboardData +import at.hannibal2.skyhanni.data.SlayerAPI +import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboard.Companion.config +import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboard.Companion.displayConfig +import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboard.Companion.informationFilteringConfig +import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboardUtils.formatNum +import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboardUtils.getGroupFromPattern +import at.hannibal2.skyhanni.mixins.hooks.tryToReplaceScoreboardLine +import at.hannibal2.skyhanni.test.command.ErrorManager +import at.hannibal2.skyhanni.utils.CollectionUtils.nextAfter +import at.hannibal2.skyhanni.utils.LorenzUtils.inAdvancedMiningIsland +import at.hannibal2.skyhanni.utils.LorenzUtils.inAnyIsland +import at.hannibal2.skyhanni.utils.LorenzUtils.inDungeons +import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators +import at.hannibal2.skyhanni.utils.NumberUtil.percentageColor +import at.hannibal2.skyhanni.utils.RenderUtils.HorizontalAlignment +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.TimeUtils.format +import at.hannibal2.skyhanni.utils.TimeUtils.formatted +import io.github.moulberry.notenoughupdates.util.SkyBlockTime +import java.util.function.Supplier + +internal var unknownLines = listOf<String>() +internal var amountOfUnknownLines = 0 + +enum class ScoreboardElement( + private val displayPair: Supplier<List<ScoreboardElementType>>, + 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, + ::getObjectiveShowWhen, + "Objective:\n§eStar SkyHanni on Github" + ), + SLAYER( + ::getSlayerDisplayPair, + ::getSlayerShowWhen, + "Slayer Quest\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 to show all)" + ), + 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 getVisiblePair() = if (isVisible()) getPair() else listOf("<hidden>" to HorizontalAlignment.LEFT) + + private fun getPair(): List<ScoreboardElementType> { + return try { + displayPair.get() + } catch (e: NoSuchElementException) { + listOf("<hidden>" to HorizontalAlignment.LEFT) + } + } + + private fun isVisible(): Boolean { + if (!informationFilteringConfig.hideIrrelevantLines) return true + return showWhen() + } +} + + +private fun getTitleDisplayPair() = if (displayConfig.titleAndFooter.useHypixelTitleAnimation) { + listOf(ScoreboardData.objectiveTitle to displayConfig.titleAndFooter.alignTitleAndFooter) +} else { + listOf( + displayConfig.titleAndFooter.customTitle.get().toString() + .replace("&", "§") to displayConfig.titleAndFooter.alignTitleAndFooter + ) +} + +private fun getProfileDisplayPair() = + listOf(CustomScoreboardUtils.getProfileTypeSymbol() + HypixelData.profileName.firstLetterUppercase() to HorizontalAlignment.LEFT) + +private fun getPurseDisplayPair(): List<ScoreboardElementType> { + var purse = PurseAPI.currentPurse.formatNum() + + val earned = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, PurseAPI.coinsPattern, "earned") + + if (earned != "0") { + purse += " §7(§e+$earned§7)§6" + } + + return listOf( + when { + informationFilteringConfig.hideEmptyLines && purse == "0" -> "<hidden>" + displayConfig.displayNumbersFirst -> "§6$purse Purse" + else -> "Purse: §6$purse" + } to HorizontalAlignment.LEFT + ) +} + +private fun getPurseShowWhen() = !inAnyIsland(IslandType.THE_RIFT) + +private fun getMotesDisplayPair(): List<ScoreboardElementType> { + val motes = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.motesPattern, "motes") + .formatNum() + + return listOf( + when { + informationFilteringConfig.hideEmptyLines && motes == "0" -> "<hidden>" + displayConfig.displayNumbersFirst -> "§d$motes Motes" + else -> "Motes: §d$motes" + } to HorizontalAlignment.LEFT + ) +} + +private fun getMotesShowWhen() = inAnyIsland(IslandType.THE_RIFT) + +private fun getBankDisplayPair(): List<ScoreboardElementType> { + val bank = getGroupFromPattern(TabListData.getTabList(), ScoreboardPattern.bankPattern, "bank") + + return listOf( + when { + informationFilteringConfig.hideEmptyLines && bank == "0" -> "<hidden>" + displayConfig.displayNumbersFirst -> "§6$bank Bank" + else -> "Bank: §6$bank" + } to HorizontalAlignment.LEFT + ) +} + +private fun getBankShowWhen() = !inAnyIsland(IslandType.THE_RIFT) + +private fun getBitsDisplayPair(): List<ScoreboardElementType> { + val bits = BitsAPI.bits.coerceAtLeast(0).formatNum() + val bitsToClaim = if (BitsAPI.bitsToClaim == -1) { + "§cOpen Sbmenu§b" + } else { + BitsAPI.bitsToClaim.coerceAtLeast(0).formatNum() + } + + return listOf( + when { + informationFilteringConfig.hideEmptyLines && bits == "0" && bitsToClaim == "0" -> "<hidden>" + displayConfig.displayNumbersFirst -> { + if (displayConfig.showUnclaimedBits) { + "§b$bits§7/${if (bitsToClaim == "0") "§30" else "§b${bitsToClaim}"} §bBits" + } else { + "§b$bits Bits" + } + } + + else -> { + if (displayConfig.showUnclaimedBits) { + "Bits: §b$bits§7/${if (bitsToClaim == "0") "§30" else "§b${bitsToClaim}"}" + } else { + "Bits: §b$bits" + } + } + } to HorizontalAlignment.LEFT + ) +} + +private fun getBitsShowWhen() = !HypixelData.bingo && !inAnyIsland(IslandType.CATACOMBS, IslandType.KUUDRA_ARENA) + +private fun getCopperDisplayPair(): List<ScoreboardElementType> { + val copper = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.copperPattern, "copper") + .formatNum() + + return listOf( + when { + informationFilteringConfig.hideEmptyLines && copper == "0" -> "<hidden>" + displayConfig.displayNumbersFirst -> "§c$copper Copper" + else -> "Copper: §c$copper" + } to HorizontalAlignment.LEFT + ) +} + +private fun getCopperShowWhen() = inAnyIsland(IslandType.GARDEN) + +private fun getGemsDisplayPair(): List<ScoreboardElementType> { + val gems = getGroupFromPattern(TabListData.getTabList(), ScoreboardPattern.gemsPattern, "gems") + + return listOf( + when { + informationFilteringConfig.hideEmptyLines && gems == "0" -> "<hidden>" + displayConfig.displayNumbersFirst -> "§a$gems Gems" + else -> "Gems: §a$gems" + } to HorizontalAlignment.LEFT + ) +} + +private fun getGemsShowWhen() = !inAnyIsland(IslandType.THE_RIFT, IslandType.CATACOMBS, IslandType.KUUDRA_ARENA) + +private fun getHeatDisplayPair(): List<ScoreboardElementType> { + val heat = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.heatPattern, "heat") + + return listOf( + when { + informationFilteringConfig.hideEmptyLines && heat == "§c♨ 0" -> "<hidden>" + displayConfig.displayNumbersFirst -> if (heat == "0") "§c♨ 0 Heat" else "$heat Heat" + else -> if (heat == "0") "Heat: §c♨ 0" else "Heat: $heat" + } to HorizontalAlignment.LEFT + ) +} + +private fun getHeatShowWhen() = inAnyIsland(IslandType.CRYSTAL_HOLLOWS) + && ScoreboardData.sidebarLinesFormatted.any { ScoreboardPattern.heatPattern.matches(it) } + +private fun getNorthStarsDisplayPair(): List<ScoreboardElementType> { + val northStars = + getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.northstarsPattern, "northstars") + .formatNum() + + return listOf( + when { + informationFilteringConfig.hideEmptyLines && northStars == "0" -> "<hidden>" + displayConfig.displayNumbersFirst -> "§d$northStars North Stars" + else -> "North Stars: §d$northStars" + } to HorizontalAlignment.LEFT + ) +} + +private fun getNorthStarsShowWhen() = inAnyIsland(IslandType.WINTER) + +private fun getEmptyLineDisplayPair() = listOf("<empty>" to HorizontalAlignment.LEFT) + +private fun getIslandDisplayPair() = + listOf("§7㋖ §a" + HypixelData.skyBlockIsland.displayName to HorizontalAlignment.LEFT) + +private fun getLocationDisplayPair() = buildList { + add( + (tryToReplaceScoreboardLine( + getGroupFromPattern( + ScoreboardData.sidebarLinesFormatted, + ScoreboardPattern.locationPattern, + "location" + ) + )?.trim() + ?: "<hidden>") to HorizontalAlignment.LEFT + ) + + val plotLine = ScoreboardData.sidebarLinesFormatted.firstOrNull { ScoreboardPattern.plotPattern.matches(it) } + if (plotLine != null) add(plotLine to HorizontalAlignment.LEFT) +} + +private fun getVisitDisplayPair() = + listOf( + ScoreboardData.sidebarLinesFormatted.first { ScoreboardPattern.visitingPattern.matches(it) } to HorizontalAlignment.LEFT + ) + +private fun getVisitShowWhen() = + ScoreboardData.sidebarLinesFormatted.any { ScoreboardPattern.visitingPattern.matches(it) } + +private fun getDateDisplayPair() = + listOf( + SkyBlockTime.now().formatted(yearElement = false, hoursAndMinutesElement = false) to HorizontalAlignment.LEFT + ) + + +private fun getTimeDisplayPair(): List<ScoreboardElementType> { + var symbol = getGroupFromPattern(ScoreboardData.sidebarLinesFormatted, ScoreboardPattern.timePattern, "symbol") + if (symbol == "0") symbol = "" + return listOf( + "§7" + SkyBlockTime.now() + .formatted(dayAndMonthElement = false, yearElement = false) + " $symbol" to HorizontalAlignment.LEFT + ) +} + +private fun getLobbyDisplayPair(): List<ScoreboardElementType> { + val lobbyCode = getGroupFromPattern( + ScoreboardData.sidebarLinesFormatted, + ScoreboardPattern.lobbyCodePattern, + "code" + ) + + val displayValue = if (lobbyCode == "0") "<hidden>" else "§8$lobbyCode" + return listOf(displayValue to HorizontalAlignment.LEFT) +} + +private fun getPowerDisplayPair() = listOf( + when (MaxwellAPI.currentPower) { + null -> "§cOpen \"Your Bags\"!" + else -> + if (displayConfig.displayNumbersFirst) { + "${MaxwellAPI.currentPower?.replace("Power", "")} Power " + + "§7(§6${MaxwellAPI.magicalPower}§7)" + } else { + "Power: ${MaxwellAPI.currentPower?.replace("Power", "")} " + + "§7(§6${MaxwellAPI.magicalPower?.addSeparators()}§7)" + } + } to HorizontalAlignment.LEFT +) + +private fun getPowerShowWhen() = !inAnyIsland(IslandType.THE_RIFT) + +private fun getCookieDisplayPair(): List<ScoreboardElementType> { + val timeLine = CustomScoreboardUtils.getTablistFooter().split("\n") + .nextAfter("§d§lCookie Buff") ?: "<hidden>" + + return listOf( + "§d§lCookie Buff" to HorizontalAlignment.LEFT, + if (timeLine.contains("Not active")) + " §7- §cNot active" to HorizontalAlignment.LEFT + else + " §7- §e${timeLine.substringAfter("§d§lCookie Buff").trim()}" to HorizontalAlignment.LEFT + ) +} + +private fun getCookieShowWhen(): Boolean { + if (HypixelData.bingo) return false + + return if (informationFilteringConfig.hideEmptyLines) { + CustomScoreboardUtils.getTablistFooter().split("\n").any { + CustomScoreboardUtils.getTablistFooter().split("\n").nextAfter("§d§lCookie Buff")?.contains(it) + ?: false + } + } else { + true + } +} + +private fun getObjectiveDisplayPair() = buildList { + val objective = ScoreboardData.sidebarLinesFormatted.first { ScoreboardPattern.objectivePattern.matches(it) } + + add(objective to HorizontalAlignment.LEFT) + add((ScoreboardData.sidebarLinesFormatted.nextAfter(objective) ?: "<hidden>") to HorizontalAlignment.LEFT) + + if (ScoreboardData.sidebarLinesFormatted.any { ScoreboardPattern.thirdObjectiveLinePattern.matches(it) }) { + add( + (ScoreboardData.sidebarLinesFormatted.nextAfter(objective, 2) + ?: "Second objective here") to HorizontalAlignment.LEFT + ) + } +} + +private fun getObjectiveShowWhen(): Boolean = + !inAnyIsland(IslandType.KUUDRA_ARENA) + && ScoreboardData.sidebarLinesFormatted.none { ScoreboardPattern.objectivePattern.matches(it) } + + +private fun getSlayerDisplayPair(): List<ScoreboardElementType> = listOf( + (if (SlayerAPI.hasActiveSlayerQuest()) "Slayer Quest" else "<hidden>") to HorizontalAlignment.LEFT, + (" §7- §e${SlayerAPI.latestSlayerCategory.trim()}" to HorizontalAlignment.LEFT), + (" §7- §e${SlayerAPI.latestSlayerProgress.trim()}" to HorizontalAlignment.LEFT) +) + +// TODO: Redo the Slayer showWhen +private fun getSlayerShowWhen() = true + +private fun getQuiverDisplayPair(): List<ScoreboardElementType> { + if (QuiverAPI.currentArrow == null) + return listOf("§cChange your Arrow once" to HorizontalAlignment.LEFT) + if (QuiverAPI.currentArrow == NONE_ARROW_TYPE) + return listOf("No Arrows selected" to HorizontalAlignment.LEFT) + + val amountString = (if (displayConfig.colorArrowAmount) { + percentageColor(QuiverAPI.currentAmount.toLong(), QuiverAPI.MAX_ARROW_AMOUNT.toLong()).getChatColor() + } else { + "" + }) + when (displayConfig.arrowAmountDisplay) { + ArrowAmountDisplay.NUMBER -> QuiverAPI.currentAmount.addSeparators() + ArrowAmountDisplay.PERCENTAGE -> "${QuiverAPI.currentAmount.asArrowPercentage()}%" + else -> QuiverAPI.currentAmount.addSeparators() + } + + return listOf( + if (displayConfig.displayNumbersFirst) { + "$amountString ${QuiverAPI.currentArrow?.arrow}s" + } else { + "${QuiverAPI.currentArrow?.arrow?.replace(" Arrow", "")}: $amountString Arrows" + } to HorizontalAlignment.LEFT + ) +} + +private fun getQuiverShowWhen(): Boolean { + if (informationFilteringConfig.hideIrrelevantLines && !QuiverAPI.hasBowInInventory()) return false + return !inAnyIsland(IslandType.THE_RIFT) +} + +private fun getPowderDisplayPair() = buildList { + val mithrilPowder = + getGroupFromPattern(TabListData.getTabList(), ScoreboardPattern.mithrilPowderPattern, "mithrilpowder") + .formatNum() + val gemstonePowder = + getGroupFromPattern(TabListData.getTabList(), ScoreboardPattern.gemstonePowderPattern, "gemstonepowder") + .formatNum() + + add("§9§lPowder" to HorizontalAlignment.LEFT) + + if (informationFilteringConfig.hideEmptyLines && mithrilPowder == "0" && gemstonePowder == "0") { + add(0, "<hidden>" to HorizontalAlignment.LEFT) + } else { + if (displayConfig.displayNumbersFirst) { + add(" §7- §2$mithrilPowder Mithril" to HorizontalAlignment.LEFT) + add(" §7- §d$gemstonePowder Gemstone" to HorizontalAlignment.LEFT) + } else { + add(" §7- §fMithril: §2$mithrilPowder" to HorizontalAlignment.LEFT) + add(" §7- §fGemstone: §d$gemstonePowder" to HorizontalAlignment.LEFT) + } + } +} + +private fun getPowderShowWhen() = inAdvancedMiningIsland() + +private fun getEventsDisplayPair(): List<ScoreboardElementType> { + return ScoreboardEvents.getEvent() + .flatMap { it.getLines().map { i -> i to HorizontalAlignment.LEFT } } + .takeIf { it.isNotEmpty() } ?: listOf("<hidden>" to HorizontalAlignment.LEFT) +} + +private fun getEventsShowWhen() = ScoreboardEvents.getEvent().isNotEmpty() + +private fun getMayorDisplayPair() = buildList { + add( + ((MayorAPI.currentMayor?.mayorName?.let { MayorAPI.mayorNameWithColorCode(it) } + ?: "<hidden>") + + (if (config.mayorConfig.showTimeTillNextMayor) { + "§7 (§e${MayorAPI.timeTillNextMayor.format()}§7)" + } else { + "" + })) to HorizontalAlignment.LEFT + ) + if (config.mayorConfig.showMayorPerks) { + MayorAPI.currentMayor?.activePerks?.forEach { + add(" §7- §e${it.perkName}" to HorizontalAlignment.LEFT) + } + } +} + +private fun getMayorShowWhen() = + !inAnyIsland(IslandType.THE_RIFT) && MayorAPI.currentMayor != null + +private fun getPartyDisplayPair() = + if (PartyAPI.partyMembers.isEmpty() && informationFilteringConfig.hideEmptyLines) { + listOf("<hidden>" to HorizontalAlignment.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 HorizontalAlignment.LEFT } + } + +private fun getPartyShowWhen() = if (inDungeons) { + false // Hidden bc the scoreboard lines already exist +} else { + if (config.partyConfig.showPartyEverywhere) { + true + } else { + inAnyIsland( + IslandType.DUNGEON_HUB, + IslandType.KUUDRA_ARENA, + IslandType.CRIMSON_ISLE + ) + } +} + +private fun getFooterDisplayPair() = listOf( + displayConfig.titleAndFooter.customFooter.get().toString() + .replace("&", "§") to displayConfig.titleAndFooter.alignTitleAndFooter +) + +private fun getExtraDisplayPair(): List<ScoreboardElementType> { + if (unknownLines.isEmpty()) return listOf("<hidden>" to HorizontalAlignment.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, + noStackTrace = true + ) + amountOfUnknownLines = unknownLines.size + } + + return listOf("§cUndetected Lines:" to HorizontalAlignment.LEFT) + unknownLines.map { it to HorizontalAlignment.LEFT } +} + +private fun getExtraShowWhen(): Boolean { + if (unknownLines.isEmpty()) { + amountOfUnknownLines = 0 + } + return unknownLines.isNotEmpty() +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardEvents.kt b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardEvents.kt new file mode 100644 index 000000000..0ca968a98 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardEvents.kt @@ -0,0 +1,498 @@ +package at.hannibal2.skyhanni.features.gui.customscoreboard + +import at.hannibal2.skyhanni.data.HypixelData +import at.hannibal2.skyhanni.data.IslandType +import at.hannibal2.skyhanni.data.ScoreboardData +import at.hannibal2.skyhanni.features.garden.contest.FarmingContestAPI.sidebarCropPattern +import at.hannibal2.skyhanni.features.gui.customscoreboard.CustomScoreboard.Companion.config +import at.hannibal2.skyhanni.features.gui.customscoreboard.ScoreboardEvents.VOTING +import at.hannibal2.skyhanni.features.gui.customscoreboard.ScoreboardPattern +import at.hannibal2.skyhanni.features.misc.ServerRestartTitle +import at.hannibal2.skyhanni.features.rift.area.stillgorechateau.RiftBloodEffigies +import at.hannibal2.skyhanni.utils.CollectionUtils.addIfNotNull +import at.hannibal2.skyhanni.utils.CollectionUtils.nextAfter +import at.hannibal2.skyhanni.utils.LorenzUtils.inAdvancedMiningIsland +import at.hannibal2.skyhanni.utils.LorenzUtils.inDungeons +import at.hannibal2.skyhanni.utils.LorenzUtils.isInIsland +import at.hannibal2.skyhanni.utils.StringUtils.anyMatches +import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher +import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.TabListData +import java.util.function.Supplier +import at.hannibal2.skyhanni.features.gui.customscoreboard.ScoreboardPattern as SbPattern + +/** + * 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 [ScoreboardElement] 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<String> { + return ScoreboardData.sidebarLinesFormatted +} + +enum class ScoreboardEvents(private val displayLine: Supplier<List<String>>, private val showWhen: () -> Boolean) { + VOTING( + ::getVotingLines, + ::getVotingShowWhen + ), + SERVER_CLOSE( + ::getServerCloseLines, + ::getServerCloseShowWhen + ), + DUNGEONS( + ::getDungeonsLines, + ::getDungeonsShowWhen + ), + KUUDRA( + ::getKuudraLines, + ::getKuudraShowWhen + ), + DOJO( + ::getDojoLines, + ::getDojoShowWhen + ), + DARK_AUCTION( + ::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 + ), + ACTIVE_TABLIST_EVENTS( + ::getActiveEventLine, + ::getActiveEventShowWhen + ), + 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 + ), + + // Maybe as a default state, use tablist "Events: ..." + NONE( + ::getNoneLines, + { false } + ); + + fun getLines(): List<String> { + return displayLine.get() + } + + companion object { + fun getEvent(): List<ScoreboardEvents> { + if (config.displayConfig.showAllActiveEvents) { + return entries.filter { it.showWhen() } + } + return listOf(entries.firstOrNull { it.showWhen() } ?: NONE) + } + } +} + +private fun getVotingLines() = buildList { + val sbLines = getSbLines() + + val yearLine = sbLines.firstOrNull { SbPattern.yearVotesPattern.matches(it) } ?: return emptyList<String>() + addIfNotNull(yearLine) + + if (sbLines.nextAfter(yearLine) == "§7Waiting for") { + add("§7Waiting for") + add("§7your vote...") + } else { + if (SbPattern.votesPattern.anyMatches(sbLines)) { + addAll(sbLines.filter { SbPattern.votesPattern.matches(it) }) + } + } +} + + +private fun getVotingShowWhen(): Boolean { + return SbPattern.yearVotesPattern.anyMatches(getSbLines()) +} + +private fun getServerCloseLines() = buildList { + val matchingLine = getSbLines().first { ServerRestartTitle.restartingGreedyPattern.matches(it) } + add(matchingLine.split("§8")[0]) +} + +private fun getServerCloseShowWhen(): Boolean { + return ServerRestartTitle.restartingGreedyPattern.anyMatches(getSbLines()) +} + +private fun getDungeonsLines() = listOf( + SbPattern.autoClosingPattern, + SbPattern.startingInPattern, + SbPattern.keysPattern, + SbPattern.timeElapsedPattern, + SbPattern.clearedPattern, + SbPattern.soloPattern, + SbPattern.teammatesPattern, + SbPattern.floor3GuardiansPattern +).let { patterns -> + // BetterMap adds a random §r at the start, making it go black + getSbLines().filter { line -> patterns.any { it.matches(line.replace("§r", "")) } } +} + +private fun getDungeonsShowWhen(): Boolean { + return IslandType.CATACOMBS.isInIsland() || inDungeons +} + +private fun getKuudraLines() = listOf( + SbPattern.autoClosingPattern, + SbPattern.startingInPattern, + SbPattern.timeElapsedPattern, + SbPattern.instanceShutdownPattern, + SbPattern.wavePattern, + SbPattern.tokensPattern, + SbPattern.submergesPattern +) + .mapNotNull { pattern -> + getSbLines().firstOrNull { pattern.matches(it) } + } + +private fun getKuudraShowWhen(): Boolean { + return IslandType.KUUDRA_ARENA.isInIsland() +} + +private fun getDojoLines() = listOf( + SbPattern.dojoChallengePattern, + SbPattern.dojoDifficultyPattern, + SbPattern.dojoPointsPattern, + SbPattern.dojoTimePattern +) + .mapNotNull { pattern -> + getSbLines().firstOrNull { pattern.matches(it) } + } + +private fun getDojoShowWhen(): Boolean { + return SbPattern.dojoChallengePattern.anyMatches(getSbLines()) +} + +private fun getDarkAuctionLines() = buildList { + getSbLines().firstOrNull { SbPattern.startingInPattern.matches(it) }?.let { add(it) } + getSbLines().firstOrNull { SbPattern.timeLeftPattern.matches(it) }?.let { add(it) } + + val darkAuctionCurrentItemLine = + getSbLines().firstOrNull { SbPattern.darkAuctionCurrentItemPattern.matches(it) } + + if (darkAuctionCurrentItemLine != null) { + addIfNotNull(darkAuctionCurrentItemLine) + addIfNotNull(getSbLines().nextAfter(darkAuctionCurrentItemLine)) + } +} + +private fun getDarkAuctionShowWhen(): Boolean { + return IslandType.DARK_AUCTION.isInIsland() +} + +private fun getJacobContestLines() = buildList { + val jacobsContestLine = getSbLines().firstOrNull { SbPattern.jacobsContestPattern.matches(it) } + + jacobsContestLine?.let { + addIfNotNull(it) + addIfNotNull(getSbLines().nextAfter(it)) + addIfNotNull(getSbLines().nextAfter(it, 2)) + addIfNotNull(getSbLines().nextAfter(it, 3)) + } +} + +private fun getJacobContestShowWhen(): Boolean { + return sidebarCropPattern.anyMatches(getSbLines()) +} + +private fun getJacobMedalsLines(): List<String> { + return getSbLines().filter { SbPattern.medalsPattern.matches(it) } +} + +private fun getJacobMedalsShowWhen(): Boolean { + return SbPattern.medalsPattern.anyMatches(getSbLines()) +} + +private fun getTrapperLines() = buildList { + addIfNotNull(getSbLines().firstOrNull { SbPattern.peltsPattern.matches(it) }) + + val trapperMobLocationLine = getSbLines().firstOrNull { SbPattern.mobLocationPattern.matches(it) } + if (trapperMobLocationLine != null) { + add("Tracker Mob Location:") + addIfNotNull(getSbLines().nextAfter(trapperMobLocationLine)) + } +} + +private fun getTrapperShowWhen(): Boolean { + return getSbLines().any { + ScoreboardPattern.peltsPattern.matches(it) || ScoreboardPattern.mobLocationPattern.matches(it) + } +} + +private fun getGardenCleanUpLines(): List<String> { + return listOf(getSbLines().first { SbPattern.cleanUpPattern.matches(it) }.trim()) +} + +private fun getGardenCleanUpShowWhen(): Boolean { + return SbPattern.cleanUpPattern.anyMatches(getSbLines()) +} + +private fun getGardenPastingLines(): List<String> { + return listOf(getSbLines().first { SbPattern.pastingPattern.matches(it) }.trim()) +} + +private fun getGardenPastingShowWhen(): Boolean { + return SbPattern.pastingPattern.anyMatches(getSbLines()) +} + +private fun getFlightDurationLines(): List<String> { + return listOf(getSbLines().first { SbPattern.flightDurationPattern.matches(it) }.trim()) +} + +private fun getFlightDurationShowWhen(): Boolean { + return SbPattern.flightDurationPattern.anyMatches(getSbLines()) +} + +private fun getWinterLines() = buildList { + addIfNotNull(getSbLines().firstOrNull { SbPattern.winterEventStartPattern.matches(it) }) + addIfNotNull(getSbLines().firstOrNull { SbPattern.winterNextWavePattern.matches(it) && !it.endsWith("Soon!") }) + addIfNotNull(getSbLines().firstOrNull { SbPattern.winterWavePattern.matches(it) }) + addIfNotNull(getSbLines().firstOrNull { SbPattern.winterMagmaLeftPattern.matches(it) }) + addIfNotNull(getSbLines().firstOrNull { SbPattern.winterTotalDmgPattern.matches(it) }) + addIfNotNull(getSbLines().firstOrNull { SbPattern.winterCubeDmgPattern.matches(it) }) +} + +private fun getWinterShowWhen(): Boolean { + return getSbLines().any { + ScoreboardPattern.winterEventStartPattern.matches(it) + || (ScoreboardPattern.winterNextWavePattern.matches(it) && !it.endsWith("Soon!")) + || ScoreboardPattern.winterWavePattern.matches(it) + } +} + +private fun getSpookyLines() = buildList { + addIfNotNull(getSbLines().firstOrNull { SbPattern.spookyPattern.matches(it) }) // Time + addIfNotNull("§7Your Candy: ") + addIfNotNull( + 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 getActiveEventLine(): List<String> { + val currentActiveEvent = TabListData.getTabList().firstOrNull { SbPattern.eventNamePattern.matches(it) } + ?.let { + SbPattern.eventNamePattern.matchMatcher(it) { + group("name") + } + } + val currentActiveEventEndsIn = TabListData.getTabList().firstOrNull { SbPattern.eventTimeEndsPattern.matches(it) } + ?.let { + SbPattern.eventTimeEndsPattern.matchMatcher(it) { + group("time") + } + } + + return listOf("$currentActiveEvent $currentActiveEventEndsIn") +} + +private fun getActiveEventShowWhen(): Boolean { + return TabListData.getTabList().any { SbPattern.eventNamePattern.matches(it) } && + TabListData.getTabList().any { SbPattern.eventTimeEndsPattern.matches(it) } +} + +private fun getBroodmotherLines(): List<String> { + return listOf(getSbLines().first { SbPattern.broodmotherPattern.matches(it) }) +} + +private fun getBroodmotherShowWhen(): Boolean { + return getSbLines().any { SbPattern.broodmotherPattern.matches(it) } +} + +private fun getNewYearLines(): List<String> { + return listOf(getSbLines().first { SbPattern.newYearPattern.matches(it) }) +} + +private fun getNewYearShowWhen(): Boolean { + return getSbLines().any { SbPattern.newYearPattern.matches(it) } +} + +private fun getOringoLines(): List<String> { + return listOf(getSbLines().first { SbPattern.travelingZooPattern.matches(it) }) +} + +private fun getOringoShowWhen(): Boolean { + return getSbLines().any { SbPattern.travelingZooPattern.matches(it) } +} + +private fun getMiningEventsLines() = buildList { + // Wind + if (getSbLines().any { SbPattern.windCompassPattern.matches(it) } + && getSbLines().any { SbPattern.windCompassArrowPattern.matches(it) }) { + add(getSbLines().first { SbPattern.windCompassPattern.matches(it) }) + add("| ${getSbLines().first { SbPattern.windCompassArrowPattern.matches(it) }} §f|") + } + + // Better Together + if (getSbLines().any { SbPattern.nearbyPlayersPattern.matches(it) }) { + add("§dBetter Together") + add(" ${getSbLines().first { SbPattern.nearbyPlayersPattern.matches(it) }}") + } + + // Zone Events + if (getSbLines().any { SbPattern.miningEventPattern.matches(it) } + && getSbLines().any { SbPattern.miningEventZonePattern.matches(it) }) { + add(getSbLines().first { SbPattern.miningEventPattern.matches(it) }.removePrefix("Event: ")) + add("in ${getSbLines().first { SbPattern.miningEventZonePattern.matches(it) }.removePrefix("Zone: ")}") + } + + // Zone Events but no Zone Line + if (getSbLines().any { SbPattern.miningEventPattern.matches(it) } + && getSbLines().none { SbPattern.miningEventZonePattern.matches(it) }) { + add(getSbLines().first { SbPattern.miningEventPattern.matches(it) } + .removePrefix("Event: ")) + } + + // Mithril Gourmand + if (getSbLines().any { SbPattern.mithrilRemainingPattern.matches(it) } + && getSbLines().any { SbPattern.mithrilYourMithrilPattern.matches(it) }) { + add(getSbLines().first { SbPattern.mithrilRemainingPattern.matches(it) }) + add(getSbLines().first { SbPattern.mithrilYourMithrilPattern.matches(it) }) + } + + // Raffle + if (getSbLines().any { SbPattern.raffleTicketsPattern.matches(it) } + && getSbLines().any { SbPattern.rafflePoolPattern.matches(it) }) { + add(getSbLines().first { SbPattern.raffleTicketsPattern.matches(it) }) + add(getSbLines().first { SbPattern.rafflePoolPattern.matches(it) }) + } + + // Raid + if (getSbLines().any { SbPattern.yourGoblinKillsPattern.matches(it) } + && getSbLines().any { SbPattern.remainingGoblinPattern.matches(it) }) { + add(getSbLines().first { SbPattern.yourGoblinKillsPattern.matches(it) }) + add(getSbLines().first { SbPattern.remainingGoblinPattern.matches(it) }) + } +} + +private fun getMiningEventsShowWhen(): Boolean { + return inAdvancedMiningIsland() +} + +private fun getDamageLines(): List<String> { + 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) } + && getSbLines().any { SbPattern.bossDamagePattern.matches(it) } +} + +private fun getMagmaBossLines() = getSbLines().filter { line -> + SbPattern.magmaBossPattern.matches(line) + || SbPattern.damageSoakedPattern.matches(line) + || SbPattern.killMagmasPattern.matches(line) + || SbPattern.killMagmasDamagedSoakedBarPattern.matches(line) + || SbPattern.reformingPattern.matches(line) + || SbPattern.bossHealthPattern.matches(line) + || SbPattern.bossHealthBarPattern.matches(line) +} + +private fun getMagmaBossShowWhen(): Boolean { + return SbPattern.magmaChamberPattern.matches(HypixelData.skyBlockArea) +} + +private fun getEssenceLines(): List<String> { + return listOf(getSbLines().first { SbPattern.essencePattern.matches(it) }) +} + +private fun getEssenceShowWhen(): Boolean { + return SbPattern.essencePattern.anyMatches(getSbLines()) +} + +private fun getEffigiesLines(): List<String> { + return listOf(getSbLines().first { RiftBloodEffigies.heartsPattern.matches(it) }) +} + +private fun getEffigiesShowWhen(): Boolean { + return RiftBloodEffigies.heartsPattern.anyMatches(getSbLines()) +} + +private fun getRedstoneLines(): List<String> { + return listOf(getSbLines().first { SbPattern.redstonePattern.matches(it) }) +} + +private fun getRedstoneShowWhen(): Boolean { + return SbPattern.redstonePattern.anyMatches(getSbLines()) +} + +private fun getNoneLines(): List<String> { + return when { + config.informationFilteringConfig.hideEmptyLines -> listOf("<hidden>") + else -> listOf("§cNo Event") + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardPattern.kt b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardPattern.kt new file mode 100644 index 000000000..45c0aa01d --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardPattern.kt @@ -0,0 +1,404 @@ +package at.hannibal2.skyhanni.features.gui.customscoreboard + +import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern + +object ScoreboardPattern { + val group = RepoPattern.group("features.gui.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: (§.)*(?<motes>[\\d,]+).*$" + ) + val heatPattern by mainSb.pattern( + "heat", + "^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: (§.)*(?<copper>[\\d,]+).*$" + ) + val locationPattern by mainSb.pattern( + "location", + "^\\s*(?<location>(§7⏣|§5ф) .*)$" + ) + val lobbyCodePattern by mainSb.pattern( + "lobbycode", + "^\\s*§.((\\d{2}/\\d{2}/\\d{2})|Server closing: [\\d:]+) §8(?<code>.*)\$" + ) + 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) (?<symbol>(§b☽|§e☀|§.⚡|§.☔)).*$" + ) + val footerPattern by mainSb.pattern( + "footer", + "§e(www|alpha).hypixel.net\$" + ) + val yearVotesPattern by mainSb.pattern( + "yearvotes", + "(?<yearvotes>^§6Year \\d+ Votes\$)" + ) + val votesPattern by mainSb.pattern( + "votes", + "(?<votes>§[caebd]\\|+§f\\|+ §(.+)\$)" + ) + val waitingForVotePattern by mainSb.pattern( + "waitingforvote", + "(§7Waiting for|§7your vote\\.\\.\\.)$" + ) + val northstarsPattern by mainSb.pattern( + "northstars", + "North Stars: §d(?<northstars>[\\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: (§.)*(?<time>(\\w+[ydhms] ?)+)$" + ) + val instanceShutdownPattern by multiUseSb.pattern( + "instanceshutdown", + "(§.)*Instance Shutdown: (§.)*(?<time>(\\w+[ydhms] ?)+)$" + ) + val timeLeftPattern by multiUseSb.pattern( + "timeleft", + "(§.)*Time Left: (§.)*[\\w:,.]*$" + ) + + // dungeon scoreboard + private val dungeonSb = scoreboardGroup.group("dungeon") + val keysPattern by dungeonSb.pattern( + "keys", + "Keys: §.■ §.[✗✓] §.■ §a.x$" + ) + val clearedPattern by dungeonSb.pattern( + "cleared", + "(§.)*Cleared: (§.)*(?<percent>[\\w,.]+)% (§.)*\\((§.)*(?<score>[\\w,.]+)(§.)*\\)$" + ) + val soloPattern by dungeonSb.pattern( + "solo", + "§3§lSolo$" + ) + val teammatesPattern by dungeonSb.pattern( + "teammates", + "(§.)*(?<classAbbv>\\[\\w]) (§.)*(?<username>[a-zA-Z0-9_]{2,16}) ((§.)*(?<classLevel>\\[Lvl?(?<level>[\\w,.]+)?]?)|(§.)*(?<health>[\\w,.]+)(§.)*.?)$" + ) + val floor3GuardiansPattern by dungeonSb.pattern( + "floor3guardians", + "^§. - §.(Healthy|Reinforced|Laser|Chaos)§a ([\\w,.]?)+§c❤$" + ) + + // kuudra + private val kuudraSb = scoreboardGroup.group("kuudra") + val wavePattern by kuudraSb.pattern( + "wave", + "^(§.)*Wave: (§.)*\\d+(§.)*( §.- §.\\d+:\\d+)?$" + ) + val tokensPattern by kuudraSb.pattern( + "tokens", + "^(§.)*Tokens: §.[\\w,]+$" + ) + val submergesPattern by kuudraSb.pattern( + "submerges", + "^(§.)*Submerges In: (§.)*[\\w,?:]+$" + ) + + // farming + private val farmingSb = scoreboardGroup.group("farming") + val medalsPattern by farmingSb.pattern( + "medals", + "§[6fc]§l(GOLD|SILVER|BRONZE) §fmedals: §[6fc]\\d+$" + ) + val lockedPattern by farmingSb.pattern( + "locked", + "^\\s*§cLocked$" + ) + val cleanUpPattern by farmingSb.pattern( + "cleanup", + "^\\s*(§.)*Cleanup(§.)*: (§.)*[\\d,.]+%$" + ) + val pastingPattern by farmingSb.pattern( + "pasting", + "^\\s*§f(Barn )?Pasting§7: (§.)*[\\d,]+%$" + ) + val peltsPattern by farmingSb.pattern( + "pelts", + "^(§.)*Pelts: (§.)*(?<pelts>[\\d,]+)( (§.)*\\([+-](?<diff>[\\w,.]+)\\))?\$" + ) + val mobLocationPattern by farmingSb.pattern( + "moblocation", + "^(§.)*Tracker Mob Location:" + ) + val jacobsContestPattern by farmingSb.pattern( + "jacobscontest", + "^§eJacob's Contest$" + ) + val plotPattern by farmingSb.pattern( + "plot", + "\\s*§aPlot §7-.*" + ) + + // mining + private val miningSb = scoreboardGroup.group("mining") + val powderPattern by miningSb.pattern( + "powder", + "(§.)*᠅ §f(Gemstone|Mithril)( Powder)?(§.)*:?.*$" + ) + val windCompassPattern by miningSb.pattern( + "windcompass", + "§9Wind Compass$" + ) + val windCompassArrowPattern by miningSb.pattern( + "windcompassarrow", + "( )*((§[a-zA-Z0-9]|[⋖⋗≈])+)( )*((§[a-zA-Z0-9]|[⋖⋗≈])+)?( )*" + ) + val miningEventPattern by miningSb.pattern( + "miningevent", + "^Event: §.§L.*$" + ) + val miningEventZonePattern by miningSb.pattern( + "miningeventzone", + "^Zone: §.*$" + ) + val raffleUselessPattern by miningSb.pattern( + "raffleuseless", + "^(Find tickets on the|ground and bring them|to the raffle box)$" + ) + val raffleTicketsPattern by miningSb.pattern( + "raffletickets", + "^Tickets: §a\\d+ §7\\(\\d{1,3}\\.\\d%\\)$" + ) + val rafflePoolPattern by miningSb.pattern( + "rafflepool", + "^Pool: §6\\d+§8/500$" + ) + val mithrilUselessPattern by miningSb.pattern( + "mithriluseless", + "^§7Give Tasty Mithril to Don!$" + ) + val mithrilRemainingPattern by miningSb.pattern( + "mithrilremaining", + "^Remaining: §a(\\d+ Tasty Mithril|FULL)$" + ) + val mithrilYourMithrilPattern by miningSb.pattern( + "mithrilyourmithril", + "^Your Tasty Mithril: §c\\d+.*$" + ) + val nearbyPlayersPattern by miningSb.pattern( + "nearbyplayers", + "^Nearby Players: §.(\\d+|N/A)$" + ) + val uselessGoblinPattern by miningSb.pattern( + "uselessgoblin", + "^§7Kill goblins!$" + ) + val remainingGoblinPattern by miningSb.pattern( + "remaininggoblin", "^Remaining: §a\\d+ goblins$" + ) + val yourGoblinKillsPattern by miningSb.pattern( + "yourgoblin", "^Your kills: §c\\d+ ☠( §a\\(\\+\\d+\\))?$" + ) + + // combat + private val combatSb = scoreboardGroup.group("combat") + val magmaChamberPattern by combatSb.pattern( + "magmachamber", + "^Magma Chamber$" + ) + val magmaBossPattern by combatSb.pattern( + "magmaboss", + "^§7Boss: §[c6e]\\d{1,3}%$" + ) + val damageSoakedPattern by combatSb.pattern( + "damagesoaked", + "^§7Damage Soaked:" + ) + val killMagmasPattern by combatSb.pattern( + "killmagmas", + "^§6Kill the Magmas:$" + ) + val killMagmasDamagedSoakedBarPattern by combatSb.pattern( + "killmagmasbar", + "^((§.)*▎+)+.*$" + ) + val reformingPattern by combatSb.pattern( + "magmareforming", + "^§cThe boss is reforming!$" + ) + val bossHealthPattern by combatSb.pattern( + "magmabosshealth", + "^§7Boss Health:$" + ) + val bossHealthBarPattern by combatSb.pattern( + "magmabosshealthbar", + "^§.(\\d{1,2}(\\.\\d)?M|\\d{1,3}k)§f/§a10M§c❤$" + ) + val broodmotherPattern by combatSb.pattern( + "broodmother", + "^§4Broodmother§7: §[e64](Slain|Dormant|Soon|Awakening|Imminent|Alive!)$" + ) + val bossHPPattern by combatSb.pattern( + "bosshp", + "^(Protector|Dragon) HP: §a(,?\\d{1,3})* §c❤$" + ) + val bossDamagePattern by combatSb.pattern( + "bossdamage", + "^Your Damage: §c(,?\\d{1,3}(\\.\\d)?)*$" + ) + val slayerQuestPattern by combatSb.pattern( + "slayerquest", + "^Slayer Quest$" + ) + + // misc + private val miscSb = scoreboardGroup.group("misc") + val essencePattern by miscSb.pattern( + "essence", + "^\\s*(.*)?Essence: §.(?<essence>-?\\d+(:?,\\d{3})*(?:\\.\\d+)?)$" + ) + val brokenRedstonePattern by miscSb.pattern( + "brokenredstone", + "\\s*e: §e§b\\d{1,3}%$" + ) + val redstonePattern by miscSb.pattern( + "redstone", + "\\s*(§.)*⚡ §cRedstone: (§.)*\\d{1,3}%$" + ) + val visitingPattern by miscSb.pattern( + "visiting", + "^\\s*§a✌ §7\\(§.\\d+(§.)?/\\d+(§.)?\\)$" + ) + val flightDurationPattern by miscSb.pattern( + "flightduration", + "^\\s*Flight Duration: §a(:?\\d{1,3})*$" + ) + val dojoChallengePattern by miscSb.pattern( + "dojochallenge", + "^(§.)*Challenge: (§.)*(?<challenge>[\\w ]+)$" + ) + val dojoDifficultyPattern by miscSb.pattern( + "dojodifficulty", + "^(§.)*Difficulty: (§.)*(?<difficulty>[\\w ]+)$" + ) + val dojoPointsPattern by miscSb.pattern( + "dojopoints", + "^(§.)*Points: (§.)*(?<points>[\\w,.]+) ?( (§.)*\\((§.)*[+-](§.)*(?<difference>[\\w,.]+)(§.)*\\))?\$" + ) + val dojoTimePattern by miscSb.pattern( + "dojotime", + "^(§.)*Time: (§.)*(?<time>(?<seconds>\\w+s))( (§.)*\\((§.)*[+-](§.)*(?<difference>[\\w,.]+)(§.)*\\))?$" + ) + val objectivePattern by miscSb.pattern( + "objective", + "^(§.)*(Objective|Quest).*" + ) + // this thirdObjectiveLinePattern includes all those weird objective lines that go into a third scoreboard line + val thirdObjectiveLinePattern by miscSb.pattern( + "thirdobjectiveline", + "(\\s*§.\\(§.\\w+§./§.\\w+§.\\)|§f Mages.*|§f Barbarians.*|§edefeat Kuudra|§eand stun him)" + ) + // collection of lines that just randomly exist and I have no clue how on earth to effectively remove them + val wtfAreThoseLinesPattern by miscSb.pattern( + "wtfarethoselines", + "^§eMine 10 Rubies$" + ) + val darkAuctionCurrentItemPattern by miscSb.pattern( + "darkauction.currentitem", + "^Current Item:$" + ) + + // events + private val eventsSb = scoreboardGroup.group("events") + val travelingZooPattern by eventsSb.pattern( + "travelingzoo", + "§aTraveling Zoo§f \\d{0,2}:\\d{2}$" + ) + val newYearPattern by eventsSb.pattern( + "newyear", + "§dNew Year Event!§f \\d{0,2}?:?\\d{2}$" + ) + val spookyPattern by eventsSb.pattern( + "spooky", + "§6Spooky Festival§f \\d{0,2}?:?\\d{2}$" + ) + val winterEventStartPattern by eventsSb.pattern( + "wintereventstart", + "(§.)*Event Start: §.\\d+:\\d+$" + ) + val winterNextWavePattern by eventsSb.pattern( + "wintereventnextwave", + "(§.)*Next Wave: (§.)*(\\d+:\\d+|Soon!)$" + ) + val winterWavePattern by eventsSb.pattern( + "wintereventwave", + "(§.)*Wave \\d+$" + ) + val winterMagmaLeftPattern by eventsSb.pattern( + "wintereventmagmaleft", + "(§.)*Magma Cubes Left: §.\\d+$" + ) + val winterTotalDmgPattern by eventsSb.pattern( + "wintereventtotaldmg", + "(§.)*Your Total Damage: §.\\d+( §e\\(#\\d+\\)?)?$" + ) + val winterCubeDmgPattern by eventsSb.pattern( + "wintereventcubedmg", + "(§.)*Your Cube Damage: §.\\d+$" + ) + + // rift + private val riftSb = scoreboardGroup.group("rift") + val riftDimensionPattern by riftSb.pattern( + "dimension", + "^\\s*§fRift Dimension$" + ) + + + // Stats from the tablist + private val tablistGroup = group.group("tablist") + val gemsPattern by tablistGroup.pattern( + "gems", + "^\\s*Gems: §a(?<gems>\\d*,?(\\.\\d+)?[a-zA-Z]?)$" + ) + val bankPattern by tablistGroup.pattern( + "bank", + "^\\s*Bank: §6(?<bank>[\\w.,]+(?:§7 \\/ §6(?<coop>[\\w.,]+))?)$" + ) + val mithrilPowderPattern by tablistGroup.pattern( + "mithrilpowder", + "^\\s*Gemstone Powder: (?:§.)+(?<mithrilpowder>[\\d,\\.]+)$" + ) + val gemstonePowderPattern by tablistGroup.pattern( + "gemstonepowder", + "^\\s*Gemstone Powder: (?:§.)+(?<gemstonepowder>[\\d,\\.]+)$" + ) + val eventNamePattern by tablistGroup.pattern( + "event", + "^\\s*§e§lEvent: §r(?<name>§.*)$" + ) + val eventTimeEndsPattern by tablistGroup.pattern( + "eventtime", + "^\\s+Ends In: §r§e(?<time>.*)$" + ) +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/UnknownLinesHandler.kt b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/UnknownLinesHandler.kt new file mode 100644 index 000000000..5fe788983 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/UnknownLinesHandler.kt @@ -0,0 +1,156 @@ +package at.hannibal2.skyhanni.features.gui.customscoreboard + +import at.hannibal2.skyhanni.data.BitsAPI +import at.hannibal2.skyhanni.data.PurseAPI +import at.hannibal2.skyhanni.data.ScoreboardData +import at.hannibal2.skyhanni.features.misc.ServerRestartTitle +import at.hannibal2.skyhanni.features.rift.area.stillgorechateau.RiftBloodEffigies +import at.hannibal2.skyhanni.utils.CollectionUtils.nextAfter +import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.StringUtils.removeResets +import at.hannibal2.skyhanni.features.gui.customscoreboard.ScoreboardPattern as SbPattern + +object UnknownLinesHandler { + fun handleUnknownLines() { + val sidebarLines = ScoreboardData.sidebarLinesFormatted + + unknownLines = sidebarLines.toMutableList().filter { it.isNotBlank() }.map { it.removeResets() } + + /* + * remove with pattern + */ + val patternsToExclude = listOf( + PurseAPI.coinsPattern, + SbPattern.motesPattern, + BitsAPI.bitsScoreboardPattern, + SbPattern.heatPattern, + SbPattern.copperPattern, + SbPattern.locationPattern, + SbPattern.lobbyCodePattern, + SbPattern.datePattern, + SbPattern.timePattern, + SbPattern.footerPattern, + SbPattern.yearVotesPattern, + SbPattern.votesPattern, + SbPattern.waitingForVotePattern, + SbPattern.northstarsPattern, + SbPattern.profileTypePattern, + SbPattern.autoClosingPattern, + SbPattern.startingInPattern, + SbPattern.timeElapsedPattern, + SbPattern.keysPattern, + SbPattern.clearedPattern, + SbPattern.soloPattern, + SbPattern.teammatesPattern, + SbPattern.floor3GuardiansPattern, + SbPattern.wavePattern, + SbPattern.tokensPattern, + SbPattern.submergesPattern, + SbPattern.medalsPattern, + SbPattern.lockedPattern, + SbPattern.cleanUpPattern, + SbPattern.pastingPattern, + SbPattern.peltsPattern, + SbPattern.mobLocationPattern, + SbPattern.jacobsContestPattern, + SbPattern.plotPattern, + SbPattern.powderPattern, + SbPattern.windCompassPattern, + SbPattern.windCompassArrowPattern, + SbPattern.miningEventPattern, + SbPattern.miningEventZonePattern, + SbPattern.raffleUselessPattern, + SbPattern.raffleTicketsPattern, + SbPattern.rafflePoolPattern, + SbPattern.mithrilUselessPattern, + SbPattern.mithrilRemainingPattern, + SbPattern.mithrilYourMithrilPattern, + SbPattern.nearbyPlayersPattern, + SbPattern.uselessGoblinPattern, + SbPattern.remainingGoblinPattern, + SbPattern.yourGoblinKillsPattern, + SbPattern.magmaBossPattern, + SbPattern.damageSoakedPattern, + SbPattern.killMagmasPattern, + SbPattern.killMagmasDamagedSoakedBarPattern, + SbPattern.reformingPattern, + SbPattern.bossHealthPattern, + SbPattern.bossHealthBarPattern, + SbPattern.broodmotherPattern, + SbPattern.bossHPPattern, + SbPattern.bossDamagePattern, + SbPattern.slayerQuestPattern, + SbPattern.essencePattern, + SbPattern.brokenRedstonePattern, + SbPattern.redstonePattern, + SbPattern.visitingPattern, + SbPattern.flightDurationPattern, + SbPattern.dojoChallengePattern, + SbPattern.dojoDifficultyPattern, + SbPattern.dojoPointsPattern, + SbPattern.dojoTimePattern, + SbPattern.objectivePattern, + ServerRestartTitle.restartingGreedyPattern, + SbPattern.travelingZooPattern, + SbPattern.newYearPattern, + SbPattern.spookyPattern, + SbPattern.winterEventStartPattern, + SbPattern.winterNextWavePattern, + SbPattern.winterWavePattern, + SbPattern.winterMagmaLeftPattern, + SbPattern.winterTotalDmgPattern, + SbPattern.winterCubeDmgPattern, + SbPattern.riftDimensionPattern, + RiftBloodEffigies.heartsPattern, + SbPattern.wtfAreThoseLinesPattern, + SbPattern.timeLeftPattern, + SbPattern.darkAuctionCurrentItemPattern, + ) + + unknownLines = unknownLines.filterNot { line -> + patternsToExclude.any { pattern -> pattern.matches(line) } + } + + + /* + * remove known text + */ + // remove objectives + val objectiveLine = + sidebarLines.firstOrNull { SbPattern.objectivePattern.matches(it) } + ?: "Objective" + unknownLines = unknownLines.filter { sidebarLines.nextAfter(objectiveLine) != it } + unknownLines = unknownLines.filter { + sidebarLines.nextAfter(objectiveLine, 2) != it + && !SbPattern.thirdObjectiveLinePattern.matches(it) + } + + // Remove jacobs contest + for (i in 1..3) + unknownLines = unknownLines.filter { + sidebarLines.nextAfter(sidebarLines.firstOrNull { line -> + SbPattern.jacobsContestPattern.matches(line) + } ?: "§eJacob's Contest", i) != it + } + + // Remove slayer + for (i in 1..2) + unknownLines = unknownLines.filter { + sidebarLines.nextAfter(sidebarLines.firstOrNull { line -> + SbPattern.slayerQuestPattern.matches(line) + } ?: "Slayer Quest", i) != it + } + + // remove trapper mob location + unknownLines = unknownLines.filter { + sidebarLines.nextAfter(sidebarLines.firstOrNull { line -> + SbPattern.mobLocationPattern.matches(line) + } ?: "Tracker Mob Location:") != it + } + + // da + unknownLines = unknownLines.filter { sidebarLines.nextAfter(sidebarLines.firstOrNull { line -> + SbPattern.darkAuctionCurrentItemPattern.matches(line) + } ?: "Current Item:") != it } + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/inventory/MaxPurseItems.kt b/src/main/java/at/hannibal2/skyhanni/features/inventory/MaxPurseItems.kt index d835fc5a8..53b74c7f7 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/inventory/MaxPurseItems.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/inventory/MaxPurseItems.kt @@ -45,7 +45,7 @@ class MaxPurseItems { for (info in item.getLore()) { orderPattern.matchMatcher(info) { // +0.1 because I expect people to use the gold nugget option - buyOrderPrice = group("coins").formatDouble()?.let { it + 0.1 } ?: 0.0 + buyOrderPrice = group("coins").formatDouble() + 0.1 // If we get to this point, we have the instant price because instant is earlier in the list of items // So we can return return @@ -55,7 +55,7 @@ class MaxPurseItems { createInstantPattern.matchMatcher(name) { for (info in item.getLore()) { instantPattern.matchMatcher(info) { - instantBuyPrice = group("coins").formatDouble() ?: 0.0 + instantBuyPrice = group("coins").formatDouble() } } } 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 ff27bd43f..2f1c14f53 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.Perk import at.hannibal2.skyhanni.data.jsonobjects.repo.MinionXPJson import at.hannibal2.skyhanni.events.IslandChangeEvent import at.hannibal2.skyhanni.events.LorenzToolTipEvent @@ -103,7 +103,7 @@ class MinionXp { // TODO add wisdom and temporary skill exp (Events) to calculation val baseXp = xp.amount * item.amount - val xpAmount = if (MayorElection.isPerkActive("Derpy", "MOAR SKILLZ!!!")) { + val xpAmount = if (Perk.MOAR_SKILLZ.isActive) { baseXp * 1.5 } else baseXp 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 34196acbe..a236daf4e 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/ServerRestartTitle.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/ServerRestartTitle.kt @@ -16,10 +16,17 @@ class ServerRestartTitle { private val config get() = SkyHanniMod.feature.misc - private val restartPattern by RepoPattern.pattern( - "misc.serverrestart.restart", - "§cServer closing: (?<minutes>\\d+):(?<seconds>\\d+) §8.*" - ) + companion object { + private val restartingGroup = RepoPattern.group("features.misc.serverrestart") + private val restartingPattern by restartingGroup.pattern( + "time", + "§cServer closing: (?<minutes>\\d+):(?<seconds>\\d+) ?§8.*" + ) + val restartingGreedyPattern by restartingGroup.pattern( + "greedy", + "§cServer closing:.*" + ) + } @SubscribeEvent fun onTick(event: LorenzTickEvent) { @@ -29,7 +36,7 @@ class ServerRestartTitle { if (!event.repeatSeconds(1)) return for (line in ScoreboardData.sidebarLinesFormatted) { - restartPattern.matchMatcher(line) { + restartingPattern.matchMatcher(line) { try { val minutes = group("minutes").toInt().minutes val seconds = group("seconds").toInt().seconds @@ -39,9 +46,9 @@ class ServerRestartTitle { LorenzUtils.sendTitle("§cServer Restart in §b$time", 2.seconds) } catch (e: Throwable) { ErrorManager.logErrorWithData( - e, "Error reading server restart time from socreboard", + e, "Error reading server restart time from scoreboard", "line" to line, - "restartPattern" to restartPattern.pattern(), + "restartPattern" to restartingPattern.pattern(), ) } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListReader.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListReader.kt index 07473f9d9..bba2ff6aa 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListReader.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListReader.kt @@ -2,14 +2,12 @@ package at.hannibal2.skyhanni.features.misc.compacttablist import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.events.LorenzTickEvent -import at.hannibal2.skyhanni.mixins.transformers.AccessorGuiPlayerTabOverlay import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.StringUtils.removeResets import at.hannibal2.skyhanni.utils.StringUtils.removeSFormattingCode import at.hannibal2.skyhanni.utils.StringUtils.trimWhiteSpaceAndResets import at.hannibal2.skyhanni.utils.TabListData import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern -import net.minecraft.client.Minecraft import net.minecraftforge.fml.common.eventhandler.SubscribeEvent // heavily inspired by SBA code @@ -99,7 +97,7 @@ object TabListReader { } private fun parseFooterAsColumn(): TabColumn? { - val tabList = Minecraft.getMinecraft().ingameGUI.tabList as AccessorGuiPlayerTabOverlay + val tabList = TabListData.getPlayerTabOverlay() if (tabList.footer_skyhanni == null) { return null diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListRenderer.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListRenderer.kt index c6aa9a510..6f2d5d636 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListRenderer.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListRenderer.kt @@ -3,11 +3,11 @@ package at.hannibal2.skyhanni.features.misc.compacttablist import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.events.SkipTabListLineEvent -import at.hannibal2.skyhanni.mixins.transformers.AccessorGuiPlayerTabOverlay import at.hannibal2.skyhanni.utils.CollectionUtils.filterToMutable import at.hannibal2.skyhanni.utils.KeyboardManager.isActive import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.StringUtils.matches +import at.hannibal2.skyhanni.utils.TabListData.Companion.getPlayerTabOverlay import at.hannibal2.skyhanni.utils.repopatterns.RepoPattern import net.minecraft.client.Minecraft import net.minecraft.client.gui.Gui @@ -78,7 +78,7 @@ object TabListRenderer { } var totalHeight = maxLines * lineHeight - val tabList = Minecraft.getMinecraft().ingameGUI.tabList as AccessorGuiPlayerTabOverlay + val tabList = getPlayerTabOverlay() var header = listOf<String>() if (tabList.header_skyhanni != null) { diff --git a/src/main/java/at/hannibal2/skyhanni/features/rift/area/stillgorechateau/RiftBloodEffigies.kt b/src/main/java/at/hannibal2/skyhanni/features/rift/area/stillgorechateau/RiftBloodEffigies.kt index cd18de35a..be05e11fb 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/rift/area/stillgorechateau/RiftBloodEffigies.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/rift/area/stillgorechateau/RiftBloodEffigies.kt @@ -28,15 +28,6 @@ class RiftBloodEffigies { private val config get() = RiftAPI.config.area.stillgoreChateau.bloodEffigies - private val patternGroup = RepoPattern.group("rift.area.stillgore.effegies") - private val effigiesTimerPattern by patternGroup.pattern( - "respawn", - "§eRespawn §c(?<time>.*) §7\\(or click!\\)" - ) - private val effegieHeartPattern by patternGroup.pattern( - "heart", - "Effigies: (?<hearts>.*)" - ) private var locations: List<LorenzVec> = emptyList() private var effigiesTimes = mapOf( @@ -48,6 +39,18 @@ class RiftBloodEffigies { 5 to -1L, ) + companion object { + private val group = RepoPattern.group("rift.area.stillgore.effegies") + val effigiesTimerPattern by group.pattern( + "respawn", + "§eRespawn §c(?<time>.*) §7\\(or click!\\)" + ) + val heartsPattern by group.pattern( + "heart", + "Effigies: (?<hearts>((§[7c])?⧯)*)" + ) + } + @SubscribeEvent fun onWorldChange(event: LorenzWorldChangeEvent) { effigiesTimes = mapOf( @@ -74,7 +77,7 @@ class RiftBloodEffigies { if (!isEnabled()) return val line = event.newList.firstOrNull { it.startsWith("Effigies:") } ?: return - val hearts = effegieHeartPattern.matchMatcher(line) { + val hearts = heartsPattern.matchMatcher(line) { group("hearts") } ?: return diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/hooks/GuiIngameHook.kt b/src/main/java/at/hannibal2/skyhanni/mixins/hooks/GuiIngameHook.kt index 9d1e7ab87..0bf44d0a0 100644 --- a/src/main/java/at/hannibal2/skyhanni/mixins/hooks/GuiIngameHook.kt +++ b/src/main/java/at/hannibal2/skyhanni/mixins/hooks/GuiIngameHook.kt @@ -15,11 +15,16 @@ fun drawString( x: Int, y: Int, color: Int, -) = replaceString(text)?.let { +) = tryToReplaceScoreboardLine(text)?.let { instance.drawString(it, x, y, color) } ?: 0 -private fun replaceString(text: String): String? { +/** + * Tries to replace a scoreboard line with a modified one + * @param text The line to check and possibly replace + * @return The replaced line, or null if it should be hidden + */ +fun tryToReplaceScoreboardLine(text: String): String? { if (SkyHanniMod.feature.misc.hideScoreboardNumbers && text.startsWith("§c") && text.length <= 4) { return null } diff --git a/src/main/java/at/hannibal2/skyhanni/test/DebugCommand.kt b/src/main/java/at/hannibal2/skyhanni/test/DebugCommand.kt index 3ffe8f8e6..4a379fed6 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/DebugCommand.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/DebugCommand.kt @@ -58,7 +58,7 @@ object DebugCommand { private fun profileType(event: DebugDataCollectEvent) { event.title("Profile Type") if (!LorenzUtils.inSkyBlock) { - event.addIrrelevant("Not on SkyBlcok") + event.addIrrelevant("Not on SkyBlock") return } @@ -81,7 +81,7 @@ object DebugCommand { private fun profileName(event: DebugDataCollectEvent) { event.title("Profile Name") if (!LorenzUtils.inSkyBlock) { - event.addIrrelevant("Not on SkyBlcok") + event.addIrrelevant("Not on SkyBlock") return } diff --git a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt index b2b6bccb3..cd5d9bf0b 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt @@ -129,7 +129,7 @@ class SkyHanniDebugsAndTests { // b = args[1].toDouble() // c = args[2].toDouble() -// for (line in (Minecraft.getMinecraft().ingameGUI.tabList as AccessorGuiPlayerTabOverlay).footer.unformattedText +// for (line in getPlayerTabOverlay().footer.unformattedText // .split("\n")) { // println("footer: '$line'") // } diff --git a/src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt b/src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt index 4c0a3cf27..16757d255 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt @@ -101,8 +101,9 @@ object ErrorManager { message: String, vararg extraData: Pair<String, Any?>, ignoreErrorCache: Boolean = false, + noStackTrace: Boolean = false, ) { - logError(throwable, message, ignoreErrorCache, noStackTrace = false, *extraData) + logError(throwable, message, ignoreErrorCache, noStackTrace, *extraData) } private fun logError( diff --git a/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt index b93f17fa4..21f731fc2 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt @@ -51,6 +51,9 @@ object CollectionUtils { fun <K> MutableMap<K, Double>.addOrPut(key: K, number: Double): Double = this.merge(key, number, Double::plus)!! // Never returns null since "plus" can't return null + fun <K> MutableMap<K, Float>.addOrPut(key: K, number: Float): Float = + this.merge(key, number, Float::plus)!! // Never returns null since "plus" can't return null + fun <K, N : Number> Map<K, N>.sumAllValues(): Double { if (values.isEmpty()) return 0.0 @@ -81,6 +84,30 @@ object CollectionUtils { return null } + fun List<String>.removeNextAfter(after: String, skip: Int = 1) = removeNextAfter({ it == after }, skip) + + fun List<String>.removeNextAfter(after: (String) -> Boolean, skip: Int = 1): List<String> { + val newList = mutableListOf<String>() + var missing = -1 + for (line in this) { + if (after(line)) { + missing = skip - 1 + continue + } + if (missing == 0) { + missing-- + continue + } + if (missing != -1) { + missing-- + } + newList.add(line) + } + return newList + } + + fun List<String>.addIfNotNull(element: String?) = element?.let { plus(it) } ?: this + fun <K, V> Map<K, V>.editCopy(function: MutableMap<K, V>.() -> Unit) = toMutableMap().also { function(it) }.toMap() diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt index a2788a6fa..ed0af59eb 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt @@ -3,7 +3,7 @@ package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.data.HypixelData import at.hannibal2.skyhanni.data.IslandType -import at.hannibal2.skyhanni.data.MayorElection +import at.hannibal2.skyhanni.data.Perk import at.hannibal2.skyhanni.data.TitleManager import at.hannibal2.skyhanni.events.GuiContainerEvent import at.hannibal2.skyhanni.features.dungeon.DungeonAPI @@ -270,6 +270,8 @@ object LorenzUtils { fun IslandType.isInIsland() = inSkyBlock && skyBlockIsland == this + fun inAnyIsland(vararg islandTypes: IslandType) = inSkyBlock && islandTypes.any { it.isInIsland() } + fun GuiContainerEvent.SlotClickEvent.makeShiftClick() { if (this.clickedButton == 1 && slot?.stack?.getItemCategoryOrNull() == ItemCategory.SACK) return slot?.slotNumber?.let { slotNumber -> @@ -281,7 +283,7 @@ object LorenzUtils { } private val recalculateDerpy = - RecalculatingValue(1.seconds) { MayorElection.isPerkActive("Derpy", "DOUBLE MOBS HP!!!") } + RecalculatingValue(1.seconds) { Perk.DOUBLE_MOBS_HP.isActive } val isDerpy get() = recalculateDerpy.getValue() diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt index 92cbe4d57..784b7e9ac 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt @@ -236,20 +236,22 @@ object NumberUtil { 1_000.0 } else if (text.endsWith("m")) { text = text.substring(0, text.length - 1) - 1.milion + 1.million } else if (text.endsWith("b")) { text = text.substring(0, text.length - 1) - 1.bilion + 1.billion } else 1.0 return text.toDoubleOrNull()?.let { it * multiplier } } - val Int.milion get() = this * 1_000_000.0 - private val Int.bilion get() = this * 1_000_000_000.0 - val Double.milion get() = (this * 1_000_000.0).toLong() + // Sometimes we just take an L, never find it and forget to write it down + val Int.million get() = this * 1_000_000.0 + private val Int.billion get() = this * 1_000_000_000.0 + val Double.million get() = (this * 1_000_000.0).toLong() + /** @return clamped to [0.0, 1.0]**/ fun Number.fractionOf(maxValue: Number) = maxValue.toDouble().takeIf { it != 0.0 }?.let { max -> this.toDouble() / max diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt index 32ea32329..d40f98667 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt @@ -4,6 +4,7 @@ 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.events.GuiRenderItemEvent import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent import at.hannibal2.skyhanni.features.misc.RoundedRectangleShader @@ -37,7 +38,16 @@ import kotlin.time.DurationUnit object RenderUtils { - enum class HorizontalAlignment { LEFT, CENTER, RIGHT } + enum class HorizontalAlignment(private val value: String) { + LEFT("Left"), + CENTER("Center"), + RIGHT("Right"), + ; + + override fun toString(): String { + return value + } + } enum class VerticalAlignment { TOP, CENTER, BOTTOM } private val beaconBeam = ResourceLocation("textures/entity/beacon_beam.png") @@ -401,6 +411,36 @@ object RenderUtils { return renderer.getStringWidth(display) } + // Aligns using the width of element to render + private fun Position.renderString0( + string: String?, + offsetX: Int = 0, + offsetY: Int = 0, + alignmentEnum: HorizontalAlignment + ): Int { + val display = "§f$string" + GlStateManager.pushMatrix() + transform() + val minecraft = Minecraft.getMinecraft() + val renderer = minecraft.renderManager.fontRenderer + val width = this.getDummySize().x / this.scale + + GlStateManager.translate(offsetX + 1.0, offsetY + 1.0, 0.0) + + val strLen: Int = renderer.getStringWidth(string) + val x2 = when (alignmentEnum) { + HorizontalAlignment.LEFT -> offsetX.toFloat() + HorizontalAlignment.CENTER -> offsetX + width / 2f - strLen / 2f + HorizontalAlignment.RIGHT -> offsetX + width - strLen.toFloat() + } + GL11.glTranslatef(x2, 0f, 0f) + renderer.drawStringWithShadow(display, 0f, 0f, 0) + + GlStateManager.popMatrix() + + return renderer.getStringWidth(display) + } + fun Position.renderStrings(list: List<String>, extraSpace: Int = 0, posLabel: String) { if (list.isEmpty()) return @@ -416,6 +456,25 @@ object RenderUtils { GuiEditManager.add(this, posLabel, longestX, offsetY) } + fun Position.renderStringsAlignedWidth( + list: List<Pair<String, HorizontalAlignment>>, + extraSpace: Int = 0, + posLabel: String + ) { + if (list.isEmpty()) return + + var offsetY = 0 + var longestX = 0 + for (pair in list) { + val x = renderString0(pair.first, offsetY = offsetY, alignmentEnum = pair.second) + if (x > longestX) { + longestX = x + } + offsetY += 10 + extraSpace + } + GuiEditManager.add(this, posLabel, longestX, offsetY) + } + fun Position.renderRenderables( renderables: List<Renderable>, extraSpace: Int = 0, diff --git a/src/main/java/at/hannibal2/skyhanni/utils/SimpleTimeMark.kt b/src/main/java/at/hannibal2/skyhanni/utils/SimpleTimeMark.kt index 784eb85ec..d6df48a67 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/SimpleTimeMark.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/SimpleTimeMark.kt @@ -35,6 +35,8 @@ value class SimpleTimeMark(private val millis: Long) : Comparable<SimpleTimeMark fun toSkyBlockTime() = SkyBlockTime.fromInstant(Instant.ofEpochMilli(millis)) + fun elapsedMinutes() = passedSince().inWholeMinutes + companion object { fun now() = SimpleTimeMark(System.currentTimeMillis()) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt b/src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt index 4fd442c09..a96a31789 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt @@ -24,6 +24,7 @@ class TabListData { private var cache = emptyList<String>() private var debugCache: List<String>? = null + var fullyLoaded = false // TODO replace with TabListUpdateEvent fun getTabList() = debugCache ?: cache @@ -53,7 +54,7 @@ class TabListData { val tabListLine = line.transformIf({ noColor }) { removeColor() } if (tabListLine != "") resultList.add("'$tabListLine'") } - val tabList = Minecraft.getMinecraft().ingameGUI.tabList as AccessorGuiPlayerTabOverlay + val tabList = getPlayerTabOverlay() val tabHeader = tabList.header_skyhanni.conditionalTransform(noColor, { unformattedText }, { formattedText }) val tabFooter = @@ -62,6 +63,10 @@ class TabListData { OSUtils.copyToClipboard(string) ChatUtils.chat("Tab list copied into the clipboard!") } + + fun getPlayerTabOverlay(): AccessorGuiPlayerTabOverlay { + return Minecraft.getMinecraft().ingameGUI.tabList as AccessorGuiPlayerTabOverlay + } } private val playerOrdering = Ordering.from(PlayerComparator()) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt index d2b269e9a..58bdb2d77 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt @@ -1,5 +1,6 @@ package at.hannibal2.skyhanni.utils +import at.hannibal2.skyhanni.mixins.hooks.tryToReplaceScoreboardLine import at.hannibal2.skyhanni.utils.NumberUtil.addSeparators import at.hannibal2.skyhanni.utils.StringUtils.matchMatcher import io.github.moulberry.notenoughupdates.util.SkyBlockTime @@ -129,19 +130,36 @@ object TimeUtils { }.toLong().toDuration(DurationUnit.MILLISECONDS) } - fun SkyBlockTime.formatted(): String { + fun SkyBlockTime.formatted( + dayAndMonthElement: Boolean = true, + yearElement: Boolean = true, + hoursAndMinutesElement: Boolean = true + ): String { val hour = if (this.hour > 12) this.hour - 12 else this.hour - val timeOfDay = if (this.hour > 11) "pm" else "am" // hooray for 12-hour clocks - var minute = this.minute.toString() - if (minute.length != 2) { - minute = minute.padStart(2, '0') - } - + val timeOfDay = if (this.hour > 11) "pm" else "am" + val minute = this.minute.toString().padStart(2, '0') val month = SkyBlockTime.monthName(this.month) val day = this.day val daySuffix = SkyBlockTime.daySuffix(day) val year = this.year - return "$month $day$daySuffix, Year $year $hour:${minute}$timeOfDay" // Early Winter 1st Year 300, 12:03pm + + val datePart = when { + yearElement -> "$month $day$daySuffix, Year $year" + dayAndMonthElement -> "$month $day$daySuffix" + else -> "" + } + val timePart = if (hoursAndMinutesElement) "$hour:$minute$timeOfDay" else "" + + /** + * We replace the line here, because the user might want color month names + */ + return tryToReplaceScoreboardLine( + if (datePart.isNotEmpty() && timePart.isNotEmpty()) { + "$datePart, $timePart" + } else { + "$datePart$timePart".trim() + } + ) ?: "" } fun getCurrentLocalDate(): LocalDate = LocalDate.now(ZoneId.of("UTC")) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt b/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt index 9398d0e53..3c6bc5ce0 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt @@ -19,13 +19,13 @@ object ShaderManager { /** * For any future shaders add the object instance in this enum and - * in the when expression + * in the when-expression */ enum class Shaders(val shader: Shader) { - STANDARD_CHROMA(StandardChromaShader.INSTANCE), TEXTURED_CHROMA(TexturedChromaShader.INSTANCE), - ROUNDED_RECTANGLE(RoundedRectangleShader.INSTANCE); + ROUNDED_RECTANGLE(RoundedRectangleShader.INSTANCE), + ; companion object { diff --git a/src/main/resources/assets/skyhanni/scoreboard.png b/src/main/resources/assets/skyhanni/scoreboard.png Binary files differnew file mode 100644 index 000000000..acd9d8586 --- /dev/null +++ b/src/main/resources/assets/skyhanni/scoreboard.png |