aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJ10a1n15 <45315647+j10a1n15@users.noreply.github.com>2024-03-12 19:59:55 +0100
committerGitHub <noreply@github.com>2024-03-12 19:59:55 +0100
commit4352ffb08d4bfffc06adad2a068f375ab9874333 (patch)
treeb8ce745fca8ab82ffa04f6ab87334139a6b7192b /src
parenteb863d60e82b5541a9f42d2608f61cb97ada209b (diff)
downloadskyhanni-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')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt16
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/Storage.java37
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/core/config/gui/GuiPositionEditor.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/dev/DevConfig.java5
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/GUIConfig.java6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/AlignmentConfig.java20
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/BackgroundConfig.java66
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/CustomScoreboardConfig.java65
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/DisplayConfig.java96
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/InformationFilteringConfig.java23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/MayorConfig.java17
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/PartyConfig.java23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/gui/customscoreboard/TitleAndFooterConfig.java32
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/misc/MiscConfig.java2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/ArrowType.kt9
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/BitsAPI.kt200
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/FameRanks.kt26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/GuiEditManager.kt15
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/HypixelData.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/MaxwellAPI.kt152
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/MayorAPI.kt121
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/MayorElection.kt79
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/Mayors.kt105
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/PartyAPI.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/PurseAPI.kt14
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/QuiverAPI.kt256
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/ScoreboardData.kt18
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/SlayerAPI.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/ArrowTypeJson.java14
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/FameRankJson.java24
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/ItemsJson.java3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/data/jsonobjects/repo/MaxwellPowersJson.java10
-rw-r--r--src/main/java/at/hannibal2/skyhanni/events/GuiPositionMovedEvent.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/bingo/BingoAPI.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/event/diana/DianaAPI.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/garden/contest/FarmingContestAPI.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/garden/farming/GardenCropSpeed.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/CustomScoreboard.kt130
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/CustomScoreboardUtils.kt53
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/RenderBackground.kt87
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardElements.kt635
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardEvents.kt498
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/ScoreboardPattern.kt404
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/gui/customscoreboard/UnknownLinesHandler.kt156
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/inventory/MaxPurseItems.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/minion/MinionXp.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/ServerRestartTitle.kt21
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListReader.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/compacttablist/TabListRenderer.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/rift/area/stillgorechateau/RiftBloodEffigies.kt23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/mixins/hooks/GuiIngameHook.kt9
-rw-r--r--src/main/java/at/hannibal2/skyhanni/test/DebugCommand.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/test/SkyHanniDebugsAndTests.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/test/command/ErrorManager.kt3
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt27
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/NumberUtil.kt12
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt61
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/SimpleTimeMark.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/TabListData.kt7
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/TimeUtils.kt34
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt6
-rw-r--r--src/main/resources/assets/skyhanni/scoreboard.pngbin0 -> 65859 bytes
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
new file mode 100644
index 000000000..acd9d8586
--- /dev/null
+++ b/src/main/resources/assets/skyhanni/scoreboard.png
Binary files differ