aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget
diff options
context:
space:
mode:
authorYasin <a.piri@hotmail.de>2023-10-09 12:58:02 +0200
committerYasin <a.piri@hotmail.de>2023-10-09 12:58:02 +0200
commitbd3f0329d0e391bd84b5f9e3ff207d9dd9815853 (patch)
tree2fd1d1ef625f57acc2e4916c967d8d2393844798 /src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget
parent2315b90da8117f28f66348927afdb621ee4fc815 (diff)
downloadSkyblocker-bd3f0329d0e391bd84b5f9e3ff207d9dd9815853.tar.gz
Skyblocker-bd3f0329d0e391bd84b5f9e3ff207d9dd9815853.tar.bz2
Skyblocker-bd3f0329d0e391bd84b5f9e3ff207d9dd9815853.zip
new pr because fixing merge conflict would take too long
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget')
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CameraPositionWidget.java37
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CommsWidget.java63
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ComposterWidget.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CookieWidget.java50
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java68
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java47
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDownedWidget.java44
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java103
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPuzzleWidget.java57
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java26
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonServerWidget.java48
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EffectWidget.java67
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java104
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ErrorWidget.java32
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EssenceWidget.java47
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EventWidget.java35
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java68
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ForgeWidget.java81
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenServerWidget.java54
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java80
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenVisitorsWidget.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GuestServerWidget.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandGuestsWidget.java47
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandOwnersWidget.java66
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandSelfWidget.java43
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandServerWidget.java32
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java62
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/MinionWidget.java151
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ParkServerWidget.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java71
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java29
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ProfileWidget.java28
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/QuestWidget.java33
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java69
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ServerWidget.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java78
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TrapperWidget.java25
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/UpgradeWidget.java51
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/VolcanoWidget.java59
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/Widget.java216
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/Component.java31
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/IcoFatTextComponent.java45
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/IcoTextComponent.java40
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlainTextComponent.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java39
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java69
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java58
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/hud/HudCommsWidget.java73
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/AdvertisementWidget.java35
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/GoodToKnowWidget.java69
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftProfileWidget.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftProgressWidget.java123
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftServerInfoWidget.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftStatsWidget.java43
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/ShenWidget.java22
55 files changed, 3046 insertions, 0 deletions
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CameraPositionWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CameraPositionWidget.java
new file mode 100644
index 00000000..9cff3d32
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CameraPositionWidget.java
@@ -0,0 +1,37 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.math.MathHelper;
+
+public class CameraPositionWidget extends Widget {
+ private static final MutableText TITLE = Text.literal("Camera Pos").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+
+ public CameraPositionWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ double yaw = CLIENT.getCameraEntity().getYaw();
+ double pitch = CLIENT.getCameraEntity().getPitch();
+
+ this.addComponent(
+ new PlainTextComponent(Text.literal("Yaw: " + roundToDecimalPlaces(MathHelper.wrapDegrees(yaw), 3))));
+ this.addComponent(new PlainTextComponent(
+ Text.literal("Pitch: " + roundToDecimalPlaces(MathHelper.wrapDegrees(pitch), 3))));
+
+ }
+
+ // https://stackoverflow.com/a/33889423
+ private static double roundToDecimalPlaces(double value, int decimalPlaces) {
+ double shift = Math.pow(10, decimalPlaces);
+
+ return Math.round(value * shift) / shift;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CommsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CommsWidget.java
new file mode 100644
index 00000000..e8bf91ab
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CommsWidget.java
@@ -0,0 +1,63 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.math.MathHelper;
+
+// this widget shows the status of the king's commissions.
+// (dwarven mines and crystal hollows)
+
+public class CommsWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Commissions").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ // match a comm
+ // group 1: comm name
+ // group 2: comm progress (without "%" for comms that show a percentage)
+ private static final Pattern COMM_PATTERN = Pattern.compile("(?<name>.*): (?<progress>.*)%?");
+
+ public CommsWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ for (int i = 50; i <= 53; i++) {
+ Matcher m = PlayerListMgr.regexAt(i, COMM_PATTERN);
+ // end of comms found?
+ if (m == null) {
+ if (i == 50) {
+ this.addComponent(new IcoTextComponent());
+ }
+ break;
+ }
+
+ ProgressComponent pc;
+
+ String name = m.group("name");
+ String progress = m.group("progress");
+
+ if (progress.equals("DONE")) {
+ pc = new ProgressComponent(Ico.BOOK, Text.of(name), Text.of(progress), 100f, pcntToCol(100));
+ } else {
+ float pcnt = Float.parseFloat(progress.substring(0, progress.length() - 1));
+ pc = new ProgressComponent(Ico.BOOK, Text.of(name), pcnt, pcntToCol(pcnt));
+ }
+ this.addComponent(pc);
+ }
+ }
+
+ private int pcntToCol(float pcnt) {
+ return MathHelper.hsvToRgb(pcnt / 300f, 0.9f, 0.9f);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ComposterWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ComposterWidget.java
new file mode 100644
index 00000000..fbeb5ae5
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ComposterWidget.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about the garden's composter
+
+public class ComposterWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Composter").formatted(Formatting.GREEN,
+ Formatting.BOLD);
+
+ public ComposterWidget() {
+ super(TITLE, Formatting.GREEN.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.SAPLING, "Organic Matter:", Formatting.YELLOW, 48);
+ this.addSimpleIcoText(Ico.FURNACE, "Fuel:", Formatting.BLUE, 49);
+ this.addSimpleIcoText(Ico.CLOCK, "Time Left:", Formatting.RED, 50);
+ this.addSimpleIcoText(Ico.COMPOSTER, "Stored Compost:", Formatting.DARK_GREEN, 51);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CookieWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CookieWidget.java
new file mode 100644
index 00000000..a5883e7e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CookieWidget.java
@@ -0,0 +1,50 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about active super cookies
+// or not, if you're unwilling to buy one
+
+public class CookieWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Cookie Info").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ private static final Pattern COOKIE_PATTERN = Pattern.compile(".*\\nCookie Buff\\n(?<buff>.*)\\n");
+
+ public CookieWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ String footertext = PlayerListMgr.getFooter();
+ if (footertext == null || !footertext.contains("Cookie Buff")) {
+ this.addComponent(new IcoTextComponent());
+ return;
+ }
+
+ Matcher m = COOKIE_PATTERN.matcher(footertext);
+ if (!m.find() || m.group("buff") == null) {
+ this.addComponent(new IcoTextComponent());
+ return;
+ }
+
+ String buff = m.group("buff");
+ if (buff.startsWith("Not")) {
+ this.addComponent(new IcoTextComponent(Ico.COOKIE, Text.of("Not active")));
+ } else {
+ Text cookie = Text.literal("Time Left: ").append(buff);
+ this.addComponent(new IcoTextComponent(Ico.COOKIE, cookie));
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java
new file mode 100644
index 00000000..fd896796
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java
@@ -0,0 +1,68 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+// this widget shows a list of obtained dungeon buffs
+
+public class DungeonBuffWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Dungeon Buffs").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ public DungeonBuffWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+
+ String footertext = PlayerListMgr.getFooter();
+
+ if (footertext == null || !footertext.contains("Dungeon Buffs")) {
+ this.addComponent(new PlainTextComponent(Text.literal("No data").formatted(Formatting.GRAY)));
+ return;
+ }
+
+ String interesting = footertext.split("Dungeon Buffs")[1];
+ String[] lines = interesting.split("\n");
+
+ if (!lines[1].startsWith("Blessing")) {
+ this.addComponent(new PlainTextComponent(Text.literal("No buffs found!").formatted(Formatting.GRAY)));
+ return;
+ }
+
+ //Filter out text unrelated to blessings
+ lines = Arrays.stream(lines).filter(s -> s.contains("Blessing")).toArray(String[]::new);
+
+ //Alphabetically sort the blessings
+ Arrays.sort(lines, Comparator.comparing(String::toLowerCase));
+
+ for (String line : lines) {
+ if (line.length() < 3) { // empty line is §s
+ break;
+ }
+ int color = getBlessingColor(line);
+ this.addComponent(new PlainTextComponent(Text.literal(line).styled(style -> style.withColor(color))));
+ }
+
+ }
+
+ @SuppressWarnings("DataFlowIssue")
+ public int getBlessingColor(String blessing) {
+ if (blessing.contains("Life")) return Formatting.LIGHT_PURPLE.getColorValue();
+ if (blessing.contains("Power")) return Formatting.RED.getColorValue();
+ if (blessing.contains("Stone")) return Formatting.GREEN.getColorValue();
+ if (blessing.contains("Time")) return 0xafb8c1;
+ if (blessing.contains("Wisdom")) return Formatting.AQUA.getColorValue();
+
+ return 0xffffff;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java
new file mode 100644
index 00000000..9c299210
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java
@@ -0,0 +1,47 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows various dungeon info
+// deaths, healing, dmg taken, milestones
+
+public class DungeonDeathWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Death").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ // match the deaths entry
+ // group 1: amount of deaths
+ private static final Pattern DEATH_PATTERN = Pattern.compile("Team Deaths: (?<deathnum>\\d+).*");
+
+ public DungeonDeathWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ Matcher m = PlayerListMgr.regexAt(25, DEATH_PATTERN);
+ if (m == null) {
+ this.addComponent(new IcoTextComponent());
+ } else {
+ Formatting f = (m.group("deathnum").equals("0")) ? Formatting.GREEN : Formatting.RED;
+ Text d = Widget.simpleEntryText(m.group("deathnum"), "Deaths: ", f);
+ IcoTextComponent deaths = new IcoTextComponent(Ico.SKULL, d);
+ this.addComponent(deaths);
+ }
+
+ this.addSimpleIcoText(Ico.SWORD, "Damage Dealt:", Formatting.RED, 26);
+ this.addSimpleIcoText(Ico.POTION, "Healing Done:", Formatting.RED, 27);
+ this.addSimpleIcoText(Ico.NTAG, "Milestone:", Formatting.YELLOW, 28);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDownedWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDownedWidget.java
new file mode 100644
index 00000000..9a8de0eb
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDownedWidget.java
@@ -0,0 +1,44 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about... something?
+// related to downed people in dungeons, not sure what this is supposed to show
+
+public class DungeonDownedWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Downed").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ public DungeonDownedWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ String down = PlayerListMgr.strAt(21);
+ if (down == null) {
+ this.addComponent(new IcoTextComponent());
+ } else {
+
+ Formatting format = Formatting.RED;
+ if (down.endsWith("NONE")) {
+ format = Formatting.GRAY;
+ }
+ int idx = down.indexOf(": ");
+ Text downed = (idx == -1) ? null
+ : Widget.simpleEntryText(down.substring(idx + 2), "Downed: ", format);
+ IcoTextComponent d = new IcoTextComponent(Ico.SKULL, downed);
+ this.addComponent(d);
+ }
+
+ this.addSimpleIcoText(Ico.CLOCK, "Time:", Formatting.GRAY, 22);
+ this.addSimpleIcoText(Ico.POTION, "Revive:", Formatting.GRAY, 23);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java
new file mode 100644
index 00000000..be1a3c6e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java
@@ -0,0 +1,103 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about a player in the current dungeon group
+
+public class DungeonPlayerWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Player").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ // match a player entry
+ // group 1: name
+ // group 2: class (or literal "EMPTY" pre dungeon start)
+ // group 3: level (or nothing, if pre dungeon start)
+ // this regex filters out the ironman icon as well as rank prefixes and emblems
+ // \[\d*\] (?:\[[A-Za-z]+\] )?(?<name>[A-Za-z0-9_]*) (?:.* )?\((?<class>\S*) ?(?<level>[LXVI]*)\)
+ private static final Pattern PLAYER_PATTERN = Pattern
+ .compile("\\[\\d*\\] (?:\\[[A-Za-z]+\\] )?(?<name>[A-Za-z0-9_]*) (?:.* )?\\((?<class>\\S*) ?(?<level>[LXVI]*)\\)");
+
+ private static final HashMap<String, ItemStack> ICOS = new HashMap<>();
+ private static final ArrayList<String> MSGS = new ArrayList<>();
+ static {
+ ICOS.put("Tank", Ico.CHESTPLATE);
+ ICOS.put("Mage", Ico.B_ROD);
+ ICOS.put("Berserk", Ico.DIASWORD);
+ ICOS.put("Archer", Ico.BOW);
+ ICOS.put("Healer", Ico.POTION);
+
+ MSGS.add("PRESS A TO JOIN");
+ MSGS.add("Invite a friend!");
+ MSGS.add("But nobody came.");
+ MSGS.add("More is better!");
+ }
+
+ private final int player;
+
+ // title needs to be changeable here
+ public DungeonPlayerWidget(int player) {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ this.player = player;
+ }
+
+ @Override
+ public void updateContent() {
+ int start = 1 + (player - 1) * 4;
+
+ if (PlayerListMgr.strAt(start) == null) {
+ int idx = player - 2;
+ IcoTextComponent noplayer = new IcoTextComponent(Ico.SIGN,
+ Text.literal(MSGS.get(idx)).formatted(Formatting.GRAY));
+ this.addComponent(noplayer);
+ return;
+ }
+ Matcher m = PlayerListMgr.regexAt(start, PLAYER_PATTERN);
+ if (m == null) {
+ this.addComponent(new IcoTextComponent());
+ this.addComponent(new IcoTextComponent());
+ } else {
+
+ Text name = Text.literal("Name: ").append(Text.literal(m.group("name")).formatted(Formatting.YELLOW));
+ this.addComponent(new IcoTextComponent(Ico.PLAYER, name));
+
+ String cl = m.group("class");
+ String level = m.group("level");
+
+ if (level == null) {
+ PlainTextComponent ptc = new PlainTextComponent(
+ Text.literal("Player is dead").formatted(Formatting.RED));
+ this.addComponent(ptc);
+ } else {
+
+ Formatting clf = Formatting.GRAY;
+ ItemStack cli = Ico.BARRIER;
+ if (!cl.equals("EMPTY")) {
+ cli = ICOS.get(cl);
+ clf = Formatting.LIGHT_PURPLE;
+ cl += " " + m.group("level");
+ }
+
+ Text clazz = Text.literal("Class: ").append(Text.literal(cl).formatted(clf));
+ IcoTextComponent itclass = new IcoTextComponent(cli, clazz);
+ this.addComponent(itclass);
+ }
+ }
+
+ this.addSimpleIcoText(Ico.CLOCK, "Ult Cooldown:", Formatting.GOLD, start + 1);
+ this.addSimpleIcoText(Ico.POTION, "Revives:", Formatting.DARK_PURPLE, start + 2);
+
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPuzzleWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPuzzleWidget.java
new file mode 100644
index 00000000..1b3b8644
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPuzzleWidget.java
@@ -0,0 +1,57 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about all puzzeles in the dungeon (name and status)
+
+public class DungeonPuzzleWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Puzzles").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ // match a puzzle entry
+ // group 1: name
+ // group 2: status
+ // " ?.*" to diescard the solver's name if present
+ // the teleport maze has a trailing whitespace that messes with the regex
+ private static final Pattern PUZZLE_PATTERN = Pattern.compile("(?<name>.*): \\[(?<status>.*)\\] ?.*");
+
+ public DungeonPuzzleWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ int pos = 48;
+
+ while (pos < 60) {
+ Matcher m = PlayerListMgr.regexAt(pos, PUZZLE_PATTERN);
+ if (m == null) {
+ break;
+ }
+ Text t = Text.literal(m.group("name") + ": ")
+ .append(Text.literal("[").formatted(Formatting.GRAY))
+ .append(m.group("status"))
+ .append(Text.literal("]").formatted(Formatting.GRAY));
+ IcoTextComponent itc = new IcoTextComponent(Ico.SIGN, t);
+ this.addComponent(itc);
+ pos++;
+ // code points for puzzle status chars unsolved and solved: 10022, 10004
+ // not sure which one is which
+ // still need to find out codepoint for the puzzle failed char
+ }
+ if (pos == 48) {
+ this.addComponent(
+ new IcoTextComponent(Ico.BARRIER, Text.literal("No puzzles!").formatted(Formatting.GRAY)));
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java
new file mode 100644
index 00000000..6f40f5a8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java
@@ -0,0 +1,26 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about the secrets of the dungeon
+
+public class DungeonSecretWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Discoveries").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ public DungeonSecretWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.CHEST, "Secrets:", Formatting.YELLOW, 31);
+ this.addSimpleIcoText(Ico.SKULL, "Crypts:", Formatting.YELLOW, 32);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonServerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonServerWidget.java
new file mode 100644
index 00000000..569987e8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonServerWidget.java
@@ -0,0 +1,48 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows broad info about the current dungeon
+// opened/completed rooms, % of secrets found and time taken
+
+public class DungeonServerWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Dungeon Info").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ // match the secrets text
+ // group 1: % of secrets found (without "%")
+ private static final Pattern SECRET_PATTERN = Pattern.compile("Secrets Found: (?<secnum>.*)%");
+
+ public DungeonServerWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.NTAG, "Name:", Formatting.AQUA, 41);
+ this.addSimpleIcoText(Ico.SIGN, "Rooms Visited:", Formatting.DARK_PURPLE, 42);
+ this.addSimpleIcoText(Ico.SIGN, "Rooms Completed:", Formatting.LIGHT_PURPLE, 43);
+
+ Matcher m = PlayerListMgr.regexAt(44, SECRET_PATTERN);
+ if (m == null) {
+ this.addComponent(new ProgressComponent());
+ } else {
+ ProgressComponent scp = new ProgressComponent(Ico.CHEST, Text.of("Secrets found:"),
+ Float.parseFloat(m.group("secnum")),
+ Formatting.DARK_PURPLE.getColorValue());
+ this.addComponent(scp);
+ }
+
+ this.addSimpleIcoText(Ico.CLOCK, "Time:", Formatting.GOLD, 45);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EffectWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EffectWidget.java
new file mode 100644
index 00000000..5ec3faf1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EffectWidget.java
@@ -0,0 +1,67 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoFatTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widgte shows, how many active effects you have.
+// it also shows one of those in detail.
+// the parsing is super suspect and should be replaced by some regexes sometime later
+
+public class EffectWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Effect Info").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ public EffectWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+
+ String footertext = PlayerListMgr.getFooter();
+
+ if (footertext == null || !footertext.contains("Active Effects")) {
+ this.addComponent(new IcoTextComponent());
+ return;
+
+ }
+
+ String[] lines = footertext.split("Active Effects")[1].split("\n");
+ if (lines.length < 2) {
+ this.addComponent(new IcoTextComponent());
+ return;
+ }
+
+ if (lines[1].startsWith("No")) {
+ Text txt = Text.literal("No effects active").formatted(Formatting.GRAY);
+ this.addComponent(new IcoTextComponent(Ico.POTION, txt));
+ } else if (lines[1].contains("God")) {
+ String timeleft = lines[1].split("! ")[1];
+ Text godpot = Text.literal("God potion!").formatted(Formatting.RED);
+ Text txttleft = Text.literal(timeleft).formatted(Formatting.LIGHT_PURPLE);
+ IcoFatTextComponent iftc = new IcoFatTextComponent(Ico.POTION, godpot, txttleft);
+ this.addComponent(iftc);
+ } else {
+ String number = lines[1].substring("You have ".length());
+ int idx = number.indexOf(' ');
+ if (idx == -1 || lines.length < 4) {
+ this.addComponent(new IcoFatTextComponent());
+ return;
+ }
+ number = number.substring(0, idx);
+ Text active = Text.literal("Active Effects: ")
+ .append(Text.literal(number).formatted(Formatting.YELLOW));
+
+ IcoFatTextComponent iftc = new IcoFatTextComponent(Ico.POTION, active,
+ Text.literal(lines[3]).formatted(Formatting.AQUA));
+ this.addComponent(iftc);
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java
new file mode 100644
index 00000000..ec935faf
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java
@@ -0,0 +1,104 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows the status or results of the current election
+
+public class ElectionWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Election Info").formatted(Formatting.YELLOW,
+ Formatting.BOLD);
+
+ private static final HashMap<String, ItemStack> MAYOR_DATA = new HashMap<>();
+
+ private static final Text EL_OVER = Text.literal("Election ")
+ .append(Text.literal("over!").formatted(Formatting.RED));
+
+ // pattern matching a candidate while people are voting
+ // group 1: name
+ // group 2: % of votes
+ private static final Pattern VOTE_PATTERN = Pattern.compile("(?<mayor>\\S*): \\|+ \\((?<pcnt>\\d*)%\\)");
+
+ static {
+ MAYOR_DATA.put("Aatrox", Ico.DIASWORD);
+ MAYOR_DATA.put("Cole", Ico.PICKAXE);
+ MAYOR_DATA.put("Diana", Ico.BONE);
+ MAYOR_DATA.put("Diaz", Ico.GOLD);
+ MAYOR_DATA.put("Finnegan", Ico.HOE);
+ MAYOR_DATA.put("Foxy", Ico.SUGAR);
+ MAYOR_DATA.put("Paul", Ico.COMPASS);
+ MAYOR_DATA.put("Scorpius", Ico.MOREGOLD);
+ MAYOR_DATA.put("Jerry", Ico.VILLAGER);
+ MAYOR_DATA.put("Derpy", Ico.DBUSH);
+ MAYOR_DATA.put("Marina", Ico.FISH_ROD);
+ }
+
+ private static final Formatting[] COLS = { Formatting.GOLD, Formatting.RED, Formatting.LIGHT_PURPLE };
+
+ public ElectionWidget() {
+ super(TITLE, Formatting.YELLOW.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ String status = PlayerListMgr.strAt(76);
+ if (status == null) {
+ this.addComponent(new IcoTextComponent());
+ this.addComponent(new IcoTextComponent());
+ this.addComponent(new IcoTextComponent());
+ this.addComponent(new IcoTextComponent());
+ return;
+ }
+
+ if (status.contains("Over!")) {
+ // election is over
+ IcoTextComponent over = new IcoTextComponent(Ico.BARRIER, EL_OVER);
+ this.addComponent(over);
+
+ String win = PlayerListMgr.strAt(77);
+ if (win == null || !win.contains(": ")) {
+ this.addComponent(new IcoTextComponent());
+ } else {
+ String winnername = win.split(": ")[1];
+ Text winnertext = Widget.simpleEntryText(winnername, "Winner: ", Formatting.GREEN);
+ IcoTextComponent winner = new IcoTextComponent(MAYOR_DATA.get(winnername), winnertext);
+ this.addComponent(winner);
+ }
+
+ this.addSimpleIcoText(Ico.PLAYER, "Participants:", Formatting.AQUA, 78);
+ this.addSimpleIcoText(Ico.SIGN, "Year:", Formatting.LIGHT_PURPLE, 79);
+
+ } else {
+ // election is going on
+ this.addSimpleIcoText(Ico.CLOCK, "End in:", Formatting.GOLD, 76);
+
+ for (int i = 77; i <= 79; i++) {
+ Matcher m = PlayerListMgr.regexAt(i, VOTE_PATTERN);
+ if (m == null) {
+ this.addComponent(new ProgressComponent());
+ } else {
+
+ String mayorname = m.group("mayor");
+ String pcntstr = m.group("pcnt");
+ float pcnt = Float.parseFloat(pcntstr);
+ Text candidate = Text.literal(mayorname).formatted(COLS[i - 77]);
+ ProgressComponent pc = new ProgressComponent(MAYOR_DATA.get(mayorname), candidate, pcnt,
+ COLS[i - 77].getColorValue());
+ this.addComponent(pc);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ErrorWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ErrorWidget.java
new file mode 100644
index 00000000..85019dbf
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ErrorWidget.java
@@ -0,0 +1,32 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// empty widget for when nothing can be shown
+
+public class ErrorWidget extends Widget {
+ private static final MutableText TITLE = Text.literal("Error").formatted(Formatting.RED,
+ Formatting.BOLD);
+
+ Text error = Text.of("No info available!");
+
+ public ErrorWidget() {
+ super(TITLE, Formatting.RED.getColorValue());
+ }
+
+ public ErrorWidget(String error) {
+ super(TITLE, Formatting.RED.getColorValue());
+ this.error = Text.of(error);
+ }
+
+ @Override
+ public void updateContent() {
+ PlainTextComponent inf = new PlainTextComponent(this.error);
+ this.addComponent(inf);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EssenceWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EssenceWidget.java
new file mode 100644
index 00000000..d171b753
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EssenceWidget.java
@@ -0,0 +1,47 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.TableComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows your dungeon essences (dungeon hub only)
+
+public class EssenceWidget extends Widget {
+
+ private Text undead, wither, diamond, gold, dragon, spider, ice, crimson;
+
+ private static final MutableText TITLE = Text.literal("Essences").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public EssenceWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ wither = Widget.simpleEntryText(46, "Wither:", Formatting.DARK_PURPLE);
+ spider = Widget.simpleEntryText(47, "Spider:", Formatting.DARK_PURPLE);
+ undead = Widget.simpleEntryText(48, "Undead:", Formatting.DARK_PURPLE);
+ dragon = Widget.simpleEntryText(49, "Dragon:", Formatting.DARK_PURPLE);
+ gold = Widget.simpleEntryText(50, "Gold:", Formatting.DARK_PURPLE);
+ diamond = Widget.simpleEntryText(51, "Diamond:", Formatting.DARK_PURPLE);
+ ice = Widget.simpleEntryText(52, "Ice:", Formatting.DARK_PURPLE);
+ crimson = Widget.simpleEntryText(53, "Crimson:", Formatting.DARK_PURPLE);
+
+ TableComponent tc = new TableComponent(2, 4, Formatting.DARK_AQUA.getColorValue());
+
+ tc.addToCell(0, 0, new IcoTextComponent(Ico.WITHER, wither));
+ tc.addToCell(0, 1, new IcoTextComponent(Ico.STRING, spider));
+ tc.addToCell(0, 2, new IcoTextComponent(Ico.FLESH, undead));
+ tc.addToCell(0, 3, new IcoTextComponent(Ico.DRAGON, dragon));
+ tc.addToCell(1, 0, new IcoTextComponent(Ico.GOLD, gold));
+ tc.addToCell(1, 1, new IcoTextComponent(Ico.DIAMOND, diamond));
+ tc.addToCell(1, 2, new IcoTextComponent(Ico.ICE, ice));
+ tc.addToCell(1, 3, new IcoTextComponent(Ico.REDSTONE, crimson));
+ this.addComponent(tc);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EventWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EventWidget.java
new file mode 100644
index 00000000..5a1e4239
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EventWidget.java
@@ -0,0 +1,35 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about ongoing events (e.g. election)
+
+public class EventWidget extends Widget {
+ private static final MutableText TITLE = Text.literal("Event Info").formatted(Formatting.YELLOW, Formatting.BOLD);
+
+ private final boolean isInGarden;
+
+ public EventWidget(boolean isInGarden) {
+ super(TITLE, Formatting.YELLOW.getColorValue());
+ this.isInGarden = isInGarden;
+ }
+
+ @Override
+ public void updateContent() {
+ // hypixel devs carefully inserting the most random edge cases #317:
+ // the event info is placed a bit differently when in the garden.
+ int offset = (isInGarden) ? -1 : 0;
+
+ this.addSimpleIcoText(Ico.NTAG, "Name:", Formatting.YELLOW, 73 + offset);
+
+ // this could look better
+ Text time = Widget.plainEntryText(74 + offset);
+ IcoTextComponent t = new IcoTextComponent(Ico.CLOCK, time);
+ this.addComponent(t);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java
new file mode 100644
index 00000000..0211cbd6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java
@@ -0,0 +1,68 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.MathHelper;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about fire sales when in the hub.
+// or not, if there isn't one going on
+
+public class FireSaleWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Fire Sale").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ // matches a fire sale item
+ // group 1: item name
+ // group 2: # items available
+ // group 3: # items available in total (1 digit + "k")
+ private static final Pattern FIRE_PATTERN = Pattern.compile("(?<item>.*): (?<avail>\\d*)/(?<total>[0-9.]*)k");
+
+ public FireSaleWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ String event = PlayerListMgr.strAt(46);
+
+ if (event == null) {
+ this.addComponent(new PlainTextComponent(Text.literal("No Fire Sale!").formatted(Formatting.GRAY)));
+ return;
+ }
+
+ if (event.contains("Starts In")) {
+ this.addSimpleIcoText(Ico.CLOCK, "Starts in:", Formatting.DARK_AQUA, 46);
+ return;
+ }
+
+ for (int i = 46;; i++) {
+ Matcher m = PlayerListMgr.regexAt( i, FIRE_PATTERN);
+ if (m == null) {
+ break;
+ }
+ String avail = m.group("avail");
+ Text itemTxt = Text.literal(m.group("item"));
+ float total = Float.parseFloat(m.group("total")) * 1000;
+ Text prgressTxt = Text.literal(String.format("%s/%.0f", avail, total));
+ float pcnt = (Float.parseFloat(avail) / (total)) * 100f;
+ ProgressComponent pc = new ProgressComponent(Ico.GOLD, itemTxt, prgressTxt, pcnt, pcntToCol(pcnt));
+ this.addComponent(pc);
+ }
+
+ }
+
+ private int pcntToCol(float pcnt) {
+ return MathHelper.hsvToRgb( pcnt / 300f, 0.9f, 0.9f);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ForgeWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ForgeWidget.java
new file mode 100644
index 00000000..1a4683f5
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ForgeWidget.java
@@ -0,0 +1,81 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.Component;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoFatTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows what you're forging right now.
+// for locked slots, the unlock requirement is shown
+
+public class ForgeWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Forge Status").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public ForgeWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ int forgestart = 54;
+ // why is it forges and not looms >:(
+ String pos = PlayerListMgr.strAt(53);
+ if (pos == null) {
+ this.addComponent(new IcoTextComponent());
+ return;
+ }
+
+ if (!pos.startsWith("Forges")) {
+ forgestart += 2;
+ }
+
+ for (int i = forgestart, slot = 1; i < forgestart + 5 && i < 60; i++, slot++) {
+ String fstr = PlayerListMgr.strAt(i);
+ if (fstr == null || fstr.length() < 3) {
+ if (i == forgestart) {
+ this.addComponent(new IcoTextComponent());
+ }
+ break;
+ }
+ Component c;
+ Text l1, l2;
+
+ switch (fstr.substring(3)) {
+ case "LOCKED" -> {
+ l1 = Text.literal("Locked").formatted(Formatting.RED);
+ l2 = switch (slot) {
+ case 3 -> Text.literal("Needs HotM 3").formatted(Formatting.GRAY);
+ case 4 -> Text.literal("Needs HotM 4").formatted(Formatting.GRAY);
+ case 5 -> Text.literal("Needs PotM 2").formatted(Formatting.GRAY);
+ default ->
+ Text.literal("This message should not appear").formatted(Formatting.RED, Formatting.BOLD);
+ };
+ c = new IcoFatTextComponent(Ico.BARRIER, l1, l2);
+ }
+ case "EMPTY" -> {
+ l1 = Text.literal("Empty").formatted(Formatting.GRAY);
+ c = new IcoTextComponent(Ico.FURNACE, l1);
+ }
+ default -> {
+ String[] parts = fstr.split(": ");
+ if (parts.length != 2) {
+ c = new IcoFatTextComponent();
+ } else {
+ l1 = Text.literal(parts[0].substring(3)).formatted(Formatting.YELLOW);
+ l2 = Text.literal("Done in: ").formatted(Formatting.GRAY).append(Text.literal(parts[1]).formatted(Formatting.WHITE));
+ c = new IcoFatTextComponent(Ico.FIRE, l1, l2);
+ }
+ }
+ }
+ this.addComponent(c);
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenServerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenServerWidget.java
new file mode 100644
index 00000000..221f8b08
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenServerWidget.java
@@ -0,0 +1,54 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about the garden server
+
+public class GardenServerWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Server Info").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ // match the next visitor in the garden
+ // group 1: visitor name
+ private static final Pattern VISITOR_PATTERN = Pattern.compile("Next Visitor: (?<vis>.*)");
+
+ public GardenServerWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.MAP, "Area:", Formatting.DARK_AQUA, 41);
+ this.addSimpleIcoText(Ico.NTAG, "Server ID:", Formatting.GRAY, 42);
+ this.addSimpleIcoText(Ico.EMERALD, "Gems:", Formatting.GREEN, 43);
+ this.addSimpleIcoText(Ico.COPPER, "Copper:", Formatting.GOLD, 44);
+
+ Matcher m = PlayerListMgr.regexAt(45, VISITOR_PATTERN);
+ if (m == null ) {
+ this.addComponent(new IcoTextComponent());
+ return;
+ }
+
+ String vis = m.group("vis");
+ Formatting col;
+ if (vis.equals("Not Unlocked!")) {
+ col = Formatting.RED;
+ } else {
+ col = Formatting.GREEN;
+ }
+ Text visitor = Widget.simpleEntryText(vis, "Next Visitor: ", col);
+ IcoTextComponent v = new IcoTextComponent(Ico.PLAYER, visitor);
+ this.addComponent(v);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java
new file mode 100644
index 00000000..e7058fd6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java
@@ -0,0 +1,80 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.TableComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about your skills while in the garden
+
+public class GardenSkillsWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Skill Info").formatted(Formatting.YELLOW,
+ Formatting.BOLD);
+
+ // match the skill entry
+ // group 1: skill name and level
+ // group 2: progress to next level (without "%")
+ private static final Pattern SKILL_PATTERN = Pattern
+ .compile("\\S*: (?<skill>[A-Za-z]* [0-9]*): (?<progress>\\S*)%");
+ // same, but with leading space
+ private static final Pattern MS_PATTERN = Pattern.compile("\\S*: (?<skill>[A-Za-z]* [0-9]*): (?<progress>\\S*)%");
+
+ public GardenSkillsWidget() {
+ super(TITLE, Formatting.YELLOW.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ ProgressComponent pc;
+ Matcher m = PlayerListMgr.regexAt(66, SKILL_PATTERN);
+ if (m == null) {
+ pc = new ProgressComponent();
+ } else {
+
+ String strpcnt = m.group("progress");
+ String skill = m.group("skill");
+
+ float pcnt = Float.parseFloat(strpcnt);
+ pc = new ProgressComponent(Ico.LANTERN, Text.of(skill), pcnt,
+ Formatting.GOLD.getColorValue());
+ }
+
+ this.addComponent(pc);
+
+ Text speed = Widget.simpleEntryText(67, "SPD", Formatting.WHITE);
+ IcoTextComponent spd = new IcoTextComponent(Ico.SUGAR, speed);
+ Text farmfort = Widget.simpleEntryText(68, "FFO", Formatting.GOLD);
+ IcoTextComponent ffo = new IcoTextComponent(Ico.HOE, farmfort);
+
+ TableComponent tc = new TableComponent(2, 1, Formatting.YELLOW.getColorValue());
+ tc.addToCell(0, 0, spd);
+ tc.addToCell(1, 0, ffo);
+ this.addComponent(tc);
+
+ ProgressComponent pc2;
+ m = PlayerListMgr.regexAt(69, MS_PATTERN);
+ if (m == null) {
+ pc2 = new ProgressComponent();
+ } else {
+ String strpcnt = m.group("progress");
+ String skill = m.group("skill");
+
+ float pcnt = Float.parseFloat(strpcnt);
+ pc2 = new ProgressComponent(Ico.MILESTONE, Text.of(skill), pcnt,
+ Formatting.GREEN.getColorValue());
+
+ }
+ this.addComponent(pc2);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenVisitorsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenVisitorsWidget.java
new file mode 100644
index 00000000..cfbd6cd0
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenVisitorsWidget.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+public class GardenVisitorsWidget extends Widget {
+ private static final MutableText TITLE = Text.literal("Visitors").formatted(Formatting.DARK_GREEN, Formatting.BOLD);
+
+ public GardenVisitorsWidget() {
+ super(TITLE, Formatting.DARK_GREEN.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ if (PlayerListMgr.textAt(54) == null) {
+ this.addComponent(new PlainTextComponent(Text.literal("No visitors!").formatted(Formatting.GRAY)));
+ return;
+ }
+
+ for (int i = 54; i < 59; i++) {
+ String text = PlayerListMgr.strAt(i);
+ if (text != null)
+ this.addComponent(new PlainTextComponent(Text.literal(text)));
+ }
+
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GuestServerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GuestServerWidget.java
new file mode 100644
index 00000000..bbd97fb5
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GuestServerWidget.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about the private island you're visiting
+
+public class GuestServerWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Island Info").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public GuestServerWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.MAP, "Area:", Formatting.DARK_AQUA, 41);
+ this.addSimpleIcoText(Ico.NTAG, "Server ID:", Formatting.GRAY, 42);
+ this.addSimpleIcoText(Ico.SIGN, "Owner:", Formatting.GREEN, 43);
+ this.addSimpleIcoText(Ico.SIGN, "Status:", Formatting.BLUE, 44);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandGuestsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandGuestsWidget.java
new file mode 100644
index 00000000..b527dc78
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandGuestsWidget.java
@@ -0,0 +1,47 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows a list of all people visiting the same private island as you
+
+public class IslandGuestsWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Guests").formatted(Formatting.AQUA,
+ Formatting.BOLD);
+
+ // matches a player entry, removing their level and the hand icon
+ // group 1: player name
+ private static final Pattern GUEST_PATTERN = Pattern.compile("\\[\\d*\\] (.*) \\[.\\]");
+
+ public IslandGuestsWidget() {
+ super(TITLE, Formatting.AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ for (int i = 21; i < 40; i++) {
+ String str = PlayerListMgr.strAt(i);
+ if (str == null) {
+ if (i == 21) {
+ this.addComponent(new PlainTextComponent(Text.literal("No Visitors!").formatted(Formatting.GRAY)));
+ }
+ break;
+ }
+ Matcher m = PlayerListMgr.regexAt( i, GUEST_PATTERN);
+ if (m == null) {
+ this.addComponent(new PlainTextComponent(Text.of("???")));
+ } else {
+ this.addComponent(new PlainTextComponent(Text.of(m.group(1))));
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandOwnersWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandOwnersWidget.java
new file mode 100644
index 00000000..cde1fa38
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandOwnersWidget.java
@@ -0,0 +1,66 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows a list of the owners of a home island while guesting
+
+public class IslandOwnersWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Owners").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ // matches an owner
+ // group 1: player name
+ // group 2: last seen, if owner not online
+ // ^(?<nameA>.*) \((?<lastseen>.*)\)$|^\[\d*\] (?:\[[A-Za-z]+\] )?(?<nameB>[A-Za-z0-9_]*)(?: .*)?$|^(?<nameC>.*)$
+ private static final Pattern OWNER_PATTERN = Pattern
+ .compile("^(?<nameA>.*) \\((?<lastseen>.*)\\)$|^\\[\\d*\\] (?:\\[[A-Za-z]+\\] )?(?<nameB>[A-Za-z0-9_]*)(?: .*)?$|^(?<nameC>.*)$");
+
+ public IslandOwnersWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+
+ for (int i = 1; i < 20; i++) {
+ Matcher m = PlayerListMgr.regexAt(i, OWNER_PATTERN);
+ if (m == null) {
+ break;
+ }
+
+ String name, lastseen;
+ Formatting format;
+ if (m.group("nameA") != null) {
+ name = m.group("nameA");
+ lastseen = m.group("lastseen");
+ format = Formatting.GRAY;
+ } else if (m.group("nameB")!=null){
+ name = m.group("nameB");
+ lastseen = "Online";
+ format = Formatting.WHITE;
+ } else {
+ name = m.group("nameC");
+ lastseen = "Online";
+ format = Formatting.WHITE;
+ }
+
+ Text entry = Text.literal(name)
+ .append(
+ Text.literal(" (" + lastseen + ")")
+ .formatted(format));
+ PlainTextComponent ptc = new PlainTextComponent(entry);
+ this.addComponent(ptc);
+ }
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandSelfWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandSelfWidget.java
new file mode 100644
index 00000000..31ad66f7
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandSelfWidget.java
@@ -0,0 +1,43 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows a list of the owners while on your home island
+
+public class IslandSelfWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Owners").formatted(Formatting.DARK_PURPLE,
+ Formatting.BOLD);
+
+ // matches an owner
+ // group 1: player name, optionally offline time
+ // ^\[\d*\] (?:\[[A-Za-z]+\] )?([A-Za-z0-9_() ]*)(?: .*)?$|^(.*)$
+ private static final Pattern OWNER_PATTERN = Pattern
+ .compile("^\\[\\d*\\] (?:\\[[A-Za-z]+\\] )?([A-Za-z0-9_() ]*)(?: .*)?$|^(.*)$");
+
+ public IslandSelfWidget() {
+ super(TITLE, Formatting.DARK_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ for (int i = 1; i < 20; i++) {
+ Matcher m = PlayerListMgr.regexAt(i, OWNER_PATTERN);
+ if (m == null) {
+ break;
+ }
+
+ Text entry = (m.group(1) != null) ? Text.of(m.group(1)) : Text.of(m.group(2));
+ this.addComponent(new PlainTextComponent(entry));
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandServerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandServerWidget.java
new file mode 100644
index 00000000..53dc11a6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/IslandServerWidget.java
@@ -0,0 +1,32 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about your home island
+
+public class IslandServerWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Island Info").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public IslandServerWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.MAP, "Area:", Formatting.DARK_AQUA, 41);
+ this.addSimpleIcoText(Ico.NTAG, "Server ID:", Formatting.GRAY, 42);
+ this.addSimpleIcoText(Ico.EMERALD, "Crystals:", Formatting.DARK_PURPLE, 43);
+ this.addSimpleIcoText(Ico.CHEST, "Stash:", Formatting.GREEN, 44);
+ this.addSimpleIcoText(Ico.COMMAND, "Minions:", Formatting.BLUE, 45);
+
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java
new file mode 100644
index 00000000..5ae0bd3d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java
@@ -0,0 +1,62 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.HashMap;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.TableComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about the current jacob's contest (garden only)
+
+public class JacobsContestWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Jacob's Contest").formatted(Formatting.YELLOW,
+ Formatting.BOLD);
+
+ private static final HashMap<String, ItemStack> FARM_DATA = new HashMap<>();
+
+ // again, there HAS to be a better way to do this
+ static {
+ FARM_DATA.put("Wheat", new ItemStack(Items.WHEAT));
+ FARM_DATA.put("Sugar Cane", new ItemStack(Items.SUGAR_CANE));
+ FARM_DATA.put("Carrot", new ItemStack(Items.CARROT));
+ FARM_DATA.put("Potato", new ItemStack(Items.POTATO));
+ FARM_DATA.put("Melon", new ItemStack(Items.MELON_SLICE));
+ FARM_DATA.put("Pumpkin", new ItemStack(Items.PUMPKIN));
+ FARM_DATA.put("Cocoa Beans", new ItemStack(Items.COCOA_BEANS));
+ FARM_DATA.put("Nether Wart", new ItemStack(Items.NETHER_WART));
+ FARM_DATA.put("Cactus", new ItemStack(Items.CACTUS));
+ FARM_DATA.put("Mushroom", new ItemStack(Items.RED_MUSHROOM));
+ }
+
+ public JacobsContestWidget() {
+ super(TITLE, Formatting.YELLOW.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.CLOCK, "Starts in:", Formatting.GOLD, 76);
+
+ TableComponent tc = new TableComponent(1, 3, Formatting.YELLOW .getColorValue());
+
+ for (int i = 77; i < 80; i++) {
+ String item = PlayerListMgr.strAt(i);
+ IcoTextComponent itc;
+ if (item == null) {
+ itc = new IcoTextComponent();
+ } else {
+ itc = new IcoTextComponent(FARM_DATA.get(item), Text.of(item));
+ }
+ tc.addToCell(0, i - 77, itc);
+ }
+ this.addComponent(tc);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/MinionWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/MinionWidget.java
new file mode 100644
index 00000000..35b9a0c6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/MinionWidget.java
@@ -0,0 +1,151 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about minions placed on the home island
+
+public class MinionWidget extends Widget {
+ private static final MutableText TITLE = Text.literal("Minions").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ private static final HashMap<String, ItemStack> MIN_ICOS = new HashMap<>();
+
+ // hmm...
+ static {
+ MIN_ICOS.put("Blaze", new ItemStack(Items.BLAZE_ROD));
+ MIN_ICOS.put("Cave Spider", new ItemStack(Items.SPIDER_EYE));
+ MIN_ICOS.put("Creeper", new ItemStack(Items.GUNPOWDER));
+ MIN_ICOS.put("Enderman", new ItemStack(Items.ENDER_PEARL));
+ MIN_ICOS.put("Ghast", new ItemStack(Items.GHAST_TEAR));
+ MIN_ICOS.put("Magma Cube", new ItemStack(Items.MAGMA_CREAM));
+ MIN_ICOS.put("Skeleton", new ItemStack(Items.BONE));
+ MIN_ICOS.put("Slime", new ItemStack(Items.SLIME_BALL));
+ MIN_ICOS.put("Spider", new ItemStack(Items.STRING));
+ MIN_ICOS.put("Zombie", new ItemStack(Items.ROTTEN_FLESH));
+ MIN_ICOS.put("Cactus", new ItemStack(Items.CACTUS));
+ MIN_ICOS.put("Carrot", new ItemStack(Items.CARROT));
+ MIN_ICOS.put("Chicken", new ItemStack(Items.CHICKEN));
+ MIN_ICOS.put("Cocoa Beans", new ItemStack(Items.COCOA_BEANS));
+ MIN_ICOS.put("Cow", new ItemStack(Items.BEEF));
+ MIN_ICOS.put("Melon", new ItemStack(Items.MELON_SLICE));
+ MIN_ICOS.put("Mushroom", new ItemStack(Items.RED_MUSHROOM));
+ MIN_ICOS.put("Nether Wart", new ItemStack(Items.NETHER_WART));
+ MIN_ICOS.put("Pig", new ItemStack(Items.PORKCHOP));
+ MIN_ICOS.put("Potato", new ItemStack(Items.POTATO));
+ MIN_ICOS.put("Pumpkin", new ItemStack(Items.PUMPKIN));
+ MIN_ICOS.put("Rabbit", new ItemStack(Items.RABBIT));
+ MIN_ICOS.put("Sheep", new ItemStack(Items.WHITE_WOOL));
+ MIN_ICOS.put("Sugar Cane", new ItemStack(Items.SUGAR_CANE));
+ MIN_ICOS.put("Wheat", new ItemStack(Items.WHEAT));
+ MIN_ICOS.put("Clay", new ItemStack(Items.CLAY));
+ MIN_ICOS.put("Fishing", new ItemStack(Items.FISHING_ROD));
+ MIN_ICOS.put("Coal", new ItemStack(Items.COAL));
+ MIN_ICOS.put("Cobblestone", new ItemStack(Items.COBBLESTONE));
+ MIN_ICOS.put("Diamond", new ItemStack(Items.DIAMOND));
+ MIN_ICOS.put("Emerald", new ItemStack(Items.EMERALD));
+ MIN_ICOS.put("End Stone", new ItemStack(Items.END_STONE));
+ MIN_ICOS.put("Glowstone", new ItemStack(Items.GLOWSTONE_DUST));
+ MIN_ICOS.put("Gold", new ItemStack(Items.GOLD_INGOT));
+ MIN_ICOS.put("Gravel", new ItemStack(Items.GRAVEL));
+ MIN_ICOS.put("Hard Stone", new ItemStack(Items.STONE));
+ MIN_ICOS.put("Ice", new ItemStack(Items.ICE));
+ MIN_ICOS.put("Iron", new ItemStack(Items.IRON_INGOT));
+ MIN_ICOS.put("Lapis", new ItemStack(Items.LAPIS_LAZULI));
+ MIN_ICOS.put("Mithril", new ItemStack(Items.PRISMARINE_CRYSTALS));
+ MIN_ICOS.put("Mycelium", new ItemStack(Items.MYCELIUM));
+ MIN_ICOS.put("Obsidian", new ItemStack(Items.OBSIDIAN));
+ MIN_ICOS.put("Quartz", new ItemStack(Items.QUARTZ));
+ MIN_ICOS.put("Red Sand", new ItemStack(Items.RED_SAND));
+ MIN_ICOS.put("Redstone", new ItemStack(Items.REDSTONE));
+ MIN_ICOS.put("Sand", new ItemStack(Items.SAND));
+ MIN_ICOS.put("Snow", new ItemStack(Items.SNOWBALL));
+ MIN_ICOS.put("Inferno", new ItemStack(Items.BLAZE_SPAWN_EGG));
+ MIN_ICOS.put("Revenant", new ItemStack(Items.ZOMBIE_SPAWN_EGG));
+ MIN_ICOS.put("Tarantula", new ItemStack(Items.SPIDER_SPAWN_EGG));
+ MIN_ICOS.put("Vampire", new ItemStack(Items.REDSTONE));
+ MIN_ICOS.put("Voidling", new ItemStack(Items.ENDERMAN_SPAWN_EGG));
+ MIN_ICOS.put("Acacia", new ItemStack(Items.ACACIA_LOG));
+ MIN_ICOS.put("Birch", new ItemStack(Items.BIRCH_LOG));
+ MIN_ICOS.put("Dark Oak", new ItemStack(Items.DARK_OAK_LOG));
+ MIN_ICOS.put("Flower", new ItemStack(Items.POPPY));
+ MIN_ICOS.put("Jungle", new ItemStack(Items.JUNGLE_LOG));
+ MIN_ICOS.put("Oak", new ItemStack(Items.OAK_LOG));
+ MIN_ICOS.put("Spruce", new ItemStack(Items.SPRUCE_LOG));
+ }
+
+ // matches a minion entry
+ // group 1: name
+ // group 2: level
+ // group 3: status
+ public static final Pattern MINION_PATTERN = Pattern.compile("(?<name>.*) (?<level>[XVI]*) \\[(?<status>.*)\\]");
+
+ public MinionWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+
+ // this looks a bit weird because if we used regex mismatch as a stop condition,
+ // it'd spam the chat.
+ // not sure if not having that debug output is worth the cleaner solution here...
+
+ for (int i = 48; i < 59; i++) {
+ if (!this.addMinionComponent(i)) {
+ break;
+ }
+ }
+
+ // if more minions are placed than the tab menu can display,
+ // a "And X more..." text is shown
+ // look for that and add it to the widget
+ String more = PlayerListMgr.strAt(59);
+ if (more == null) {
+ return;
+ } else if (more.startsWith("And ")) {
+ this.addComponent(new PlainTextComponent(Text.of(more)));
+ } else {
+ this.addMinionComponent(59);
+ }
+ }
+
+ public boolean addMinionComponent(int i) {
+ Matcher m = PlayerListMgr.regexAt(i, MINION_PATTERN);
+ if (m != null) {
+
+ String min = m.group("name");
+ String lvl = m.group("level");
+ String stat = m.group("status");
+
+ MutableText mt = Text.literal(min + " " + lvl).append(Text.literal(": "));
+
+ Formatting format = Formatting.RED;
+ if (stat.equals("ACTIVE")) {
+ format = Formatting.GREEN;
+ } else if (stat.equals("SLOW")) {
+ format = Formatting.YELLOW;
+ }
+ // makes "BLOCKED" also red. in reality, it's some kind of crimson
+ mt.append(Text.literal(stat).formatted(format));
+
+ IcoTextComponent itc = new IcoTextComponent(MIN_ICOS.get(min), mt);
+ this.addComponent(itc);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ParkServerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ParkServerWidget.java
new file mode 100644
index 00000000..c781a1bc
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ParkServerWidget.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about the park server
+
+public class ParkServerWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Server Info").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public ParkServerWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.MAP, "Area:", Formatting.DARK_AQUA, 41);
+ this.addSimpleIcoText(Ico.NTAG, "Server ID:", Formatting.GRAY, 42);
+ this.addSimpleIcoText(Ico.EMERALD, "Gems:", Formatting.GREEN, 43);
+ this.addSimpleIcoText(Ico.WATER, "Rain:", Formatting.BLUE, 44);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java
new file mode 100644
index 00000000..ba178a5e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java
@@ -0,0 +1,71 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlayerComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.TableComponent;
+import net.minecraft.client.network.PlayerListEntry;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+
+// this widget shows a list of players with their skins.
+// responsible for non-private-island areas
+
+public class PlayerListWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Players").formatted(Formatting.GREEN,
+ Formatting.BOLD);
+
+ public PlayerListWidget() {
+ super(TITLE, Formatting.GREEN.getColorValue());
+
+ }
+
+ @Override
+ public void updateContent() {
+ ArrayList<PlayerListEntry> list = new ArrayList<>();
+
+ // hard cap to 4x20 entries.
+ // 5x20 is too wide (and not possible in theory. in reality however...)
+ int listlen = Math.min(PlayerListMgr.getSize(), 160);
+
+ // list isn't fully loaded, so our hack won't work...
+ if (listlen < 80) {
+ this.addComponent(new PlainTextComponent(Text.literal("List loading...").formatted(Formatting.GRAY)));
+ return;
+ }
+
+ // unintuitive int ceil division stolen from
+ // https://stackoverflow.com/questions/7139382/java-rounding-up-to-an-int-using-math-ceil#21830188
+ int tblW = ((listlen - 80) - 1) / 20 + 1;
+
+ TableComponent tc = new TableComponent(tblW, Math.min(listlen - 80, 20), Formatting.GREEN.getColorValue());
+
+ for (int i = 80; i < listlen; i++) {
+ list.add(PlayerListMgr.getRaw(i));
+ }
+
+ if (SkyblockerConfigManager.get().general.tabHud.nameSorting == SkyblockerConfig.NameSorting.ALPHABETICAL) {
+ list.sort(Comparator.comparing(o -> o.getProfile().getName().toLowerCase()));
+ }
+
+ int x = 0, y = 0;
+
+ for (PlayerListEntry ple : list) {
+ tc.addToCell(x, y, new PlayerComponent(ple));
+ y++;
+ if (y >= 20) {
+ y = 0;
+ x++;
+ }
+ }
+
+ this.addComponent(tc);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java
new file mode 100644
index 00000000..44635fbe
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java
@@ -0,0 +1,29 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows how much mithril and gemstone powder you have
+// (dwarven mines and crystal hollows)
+
+public class PowderWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Powders").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public PowderWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.MITHRIL, "Mithril:", Formatting.AQUA, 46);
+ this.addSimpleIcoText(Ico.EMERALD, "Gemstone:", Formatting.DARK_PURPLE, 47);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ProfileWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ProfileWidget.java
new file mode 100644
index 00000000..de2ea0c6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ProfileWidget.java
@@ -0,0 +1,28 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about your profile and bank
+
+public class ProfileWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Profile").formatted(Formatting.YELLOW, Formatting.BOLD);
+
+ public ProfileWidget() {
+ super(TITLE, Formatting.YELLOW.getColorValue());
+
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.SIGN, "Profile:", Formatting.GREEN, 61);
+ this.addSimpleIcoText(Ico.BONE, "Pet Sitter:", Formatting.AQUA, 62);
+ this.addSimpleIcoText(Ico.EMERALD, "Balance:", Formatting.GOLD, 63);
+ this.addSimpleIcoText(Ico.CLOCK, "Interest in:", Formatting.GOLD, 64);
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/QuestWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/QuestWidget.java
new file mode 100644
index 00000000..3c3d3c92
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/QuestWidget.java
@@ -0,0 +1,33 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows your crimson isle faction quests
+
+public class QuestWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Faction Quests").formatted(Formatting.AQUA,
+ Formatting.BOLD);
+
+ public QuestWidget() {
+ super(TITLE, Formatting.AQUA.getColorValue());
+
+ }
+
+ @Override
+ public void updateContent() {
+ for (int i = 51; i < 56; i++) {
+ Text q = PlayerListMgr.textAt(i);
+ IcoTextComponent itc = new IcoTextComponent(Ico.BOOK, q);
+ this.addComponent(itc);
+ }
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java
new file mode 100644
index 00000000..3c218fb1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ReputationWidget.java
@@ -0,0 +1,69 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows your faction status (crimson isle)
+
+public class ReputationWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Faction Status").formatted(Formatting.AQUA,
+ Formatting.BOLD);
+
+ // matches your faction alignment progress
+ // group 1: percentage to next alignment level
+ private static final Pattern PROGRESS_PATTERN = Pattern.compile("\\|+ \\((?<prog>[0-9.]*)%\\)");
+
+ // matches alignment level names
+ // group 1: left level name
+ // group 2: right level name
+ private static final Pattern STATE_PATTERN = Pattern.compile("(?<from>\\S*) *(?<to>\\S*)");
+
+ public ReputationWidget() {
+ super(TITLE, Formatting.AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ String fracstr = PlayerListMgr.strAt(45);
+
+ int spaceidx;
+ IcoTextComponent faction;
+ if (fracstr == null || (spaceidx = fracstr.indexOf(' ')) == -1) {
+ faction = new IcoTextComponent();
+ } else {
+ String fname = fracstr.substring(0, spaceidx);
+ if (fname.equals("Mage")) {
+ faction = new IcoTextComponent(Ico.POTION, Text.literal(fname).formatted(Formatting.DARK_AQUA));
+ } else {
+ faction = new IcoTextComponent(Ico.SWORD, Text.literal(fname).formatted(Formatting.RED));
+ }
+ }
+ this.addComponent(faction);
+
+ Text rep = Widget.plainEntryText(46);
+ Matcher prog = PlayerListMgr.regexAt(47, PROGRESS_PATTERN);
+ Matcher state = PlayerListMgr.regexAt(48, STATE_PATTERN);
+
+ if (prog == null || state == null) {
+ this.addComponent(new ProgressComponent());
+ } else {
+ float pcnt = Float.parseFloat(prog.group("prog"));
+ Text reputationText = state.group("from").equals("Max") ? Text.literal("Max Reputation") : Text.literal(state.group("from") + " -> " + state.group("to"));
+ ProgressComponent pc = new ProgressComponent(Ico.LANTERN,
+ reputationText, rep, pcnt,
+ Formatting.AQUA.getColorValue());
+ this.addComponent(pc);
+ }
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ServerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ServerWidget.java
new file mode 100644
index 00000000..475cb038
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ServerWidget.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about "generic" servers.
+// a server is "generic", when only name, server ID and gems are shown
+// in the third column of the tab HUD
+
+public class ServerWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Server Info").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public ServerWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.MAP, "Area:", Formatting.DARK_AQUA, 41);
+ this.addSimpleIcoText(Ico.NTAG, "Server ID:", Formatting.GRAY, 42);
+ this.addSimpleIcoText(Ico.EMERALD, "Gems:", Formatting.GREEN, 43);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java
new file mode 100644
index 00000000..379fbb62
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java
@@ -0,0 +1,78 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.Component;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoFatTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.TableComponent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about a skill and some stats,
+// as seen in the rightmost column of the default HUD
+
+public class SkillsWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Skill Info").formatted(Formatting.YELLOW,
+ Formatting.BOLD);
+
+ // match the skill entry
+ // group 1: skill name and level
+ // group 2: progress to next level (without "%")
+ private static final Pattern SKILL_PATTERN = Pattern.compile("\\S*: ([A-Za-z]* [0-9]*): ([0-9.MAX]*)%?");
+
+ public SkillsWidget() {
+ super(TITLE, Formatting.YELLOW.getColorValue());
+
+ }
+
+ @Override
+ public void updateContent() {
+ Matcher m = PlayerListMgr.regexAt(66, SKILL_PATTERN);
+ Component progress;
+ if (m == null) {
+ progress = new ProgressComponent();
+ } else {
+
+ String skill = m.group(1);
+ String pcntStr = m.group(2);
+
+ if (!pcntStr.equals("MAX")) {
+ float pcnt = Float.parseFloat(pcntStr);
+ progress = new ProgressComponent(Ico.LANTERN, Text.of(skill),
+ Text.of(pcntStr + "%"), pcnt, Formatting.GOLD.getColorValue());
+ } else {
+ progress = new IcoFatTextComponent(Ico.LANTERN, Text.of(skill),
+ Text.literal(pcntStr).formatted(Formatting.RED));
+ }
+ }
+
+ this.addComponent(progress);
+
+ Text speed = Widget.simpleEntryText(67, "SPD", Formatting.WHITE);
+ IcoTextComponent spd = new IcoTextComponent(Ico.SUGAR, speed);
+ Text strength = Widget.simpleEntryText(68, "STR", Formatting.RED);
+ IcoTextComponent str = new IcoTextComponent(Ico.SWORD, strength);
+ Text critDmg = Widget.simpleEntryText(69, "CCH", Formatting.BLUE);
+ IcoTextComponent cdg = new IcoTextComponent(Ico.SWORD, critDmg);
+ Text critCh = Widget.simpleEntryText(70, "CDG", Formatting.BLUE);
+ IcoTextComponent cch = new IcoTextComponent(Ico.SWORD, critCh);
+ Text aSpeed = Widget.simpleEntryText(71, "ASP", Formatting.YELLOW);
+ IcoTextComponent asp = new IcoTextComponent(Ico.HOE, aSpeed);
+
+ TableComponent tc = new TableComponent(2, 3, Formatting.YELLOW.getColorValue());
+ tc.addToCell(0, 0, spd);
+ tc.addToCell(0, 1, str);
+ tc.addToCell(0, 2, asp);
+ tc.addToCell(1, 0, cdg);
+ tc.addToCell(1, 1, cch);
+ this.addComponent(tc);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TrapperWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TrapperWidget.java
new file mode 100644
index 00000000..74bba632
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TrapperWidget.java
@@ -0,0 +1,25 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows how meny pelts you have (farming island)
+
+public class TrapperWidget extends Widget {
+ private static final MutableText TITLE = Text.literal("Trapper").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public TrapperWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.LEATHER, "Pelts:", Formatting.AQUA, 46);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/UpgradeWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/UpgradeWidget.java
new file mode 100644
index 00000000..a245cbe9
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/UpgradeWidget.java
@@ -0,0 +1,51 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+// this widget shows info about ongoing profile/account upgrades
+// or not, if there aren't any
+// TODO: not very pretty atm
+
+public class UpgradeWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Upgrade Info").formatted(Formatting.GOLD,
+ Formatting.BOLD);
+
+ public UpgradeWidget() {
+ super(TITLE, Formatting.GOLD.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ String footertext = PlayerListMgr.getFooter();
+
+ if (footertext == null) {
+ this.addComponent(new PlainTextComponent(Text.literal("No data").formatted(Formatting.GRAY)));
+ return;
+ }
+
+ if (!footertext.contains("Upgrades")) {
+ this.addComponent(new PlainTextComponent(Text.of("Currently no upgrades...")));
+ return;
+ }
+
+ String interesting = footertext.split("Upgrades")[1];
+ String[] lines = interesting.split("\n");
+
+ for (int i = 1; i < lines.length; i++) {
+ if (lines[i].trim().length() < 3) { // empty line is §s
+ break;
+ }
+ IcoTextComponent itc = new IcoTextComponent(Ico.SIGN, Text.of(lines[i]));
+ this.addComponent(itc);
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/VolcanoWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/VolcanoWidget.java
new file mode 100644
index 00000000..8dacfb3a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/VolcanoWidget.java
@@ -0,0 +1,59 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.HashMap;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Pair;
+
+// shows the volcano status (crimson isle)
+
+public class VolcanoWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Volcano Status").formatted(Formatting.AQUA,
+ Formatting.BOLD);
+
+ private static final HashMap<String, Pair<ItemStack, Formatting>> BOOM_TYPE = new HashMap<>();
+
+ static {
+ BOOM_TYPE.put("INACTIVE",
+ new Pair<>(new ItemStack(Items.BARRIER), Formatting.DARK_GRAY));
+ BOOM_TYPE.put("CHILL",
+ new Pair<>(new ItemStack(Items.ICE), Formatting.AQUA));
+ BOOM_TYPE.put("LOW",
+ new Pair<>(new ItemStack(Items.FLINT_AND_STEEL), Formatting.GRAY));
+ BOOM_TYPE.put("DISRUPTIVE",
+ new Pair<>(new ItemStack(Items.CAMPFIRE), Formatting.WHITE));
+ BOOM_TYPE.put("MEDIUM",
+ new Pair<>(new ItemStack(Items.LAVA_BUCKET), Formatting.YELLOW));
+ BOOM_TYPE.put("HIGH",
+ new Pair<>(new ItemStack(Items.FIRE_CHARGE), Formatting.GOLD));
+ BOOM_TYPE.put("EXPLOSIVE",
+ new Pair<>(new ItemStack(Items.TNT), Formatting.RED));
+ BOOM_TYPE.put("CATACLYSMIC",
+ new Pair<>(new ItemStack(Items.SKELETON_SKULL), Formatting.DARK_RED));
+ }
+
+ public VolcanoWidget() {
+ super(TITLE, Formatting.AQUA.getColorValue());
+
+ }
+
+ @Override
+ public void updateContent() {
+ String s = PlayerListMgr.strAt(58);
+ if (s == null) {
+ this.addComponent(new IcoTextComponent());
+ } else {
+ Pair<ItemStack, Formatting> p = BOOM_TYPE.get(s);
+ this.addComponent(new IcoTextComponent(p.getLeft(), Text.literal(s).formatted(p.getRight())));
+ }
+
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/Widget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/Widget.java
new file mode 100644
index 00000000..5f0d2c3c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/Widget.java
@@ -0,0 +1,216 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget;
+
+import java.util.ArrayList;
+
+import com.mojang.blaze3d.systems.RenderSystem;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.Component;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+/**
+ * Abstract base class for a Widget.
+ * Widgets are containers for components with a border and a title.
+ * Their size is dependent on the components inside,
+ * the position may be changed after construction.
+ */
+public abstract class Widget {
+
+ private final ArrayList<Component> components = new ArrayList<>();
+ private int w = 0, h = 0;
+ private int x = 0, y = 0;
+ private final int color;
+ private final Text title;
+
+ private static final TextRenderer txtRend = MinecraftClient.getInstance().textRenderer;
+
+ static final int BORDER_SZE_N = txtRend.fontHeight + 4;
+ static final int BORDER_SZE_S = 4;
+ static final int BORDER_SZE_W = 4;
+ static final int BORDER_SZE_E = 4;
+ static final int COL_BG_BOX = 0xc00c0c0c;
+
+ public Widget(MutableText title, Integer colorValue) {
+ this.title = title;
+ this.color = 0xff000000 | colorValue;
+ }
+
+ public final void addComponent(Component c) {
+ this.components.add(c);
+ }
+
+ public final void update() {
+ this.components.clear();
+ this.updateContent();
+ this.pack();
+ }
+
+ public abstract void updateContent();
+
+ /**
+ * Shorthand function for simple components.
+ * If the entry at idx has the format "<textA>: <textB>", an IcoTextComponent is
+ * added as such:
+ * [ico] [string] [textB.formatted(fmt)]
+ */
+ public final void addSimpleIcoText(ItemStack ico, String string, Formatting fmt, int idx) {
+ Text txt = Widget.simpleEntryText(idx, string, fmt);
+ this.addComponent(new IcoTextComponent(ico, txt));
+ }
+
+ /**
+ * Calculate the size of this widget.
+ * <b>Must be called before returning from the widget constructor and after all
+ * components are added!</b>
+ */
+ private void pack() {
+ h = 0;
+ w = 0;
+ for (Component c : components) {
+ h += c.getHeight() + Component.PAD_L;
+ w = Math.max(w, c.getWidth() + Component.PAD_S);
+ }
+
+ h -= Component.PAD_L / 2; // less padding after lowest/last component
+ h += BORDER_SZE_N + BORDER_SZE_S - 2;
+ w += BORDER_SZE_E + BORDER_SZE_W;
+
+ // min width is dependent on title
+ w = Math.max(w, BORDER_SZE_W + BORDER_SZE_E + Widget.txtRend.getWidth(title) + 4 + 4 + 1);
+ }
+
+ public final void setX(int x) {
+ this.x = x;
+ }
+
+ public final int getY() {
+ return this.y;
+ }
+
+ public final int getX() {
+ return this.x;
+ }
+
+ public final void setY(int y) {
+ this.y = y;
+ }
+
+ public final int getWidth() {
+ return this.w;
+ }
+
+ public final int getHeight() {
+ return this.h;
+ }
+
+ /**
+ * Draw this widget with a background
+ */
+ public final void render(DrawContext context) {
+ this.render(context, true);
+ }
+
+ /**
+ * Draw this widget, possibly with a background
+ */
+ public final void render(DrawContext context, boolean hasBG) {
+ MatrixStack ms = context.getMatrices();
+
+ // not sure if this is the way to go, but it fixes Z-layer issues
+ // like blocks being rendered behind the BG and the hotbar clipping into things
+ RenderSystem.enableDepthTest();
+ ms.push();
+
+ float scale = SkyblockerConfigManager.get().general.tabHud.tabHudScale / 100f;
+ ms.scale(scale, scale, 1);
+
+ // move above other UI elements
+ ms.translate(0, 0, 200);
+ if (hasBG) {
+ context.fill(x + 1, y, x + w - 1, y + h, COL_BG_BOX);
+ context.fill(x, y + 1, x + 1, y + h - 1, COL_BG_BOX);
+ context.fill(x + w - 1, y + 1, x + w, y + h - 1, COL_BG_BOX);
+ }
+ // move above background (if exists)
+ ms.translate(0, 0, 100);
+
+ int strHeightHalf = Widget.txtRend.fontHeight / 2;
+ int strAreaWidth = Widget.txtRend.getWidth(title) + 4;
+
+ context.drawText(txtRend, title, x + 8, y + 2, this.color, false);
+
+ this.drawHLine(context, x + 2, y + 1 + strHeightHalf, 4);
+ this.drawHLine(context, x + 2 + strAreaWidth + 4, y + 1 + strHeightHalf, w - 4 - 4 - strAreaWidth);
+ this.drawHLine(context, x + 2, y + h - 2, w - 4);
+
+ this.drawVLine(context, x + 1, y + 2 + strHeightHalf, h - 4 - strHeightHalf);
+ this.drawVLine(context, x + w - 2, y + 2 + strHeightHalf, h - 4 - strHeightHalf);
+
+ int yOffs = y + BORDER_SZE_N;
+
+ for (Component c : components) {
+ c.render(context, x + BORDER_SZE_W, yOffs);
+ yOffs += c.getHeight() + Component.PAD_L;
+ }
+ // pop manipulations above
+ ms.pop();
+ RenderSystem.disableDepthTest();
+ }
+
+ private void drawHLine(DrawContext context, int xpos, int ypos, int width) {
+ context.fill(xpos, ypos, xpos + width, ypos + 1, this.color);
+ }
+
+ private void drawVLine(DrawContext context, int xpos, int ypos, int height) {
+ context.fill(xpos, ypos, xpos + 1, ypos + height, this.color);
+ }
+
+ /**
+ * If the entry at idx has the format "[textA]: [textB]", the following is
+ * returned:
+ * [entryName] [textB.formatted(contentFmt)]
+ */
+ public static Text simpleEntryText(int idx, String entryName, Formatting contentFmt) {
+
+ String src = PlayerListMgr.strAt(idx);
+
+ if (src == null) {
+ return null;
+ }
+
+ int cidx = src.indexOf(':');
+ if (cidx == -1) {
+ return null;
+ }
+
+ src = src.substring(src.indexOf(':') + 1);
+ return Widget.simpleEntryText(src, entryName, contentFmt);
+ }
+
+ /**
+ * @return [entryName] [entryContent.formatted(contentFmt)]
+ */
+ public static Text simpleEntryText(String entryContent, String entryName, Formatting contentFmt) {
+ return Text.literal(entryName).append(Text.literal(entryContent).formatted(contentFmt));
+ }
+
+ /**
+ * @return the entry at idx as unformatted Text
+ */
+ public static Text plainEntryText(int idx) {
+ String str = PlayerListMgr.strAt(idx);
+ if (str == null) {
+ return null;
+ }
+ return Text.of(str);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/Component.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/Component.java
new file mode 100644
index 00000000..3c987068
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/Component.java
@@ -0,0 +1,31 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.component;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+
+/**
+ * Abstract base class for a component that may be added to a Widget.
+ */
+public abstract class Component {
+
+ static final int ICO_DIM = 16;
+ public static final int PAD_S = 2;
+ public static final int PAD_L = 4;
+
+ static final TextRenderer txtRend = MinecraftClient.getInstance().textRenderer;
+
+ // these should always be the content dimensions without any padding.
+ int width, height;
+
+ public abstract void render(DrawContext context, int x, int y);
+
+ public int getWidth() {
+ return this.width;
+ }
+
+ public int getHeight() {
+ return this.height;
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/IcoFatTextComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/IcoFatTextComponent.java
new file mode 100644
index 00000000..def60d4d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/IcoFatTextComponent.java
@@ -0,0 +1,45 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.component;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+/**
+ * Component that consists of an icon and two lines of text
+ */
+public class IcoFatTextComponent extends Component {
+
+ private static final int ICO_OFFS = 1;
+
+ private ItemStack ico;
+ private Text line1, line2;
+
+ public IcoFatTextComponent(ItemStack ico, Text l1, Text l2) {
+ this.ico = (ico == null) ? Ico.BARRIER : ico;
+ this.line1 = l1;
+ this.line2 = l2;
+
+ if (l1 == null || l2 == null) {
+ this.ico = Ico.BARRIER;
+ this.line1 = Text.literal("No data").formatted(Formatting.GRAY);
+ this.line2 = Text.literal("No data").formatted(Formatting.GRAY);
+ }
+
+ this.width = ICO_DIM + PAD_L + Math.max(txtRend.getWidth(this.line1), txtRend.getWidth(this.line2));
+ this.height = txtRend.fontHeight + PAD_S + txtRend.fontHeight;
+ }
+
+ public IcoFatTextComponent() {
+ this(null, null, null);
+ }
+
+ @Override
+ public void render(DrawContext context, int x, int y) {
+ context.drawItem(ico, x, y + ICO_OFFS);
+ context.drawText(txtRend, line1, x + ICO_DIM + PAD_L, y, 0xffffffff, false);
+ context.drawText(txtRend, line2, x + ICO_DIM + PAD_L, y + txtRend.fontHeight + PAD_S, 0xffffffff, false);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/IcoTextComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/IcoTextComponent.java
new file mode 100644
index 00000000..903a1fa3
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/IcoTextComponent.java
@@ -0,0 +1,40 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.component;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+/**
+ * Component that consists of an icon and a line of text.
+ */
+public class IcoTextComponent extends Component {
+
+ private ItemStack ico;
+ private Text text;
+
+ public IcoTextComponent(ItemStack ico, Text txt) {
+ this.ico = (ico == null) ? Ico.BARRIER : ico;
+ this.text = txt;
+
+ if (txt == null) {
+ this.ico = Ico.BARRIER;
+ this.text = Text.literal("No data").formatted(Formatting.GRAY);
+ }
+
+ this.width = ICO_DIM + PAD_L + txtRend.getWidth(this.text);
+ this.height = ICO_DIM;
+ }
+
+ public IcoTextComponent() {
+ this(null, null);
+ }
+
+ @Override
+ public void render(DrawContext context, int x, int y) {
+ context.drawItem(ico, x, y);
+ context.drawText(txtRend, text, x + ICO_DIM + PAD_L, y + 5, 0xffffffff, false);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlainTextComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlainTextComponent.java
new file mode 100644
index 00000000..59e82e4e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlainTextComponent.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.component;
+
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+/**
+ * Component that consists of a line of text.
+ */
+public class PlainTextComponent extends Component {
+
+ private Text text;
+
+ public PlainTextComponent(Text txt) {
+ this.text = txt;
+
+ if (txt == null) {
+ this.text = Text.literal("No data").formatted(Formatting.GRAY);
+ }
+
+ this.width = PAD_S + txtRend.getWidth(this.text); // looks off without padding
+ this.height = txtRend.fontHeight;
+ }
+
+ @Override
+ public void render(DrawContext context, int x, int y) {
+ context.drawText(txtRend, text, x + PAD_S, y, 0xffffffff, false);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java
new file mode 100644
index 00000000..ab79bb31
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java
@@ -0,0 +1,39 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.component;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.PlayerSkinDrawer;
+import net.minecraft.client.network.PlayerListEntry;
+import net.minecraft.scoreboard.Team;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+/**
+ * Component that consists of a player's skin icon and their name
+ */
+public class PlayerComponent extends Component {
+
+ private static final int SKIN_ICO_DIM = 8;
+
+ private final Text name;
+ private final Identifier tex;
+
+ public PlayerComponent(PlayerListEntry ple) {
+
+ boolean plainNames = SkyblockerConfigManager.get().general.tabHud.plainPlayerNames;
+ Team team = ple.getScoreboardTeam();
+ String username = ple.getProfile().getName();
+ name = (team != null && !plainNames) ? Text.empty().append(team.getPrefix()).append(Text.literal(username).formatted(team.getColor())).append(team.getSuffix()) : Text.of(username);
+ tex = ple.getSkinTextures().texture();
+
+ this.width = SKIN_ICO_DIM + PAD_S + txtRend.getWidth(name);
+ this.height = txtRend.fontHeight;
+ }
+
+ @Override
+ public void render(DrawContext context, int x, int y) {
+ PlayerSkinDrawer.draw(context, tex, x, y, SKIN_ICO_DIM);
+ context.drawText(txtRend, name, x + SKIN_ICO_DIM + PAD_S, y, 0xffffffff, false);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java
new file mode 100644
index 00000000..fa839dbe
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java
@@ -0,0 +1,69 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.component;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+
+/**
+ * Component that consists of an icon, some text and a progress bar.
+ * The progress bar either shows the fill percentage or custom text.
+ * NOTICE: pcnt is 0-100, not 0-1!
+ */
+public class ProgressComponent extends Component {
+
+ private static final int BAR_WIDTH = 100;
+ private static final int BAR_HEIGHT = txtRend.fontHeight + 3;
+ private static final int ICO_OFFS = 4;
+ private static final int COL_BG_BAR = 0xf0101010;
+
+ private final ItemStack ico;
+ private final Text desc, bar;
+ private final float pcnt;
+ private final int color;
+ private final int barW;
+
+ public ProgressComponent(ItemStack ico, Text d, Text b, float pcnt, int color) {
+ if (d == null || b == null) {
+ this.ico = Ico.BARRIER;
+ this.desc = Text.literal("No data").formatted(Formatting.GRAY);
+ this.bar = Text.literal("---").formatted(Formatting.GRAY);
+ this.pcnt = 100f;
+ this.color = 0xff000000 | Formatting.DARK_GRAY.getColorValue();
+ } else {
+ this.ico = (ico == null) ? Ico.BARRIER : ico;
+ this.desc = d;
+ this.bar = b;
+ this.pcnt = pcnt;
+ this.color = 0xff000000 | color;
+ }
+
+ this.barW = BAR_WIDTH;
+ this.width = ICO_DIM + PAD_L + Math.max(this.barW, txtRend.getWidth(this.desc));
+ this.height = txtRend.fontHeight + PAD_S + 2 + txtRend.fontHeight + 2;
+ }
+
+ public ProgressComponent(ItemStack ico, Text text, float pcnt, int color) {
+ this(ico, text, Text.of(pcnt + "%"), pcnt, color);
+ }
+
+ public ProgressComponent() {
+ this(null, null, null, 100, 0);
+ }
+
+ @Override
+ public void render(DrawContext context, int x, int y) {
+ context.drawItem(ico, x, y + ICO_OFFS);
+ context.drawText(txtRend, desc, x + ICO_DIM + PAD_L, y, 0xffffffff, false);
+
+ int barX = x + ICO_DIM + PAD_L;
+ int barY = y + txtRend.fontHeight + PAD_S;
+ int endOffsX = ((int) (this.barW * (this.pcnt / 100f)));
+ context.fill(barX + endOffsX, barY, barX + this.barW, barY + BAR_HEIGHT, COL_BG_BAR);
+ context.fill(barX, barY, barX + endOffsX, barY + BAR_HEIGHT,
+ this.color);
+ context.drawTextWithShadow(txtRend, bar, barX + 3, barY + 2, 0xffffffff);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java
new file mode 100644
index 00000000..dbc0bf55
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java
@@ -0,0 +1,58 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.component;
+
+import net.minecraft.client.gui.DrawContext;
+
+/**
+ * Meta-Component that consists of a grid of other components
+ * Grid cols are separated by lines.
+ */
+public class TableComponent extends Component {
+
+ private final Component[][] comps;
+ private final int color;
+ private final int cols, rows;
+ private int cellW, cellH;
+
+ public TableComponent(int w, int h, int col) {
+ comps = new Component[w][h];
+ color = 0xff000000 | col;
+ cols = w;
+ rows = h;
+ }
+
+ public void addToCell(int x, int y, Component c) {
+ this.comps[x][y] = c;
+
+ // pad extra to add a vertical line later
+ this.cellW = Math.max(this.cellW, c.width + PAD_S + PAD_L);
+
+ // assume all rows are equally high so overwriting doesn't matter
+ // if this wasn't the case, drawing would need more math
+ // not doing any of that if it's not needed
+ this.cellH = c.height + PAD_S;
+
+ this.width = this.cellW * this.cols;
+ this.height = (this.cellH * this.rows) - PAD_S / 2;
+
+ }
+
+ @Override
+ public void render(DrawContext context, int xpos, int ypos) {
+ for (int x = 0; x < cols; x++) {
+ for (int y = 0; y < rows; y++) {
+ if (comps[x][y] != null) {
+ comps[x][y].render(context, xpos + (x * cellW), ypos + y * cellH);
+ }
+ }
+ // add a line before the col if we're not drawing the first one
+ if (x != 0) {
+ int lineX1 = xpos + (x * cellW) - PAD_S - 1;
+ int lineX2 = xpos + (x * cellW) - PAD_S;
+ int lineY1 = ypos + 1;
+ int lineY2 = ypos + this.height - PAD_S - 1; // not sure why but it looks correct
+ context.fill(lineX1, lineY1, lineX2, lineY2, this.color);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/hud/HudCommsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/hud/HudCommsWidget.java
new file mode 100644
index 00000000..6aa363c9
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/hud/HudCommsWidget.java
@@ -0,0 +1,73 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.hud;
+
+import java.util.List;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.dwarven.DwarvenHud.Commission;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.Component;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.math.MathHelper;
+
+// this widget shows the status of the king's commissions.
+// (dwarven mines and crystal hollows)
+// USE ONLY WITH THE DWARVEN HUD!
+
+public class HudCommsWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Commissions").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ private List<Commission> commissions;
+ private boolean isFancy;
+
+ // disgusting hack to get around text renderer issues.
+ // the ctor eventually tries to get the font's height, which doesn't work
+ // when called before the client window is created (roughly).
+ // the rebdering god 2 from the fabricord explained that detail, thanks!
+ public static final HudCommsWidget INSTANCE = new HudCommsWidget();
+ public static final HudCommsWidget INSTANCE_CFG = new HudCommsWidget();
+
+ // another repulsive hack to make this widget-like hud element work with the new widget class
+ // DON'T USE WITH THE WIDGET SYSTEM, ONLY USE FOR DWARVENHUD!
+ public HudCommsWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ public void updateData(List<Commission> commissions, boolean isFancy) {
+ this.commissions = commissions;
+ this.isFancy = isFancy;
+ }
+
+ @Override
+ public void updateContent() {
+ for (Commission comm : commissions) {
+
+ Text c = Text.literal(comm.commission());
+
+ float p = 100f;
+ if (!comm.progression().contains("DONE")) {
+ p = Float.parseFloat(comm.progression().substring(0, comm.progression().length() - 1));
+ }
+
+ Component comp;
+ if (isFancy) {
+ comp = new ProgressComponent(Ico.BOOK, c, p, pcntToCol(p));
+ } else {
+ comp = new PlainTextComponent(
+ Text.literal(comm.commission() + ": ")
+ .append(Text.literal(comm.progression()).formatted(Formatting.GREEN)));
+ }
+ this.addComponent(comp);
+ }
+ }
+
+ private int pcntToCol(float pcnt) {
+ return MathHelper.hsvToRgb(pcnt / 300f, 0.9f, 0.9f);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/AdvertisementWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/AdvertisementWidget.java
new file mode 100644
index 00000000..3499ce39
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/AdvertisementWidget.java
@@ -0,0 +1,35 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.rift;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+public class AdvertisementWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Advertisement").formatted(Formatting.DARK_AQUA,
+ Formatting.BOLD);
+
+ public AdvertisementWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ boolean added = false;
+ for (int i = 73; i < 80; i++) {
+ Text text = PlayerListMgr.textAt(i);
+ if (text != null) {
+ this.addComponent(new PlainTextComponent(text));
+ added = true;
+ }
+ }
+
+ if (!added) {
+ this.addComponent(new PlainTextComponent(Text.literal("No Advertisements").formatted(Formatting.GRAY)));
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/GoodToKnowWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/GoodToKnowWidget.java
new file mode 100644
index 00000000..3a5da142
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/GoodToKnowWidget.java
@@ -0,0 +1,69 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.rift;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+public class GoodToKnowWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Good To Know").formatted(Formatting.BLUE, Formatting.BOLD);
+
+ public GoodToKnowWidget() {
+ super(TITLE, Formatting.BLUE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ // After you progress further the tab adds more info so we need to be careful of
+ // that
+ // In beginning it only shows montezuma, then timecharms and enigma souls are
+ // added
+
+ int headerPos = 0;
+ // this seems suboptimal, but I'm not sure if there's a way to do it better.
+ // search for the GTK header and offset the rest accordingly.
+ for (int i = 45; i <= 49; i++) {
+ String str = PlayerListMgr.strAt(i);
+ if (str != null && str.startsWith("Good to")) {
+ headerPos = i;
+ break;
+ }
+ }
+
+ Text posA = PlayerListMgr.textAt(headerPos + 2); // Can be times visited rift
+ Text posB = PlayerListMgr.textAt(headerPos + 4); // Can be lifetime motes or visited rift
+ Text posC = PlayerListMgr.textAt(headerPos + 6); // Can be lifetime motes
+
+ int visitedRiftPos = 0;
+ int lifetimeMotesPos = 0;
+
+ // Check each position to see what is or isn't there so we don't try adding
+ // invalid components
+ if (posA != null && posA.getString().contains("times"))
+ visitedRiftPos = headerPos + 2;
+ if (posB != null && posB.getString().contains("Motes"))
+ lifetimeMotesPos = headerPos + 4;
+ if (posB != null && posB.getString().contains("times"))
+ visitedRiftPos = headerPos + 4;
+ if (posC != null && posC.getString().contains("Motes"))
+ lifetimeMotesPos = headerPos + 6;
+
+ Text timesVisitedRift = (visitedRiftPos == headerPos + 4) ? posB : (visitedRiftPos == headerPos + 2) ? posA : Text.literal("No Data").formatted(Formatting.GRAY);
+ Text lifetimeMotesEarned = (lifetimeMotesPos == headerPos + 6) ? posC : (lifetimeMotesPos == headerPos + 4) ? posB : Text.literal("No Data").formatted(Formatting.GRAY);
+
+ if (visitedRiftPos != 0) {
+ this.addComponent(new IcoTextComponent(Ico.EXPERIENCE_BOTTLE,
+ Text.literal("Visited Rift: ").append(timesVisitedRift)));
+ }
+
+ if (lifetimeMotesPos != 0) {
+ this.addComponent(
+ new IcoTextComponent(Ico.PINK_DYE, Text.literal("Lifetime Earned: ").append(lifetimeMotesEarned)));
+ }
+
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftProfileWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftProfileWidget.java
new file mode 100644
index 00000000..178bf142
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftProfileWidget.java
@@ -0,0 +1,21 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.rift;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+public class RiftProfileWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Profile").formatted(Formatting.DARK_AQUA, Formatting.BOLD);
+
+ public RiftProfileWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.SIGN, "Profile:", Formatting.GREEN, 61);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftProgressWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftProgressWidget.java
new file mode 100644
index 00000000..93ade5cb
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftProgressWidget.java
@@ -0,0 +1,123 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.rift;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.math.MathHelper;
+
+public class RiftProgressWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Rift Progress").formatted(Formatting.BLUE, Formatting.BOLD);
+
+ private static final Pattern TIMECHARMS_PATTERN = Pattern.compile("Timecharms: (?<current>[0-9]+)\\/(?<total>[0-9]+)");
+ private static final Pattern ENIGMA_SOULS_PATTERN = Pattern.compile("Enigma Souls: (?<current>[0-9]+)\\/(?<total>[0-9]+)");
+ private static final Pattern MONTEZUMA_PATTERN = Pattern.compile("Montezuma: (?<current>[0-9]+)\\/(?<total>[0-9]+)");
+
+ public RiftProgressWidget() {
+ super(TITLE, Formatting.BLUE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ // After you progress further, the tab adds more info so we need to be careful
+ // of that.
+ // In beginning it only shows montezuma, then timecharms and enigma souls are
+ // added.
+
+ String pos44 = PlayerListMgr.strAt(44);
+
+ // LHS short-circuits, so the RHS won't be evaluated on pos44 == null
+ if (pos44 == null || !pos44.contains("Rift Progress")) {
+ this.addComponent(new PlainTextComponent(Text.literal("No Progress").formatted(Formatting.GRAY)));
+ return;
+ }
+
+ // let's try to be clever by assuming what progress item may appear where and
+ // when to skip testing every slot for every thing.
+
+ // always non-null, as this holds the topmost item.
+ // if there is none, there shouldn't be a header.
+ String pos45 = PlayerListMgr.strAt(45);
+
+ // Can be Montezuma, Enigma Souls or Timecharms.
+ // assume timecharms can only appear here and that they're the last thing to
+ // appear, so if this exists, we know the rest.
+ if (pos45.contains("Timecharms")) {
+ addTimecharmsComponent(45);
+ addEnigmaSoulsComponent(46);
+ addMontezumaComponent(47);
+ return;
+ }
+
+ // timecharms didn't appear at the top, so there's two or one entries.
+ // assume that if there's two, souls is always top.
+ String pos46 = PlayerListMgr.strAt(46);
+
+ if (pos45.contains("Enigma Souls")) {
+ addEnigmaSoulsComponent(45);
+ if (pos46 != null) {
+ // souls might appear alone.
+ // if there's a second entry, it has to be montezuma
+ addMontezumaComponent(46);
+ }
+ } else {
+ // first entry isn't souls, so it's just montezuma and nothing else.
+ addMontezumaComponent(45);
+ }
+
+ }
+
+ private static int pcntToCol(float pcnt) {
+ return MathHelper.hsvToRgb(pcnt / 300f, 0.9f, 0.9f);
+ }
+
+ private void addTimecharmsComponent(int pos) {
+ Matcher m = PlayerListMgr.regexAt(pos, TIMECHARMS_PATTERN);
+
+ int current = Integer.parseInt(m.group("current"));
+ int total = Integer.parseInt(m.group("total"));
+ float pcnt = ((float) current / (float) total) * 100f;
+ Text progressText = Text.literal(current + "/" + total);
+
+ ProgressComponent pc = new ProgressComponent(Ico.NETHER_STAR, Text.literal("Timecharms"), progressText,
+ pcnt, pcntToCol(pcnt));
+
+ this.addComponent(pc);
+ }
+
+ private void addEnigmaSoulsComponent(int pos) {
+ Matcher m = PlayerListMgr.regexAt(pos, ENIGMA_SOULS_PATTERN);
+
+ int current = Integer.parseInt(m.group("current"));
+ int total = Integer.parseInt(m.group("total"));
+ float pcnt = ((float) current / (float) total) * 100f;
+ Text progressText = Text.literal(current + "/" + total);
+
+ ProgressComponent pc = new ProgressComponent(Ico.HEART_OF_THE_SEA, Text.literal("Enigma Souls"),
+ progressText, pcnt, pcntToCol(pcnt));
+
+ this.addComponent(pc);
+ }
+
+ private void addMontezumaComponent(int pos) {
+ Matcher m = PlayerListMgr.regexAt(pos, MONTEZUMA_PATTERN);
+
+ int current = Integer.parseInt(m.group("current"));
+ int total = Integer.parseInt(m.group("total"));
+ float pcnt = ((float) current / (float) total) * 100f;
+ Text progressText = Text.literal(current + "/" + total);
+
+ ProgressComponent pc = new ProgressComponent(Ico.BONE, Text.literal("Montezuma"), progressText, pcnt,
+ pcntToCol(pcnt));
+
+ this.addComponent(pc);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftServerInfoWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftServerInfoWidget.java
new file mode 100644
index 00000000..89530e2f
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftServerInfoWidget.java
@@ -0,0 +1,27 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.rift;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+/**
+ * Special version of the server info widget for the rift!
+ *
+ */
+public class RiftServerInfoWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Server Info").formatted(Formatting.LIGHT_PURPLE, Formatting.BOLD);
+
+ public RiftServerInfoWidget() {
+ super(TITLE, Formatting.LIGHT_PURPLE.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addSimpleIcoText(Ico.MAP, "Area:", Formatting.LIGHT_PURPLE, 41);
+ this.addSimpleIcoText(Ico.NTAG, "Server ID:", Formatting.GRAY, 42);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftStatsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftStatsWidget.java
new file mode 100644
index 00000000..0e2f323d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/RiftStatsWidget.java
@@ -0,0 +1,43 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.rift;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.TableComponent;
+
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+public class RiftStatsWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Stats").formatted(Formatting.DARK_AQUA, Formatting.BOLD);
+
+ public RiftStatsWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ Text riftDamage = Widget.simpleEntryText(64, "RDG", Formatting.DARK_PURPLE);
+ IcoTextComponent rdg = new IcoTextComponent(Ico.DIASWORD, riftDamage);
+
+ Text speed = Widget.simpleEntryText(65, "SPD", Formatting.WHITE);
+ IcoTextComponent spd = new IcoTextComponent(Ico.SUGAR, speed);
+
+ Text intelligence = Widget.simpleEntryText(66, "INT", Formatting.AQUA);
+ IcoTextComponent intel = new IcoTextComponent(Ico.ENCHANTED_BOOK, intelligence);
+
+ Text manaRegen = Widget.simpleEntryText(67, "MRG", Formatting.AQUA);
+ IcoTextComponent mrg = new IcoTextComponent(Ico.DIAMOND, manaRegen);
+
+ TableComponent tc = new TableComponent(2, 2, Formatting.AQUA.getColorValue());
+ tc.addToCell(0, 0, rdg);
+ tc.addToCell(0, 1, spd);
+ tc.addToCell(1, 0, intel);
+ tc.addToCell(1, 1, mrg);
+
+ this.addComponent(tc);
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/ShenWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/ShenWidget.java
new file mode 100644
index 00000000..2827400e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/rift/ShenWidget.java
@@ -0,0 +1,22 @@
+package de.hysky.skyblocker.skyblock.tabhud.widget.rift;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+public class ShenWidget extends Widget {
+
+ private static final MutableText TITLE = Text.literal("Shen's Countdown").formatted(Formatting.DARK_AQUA, Formatting.BOLD);
+
+ public ShenWidget() {
+ super(TITLE, Formatting.DARK_AQUA.getColorValue());
+ }
+
+ @Override
+ public void updateContent() {
+ this.addComponent(new PlainTextComponent(Text.literal(PlayerListMgr.strAt(70))));
+ }
+}