From 5c696dec42541a085637eae68295019e17fe7db2 Mon Sep 17 00:00:00 2001 From: Kevin <92656833+kevinthegreat1@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:09:34 -0500 Subject: Tab hud additions (#1128) * Add vanilla tab by default option * Rename PlayerListManager * Clean up builder map * Add dwarven hud config button * Add hud styles * Fancy only * Render vanilla * Update javadocs and refactor * Refactor progress component * Apply style to all progress components * Add colon processing * Change styles * Add style Javadocs * Implement minimal style * Comments --- .../java/de/hysky/skyblocker/SkyblockerMod.java | 4 +- .../config/categories/MiningCategory.java | 7 + .../config/categories/UIAndVisualsCategory.java | 19 + .../skyblocker/config/configs/MiningConfig.java | 4 + .../config/configs/UIAndVisualsConfig.java | 36 ++ .../mixins/ClientPlayNetworkHandlerMixin.java | 4 +- .../de/hysky/skyblocker/mixins/InGameHudMixin.java | 3 +- .../skyblocker/skyblock/dungeon/DungeonScore.java | 14 +- .../dungeon/secrets/DungeonPlayerManager.java | 4 +- .../skyblock/dungeon/secrets/SecretsTracker.java | 4 +- .../skyblock/dwarven/WishingCompassSolver.java | 10 +- .../skyblock/garden/FarmingHudWidget.java | 4 +- .../skyblock/garden/GardenPlotsWidget.java | 6 +- .../hysky/skyblocker/skyblock/tabhud/TabHud.java | 7 +- .../tabhud/config/WidgetsConfigurationScreen.java | 4 +- .../skyblock/tabhud/config/preview/PreviewTab.java | 6 +- .../tabhud/screenbuilder/ScreenBuilder.java | 4 +- .../tabhud/screenbuilder/ScreenMaster.java | 30 +- .../skyblock/tabhud/util/PlayerListManager.java | 421 +++++++++++++++++++++ .../skyblock/tabhud/util/PlayerListMgr.java | 421 --------------------- .../skyblock/tabhud/widget/CommsWidget.java | 16 +- .../tabhud/widget/ComponentBasedWidget.java | 23 +- .../skyblock/tabhud/widget/DungeonBuffWidget.java | 4 +- .../skyblock/tabhud/widget/DungeonDeathWidget.java | 4 +- .../tabhud/widget/DungeonDownedWidget.java | 4 +- .../tabhud/widget/DungeonPlayerWidget.java | 6 +- .../tabhud/widget/DungeonPuzzleWidget.java | 4 +- .../tabhud/widget/DungeonSecretWidget.java | 4 +- .../tabhud/widget/DungeonServerWidget.java | 10 +- .../skyblock/tabhud/widget/EffectWidget.java | 4 +- .../skyblock/tabhud/widget/ElectionWidget.java | 6 +- .../skyblock/tabhud/widget/FireSaleWidget.java | 8 +- .../skyblock/tabhud/widget/SkillsWidget.java | 11 +- .../tabhud/widget/component/Components.java | 55 +++ .../tabhud/widget/component/ProgressComponent.java | 35 +- 35 files changed, 671 insertions(+), 535 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListManager.java delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/Components.java (limited to 'src/main/java/de') diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index a08c3093..06018168 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -8,7 +8,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.datafixer.ConfigDataFixer; import de.hysky.skyblocker.skyblock.StatusBarTracker; import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.discord.DiscordRPCManager; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; @@ -66,7 +66,7 @@ public class SkyblockerMod implements ClientModInitializer { Scheduler.INSTANCE.scheduleCyclic(Utils::update, 20); Scheduler.INSTANCE.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 200); Scheduler.INSTANCE.scheduleCyclic(BackpackPreview::tick, 50); - Scheduler.INSTANCE.scheduleCyclic(PlayerListMgr::updateList, 20); + Scheduler.INSTANCE.scheduleCyclic(PlayerListManager::updateList, 20); } /** diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java index 890d86aa..3eed81da 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java @@ -7,6 +7,7 @@ import de.hysky.skyblocker.config.screens.powdertracker.PowderFilterConfigScreen import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudWidget; import de.hysky.skyblocker.skyblock.dwarven.CarpetHighlighter; import de.hysky.skyblocker.skyblock.dwarven.PowderMiningTracker; +import de.hysky.skyblocker.skyblock.tabhud.widget.CommsWidget; import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.ColorControllerBuilder; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; @@ -42,6 +43,12 @@ public class MiningCategory { .controller(ConfigUtils::createBooleanController) .build()) + .option(ButtonOption.createBuilder() + .name(Text.translatable("skyblocker.config.mining.dwarvenHud.screen")) + .text(Text.translatable("text.skyblocker.open")) + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new WidgetsConfigurationScreen(Location.DWARVEN_MINES, CommsWidget.ID, screen))) + .build()) + //Dwarven Mines .group(OptionGroup.createBuilder() .name(Text.translatable("skyblocker.config.mining.dwarvenMines")) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java index 208113ef..6d18c451 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java @@ -240,6 +240,25 @@ public class UIAndVisualsCategory { newValue -> config.uiAndVisuals.tabHud.tabHudScale = newValue) .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(10, 200).step(1)) .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.tabHud.showVanillaTabByDefault")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.tabHud.showVanillaTabByDefault.@Tooltip"))) + .binding(defaults.uiAndVisuals.tabHud.showVanillaTabByDefault, + () -> config.uiAndVisuals.tabHud.showVanillaTabByDefault, + newValue -> config.uiAndVisuals.tabHud.showVanillaTabByDefault = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.tabHud.style")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.tabHud.style.@Tooltip[0]"), + Text.translatable("skyblocker.config.uiAndVisuals.tabHud.style.@Tooltip[1]"), + Text.translatable("skyblocker.config.uiAndVisuals.tabHud.style.@Tooltip[2]"), + Text.translatable("skyblocker.config.uiAndVisuals.tabHud.style.@Tooltip[3]"))) + .binding(defaults.uiAndVisuals.tabHud.style, + () -> config.uiAndVisuals.tabHud.style, + newValue -> config.uiAndVisuals.tabHud.style = newValue) + .controller(ConfigUtils::createEnumCyclingListController) + .build()) .option(Option.createBuilder() .name(Text.translatable("skyblocker.config.uiAndVisuals.tabHud.enableHudBackground")) .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.tabHud.enableHudBackground.@Tooltip"))) diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java index f73f38a9..34c59429 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java @@ -175,6 +175,10 @@ public class MiningConfig { public boolean autoShareCorpses = false; } + /** + * @deprecated See {@link UIAndVisualsConfig.TabHudStyle}. + */ + @Deprecated public enum DwarvenHudStyle { SIMPLE, FANCY, CLASSIC; diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java index aaf6e9df..7a6b3f5d 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java @@ -187,6 +187,12 @@ public class UIAndVisualsConfig { @SerialEntry public int tabHudScale = 100; + @SerialEntry + public boolean showVanillaTabByDefault = false; + + @SerialEntry + public TabHudStyle style = TabHudStyle.FANCY; + @SerialEntry public boolean enableHudBackground = true; @@ -203,6 +209,36 @@ public class UIAndVisualsConfig { public NameSorting nameSorting = NameSorting.DEFAULT; } + public enum TabHudStyle { + /** + * The minimal style, with no decorations, icons, or custom components, + * rendered in a minimal rectangle background, + * or no background at all if {@link TabHudConf#enableHudBackground} is false. + */ + MINIMAL, + /** + * The simple style, with no decorations, icons, or custom components. + */ + SIMPLE, + /** + * The classic style, with decorations such as icons but no custom components. + */ + CLASSIC, + /** + * The default style, with all custom components and decorations in use. + */ + FANCY; + + public boolean isMinimal() { + return this == MINIMAL; + } + + @Override + public String toString() { + return I18n.translate("skyblocker.config.uiAndVisuals.tabHud.style." + name()); + } + } + @Deprecated public enum NameSorting { DEFAULT, ALPHABETICAL; diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index cf4d2060..258d6a44 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -18,7 +18,7 @@ import de.hysky.skyblocker.skyblock.end.EnderNodes; import de.hysky.skyblocker.skyblock.end.TheEnd; import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.skyblock.slayers.boss.demonlord.FirePillarAnnouncer; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual; import de.hysky.skyblocker.utils.Utils; import net.minecraft.client.network.ClientPlayNetworkHandler; @@ -101,7 +101,7 @@ public abstract class ClientPlayNetworkHandlerMixin { @Inject(method = "onPlayerListHeader", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/PlayerListHud;setFooter(Lnet/minecraft/text/Text;)V")) private void skyblocker$updatePlayerListFooter(PlayerListHeaderS2CPacket packet, CallbackInfo ci) { - PlayerListMgr.updateFooter(packet.footer()); + PlayerListManager.updateFooter(packet.footer()); } @WrapWithCondition(method = "onPlayerList", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false)) diff --git a/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java b/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java index d8373119..a41a2776 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java @@ -24,7 +24,6 @@ import net.minecraft.client.gui.LayeredDrawer; import net.minecraft.client.gui.hud.InGameHud; import net.minecraft.client.gui.hud.PlayerListHud; import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.render.RenderTickCounter; import net.minecraft.client.util.Window; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.player.PlayerEntity; @@ -202,6 +201,6 @@ public abstract class InGameHudMixin { @WrapWithCondition(method = "renderPlayerList", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/PlayerListHud;render(Lnet/minecraft/client/gui/DrawContext;ILnet/minecraft/scoreboard/Scoreboard;Lnet/minecraft/scoreboard/ScoreboardObjective;)V")) private boolean skyblocker$shouldRenderHud(PlayerListHud playerListHud, DrawContext context, int scaledWindowWidth, Scoreboard scoreboard, ScoreboardObjective objective) { - return !Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled || TabHud.defaultTgl.isPressed() || MinecraftClient.getInstance().currentScreen instanceof WidgetsConfigurationScreen; + return !Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled || TabHud.shouldRenderVanilla() || MinecraftClient.getInstance().currentScreen instanceof WidgetsConfigurationScreen; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java index a00025d0..56ae089d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java @@ -7,7 +7,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.DungeonsConfig; import de.hysky.skyblocker.events.DungeonEvents; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ProfileUtils; import de.hysky.skyblocker.utils.Utils; @@ -231,7 +231,7 @@ public class DungeonScore { } private static int getCompletedRooms() { - Matcher matcher = PlayerListMgr.regexAt(43, COMPLETED_ROOMS_PATTERN); + Matcher matcher = PlayerListManager.regexAt(43, COMPLETED_ROOMS_PATTERN); return matcher != null ? Integer.parseInt(matcher.group("rooms")) : 0; } @@ -259,14 +259,14 @@ public class DungeonScore { } private static int getPuzzleCount() { - Matcher matcher = PlayerListMgr.regexAt(47, PUZZLE_COUNT_PATTERN); + Matcher matcher = PlayerListManager.regexAt(47, PUZZLE_COUNT_PATTERN); return matcher != null ? Integer.parseInt(matcher.group("count")) : 0; } private static int getPuzzlePenalty() { int incompletePuzzles = 0; for (int index = 0; index < puzzleCount; index++) { - Matcher puzzleMatcher = PlayerListMgr.regexAt(48 + index, PUZZLES_PATTERN); + Matcher puzzleMatcher = PlayerListManager.regexAt(48 + index, PUZZLES_PATTERN); if (puzzleMatcher == null) break; if (puzzleMatcher.group("state").matches("[✖✦]")) incompletePuzzles++; } @@ -274,13 +274,13 @@ public class DungeonScore { } private static double getSecretsPercentage() { - Matcher matcher = PlayerListMgr.regexAt(44, SECRETS_PATTERN); + Matcher matcher = PlayerListManager.regexAt(44, SECRETS_PATTERN); return matcher != null ? Double.parseDouble(matcher.group("secper")) : 0; } private static int getCrypts() { - Matcher matcher = PlayerListMgr.regexAt(33, CRYPTS_PATTERN); - if (matcher == null) matcher = PlayerListMgr.regexAt(32, CRYPTS_PATTERN); //If class milestone 9 is reached, crypts goes up by 1 + Matcher matcher = PlayerListManager.regexAt(33, CRYPTS_PATTERN); + if (matcher == null) matcher = PlayerListManager.regexAt(32, CRYPTS_PATTERN); //If class milestone 9 is reached, crypts goes up by 1 return matcher != null ? Integer.parseInt(matcher.group("crypts")) : 0; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java index 6a516007..4c3a0e8c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.Range; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.events.DungeonEvents; import de.hysky.skyblocker.skyblock.dungeon.DungeonClass; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import net.minecraft.entity.player.PlayerEntity; @@ -51,6 +51,6 @@ public class DungeonPlayerManager { } private static Matcher getPlayerFromTab(@Range(from = 1, to = 5) int index) { - return PlayerListMgr.regexAt(1 + (index - 1) * 4, PLAYER_TAB_PATTERN); + return PlayerListManager.regexAt(1 + (index - 1) * 4, PLAYER_TAB_PATTERN); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java index 31d0aba0..2cf700bb 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java @@ -5,7 +5,7 @@ import com.google.gson.JsonParser; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.DungeonEvents; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.DungeonPlayerWidget; import de.hysky.skyblocker.utils.ApiUtils; import de.hysky.skyblocker.utils.Constants; @@ -131,7 +131,7 @@ public class SecretsTracker { } private static String getPlayerNameAt(int index) { - Matcher matcher = PlayerListMgr.regexAt(1 + (index - 1) * 4, DungeonPlayerWidget.PLAYER_PATTERN); + Matcher matcher = PlayerListManager.regexAt(1 + (index - 1) * 4, DungeonPlayerWidget.PLAYER_PATTERN); return matcher != null ? matcher.group("name") : ""; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java index 4b04430d..e7f4a6b7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/WishingCompassSolver.java @@ -2,7 +2,7 @@ package de.hysky.skyblocker.skyblock.dwarven; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Utils; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; @@ -110,11 +110,11 @@ public class WishingCompassSolver { return false; } //make sure the data is in tab and if not tell the user - if (PlayerListMgr.getPlayerStringList().stream().noneMatch(entry -> entry.startsWith("Active Effects:"))) { + if (PlayerListManager.getPlayerStringList().stream().noneMatch(entry -> entry.startsWith("Active Effects:"))) { CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.enableTabEffectsMessage")), false); return false; } - return PlayerListMgr.getPlayerStringList().stream().anyMatch(entry -> entry.startsWith("King's Scent")); + return PlayerListManager.getPlayerStringList().stream().anyMatch(entry -> entry.startsWith("King's Scent")); } private static boolean isKeyInInventory() { @@ -131,13 +131,13 @@ public class WishingCompassSolver { } //make sure the data is in tab and if not tell the user - if (PlayerListMgr.getPlayerStringList().stream().noneMatch(entry -> entry.equals("Crystals:"))) { + if (PlayerListManager.getPlayerStringList().stream().noneMatch(entry -> entry.equals("Crystals:"))) { CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.config.mining.crystalsWaypoints.wishingCompassSolver.enableTabMessage")), false); return false; } //return if the crystal for a zone is found - Stream displayNameStream = PlayerListMgr.getPlayerStringList().stream(); + Stream displayNameStream = PlayerListManager.getPlayerStringList().stream(); return switch (zone) { case JUNGLE -> displayNameStream.noneMatch(entry -> entry.equals("Amethyst: ✖ Not Found")); case MITHRIL_DEPOSITS -> displayNameStream.noneMatch(entry -> entry.equals("Jade: ✖ Not Found")); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java index 868bf4cf..ab37080f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java @@ -6,8 +6,8 @@ import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.widget.ComponentBasedWidget; +import de.hysky.skyblocker.skyblock.tabhud.widget.component.Components; import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent; -import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; @@ -96,7 +96,7 @@ public class FarmingHudWidget extends ComponentBasedWidget { addSimpleIcoText(Ico.GOLD, "Coins/h: ", Formatting.GOLD, getPriceText(cropItemId, cropsPerMinute)); addSimpleIcoText(cropStack, "Blocks/s: ", Formatting.YELLOW, Integer.toString(FarmingHud.blockBreaks())); //noinspection DataFlowIssue - addComponent(new ProgressComponent(Ico.LANTERN, Text.literal("Farming Level: "), FarmingHud.farmingXpPercentProgress(), Formatting.GOLD.getColorValue())); + addComponent(Components.progressComponent(Ico.LANTERN, Text.literal("Farming Level: "), FarmingHud.farmingXpPercentProgress(), Formatting.GOLD.getColorValue())); addSimpleIcoText(Ico.LIME_DYE, "Farming XP/h: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format((int) FarmingHud.farmingXpPerHour())); Entity cameraEntity = client.getCameraEntity(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/GardenPlotsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/GardenPlotsWidget.java index 41da74da..93fc0c49 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/GardenPlotsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/GardenPlotsWidget.java @@ -7,7 +7,7 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.SkyblockEvents; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.MessageScheduler; import it.unimi.dsi.fastutil.ints.*; @@ -262,8 +262,8 @@ public class GardenPlotsWidget extends ClickableWidget { private void updateInfestedFromTab() { infectedPlots.clear(); - for (int i = 0; i < PlayerListMgr.getPlayerStringList().size(); i++) { - String string = PlayerListMgr.getPlayerStringList().get(i); + for (int i = 0; i < PlayerListManager.getPlayerStringList().size(); i++) { + String string = PlayerListManager.getPlayerStringList().get(i); if (string.startsWith("Plots:")) { String[] split = string.split(":")[1].split(","); for (String s : split) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/TabHud.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/TabHud.java index 19663040..aa56e3e2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/TabHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/TabHud.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.tabhud; import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; @@ -8,7 +9,7 @@ import org.lwjgl.glfw.GLFW; public class TabHud { public static KeyBinding toggleSecondary; - public static KeyBinding defaultTgl; + private static KeyBinding defaultTgl; @Init public static void init() { @@ -23,4 +24,8 @@ public class TabHud { GLFW.GLFW_KEY_M, "key.categories.skyblocker")); } + + public static boolean shouldRenderVanilla() { + return defaultTgl.isPressed() != SkyblockerConfigManager.get().uiAndVisuals.tabHud.showVanillaTabByDefault; + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsConfigurationScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsConfigurationScreen.java index 71c5b494..8a0e0509 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsConfigurationScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsConfigurationScreen.java @@ -5,7 +5,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.tabhud.config.entries.WidgetEntry; import de.hysky.skyblocker.skyblock.tabhud.config.preview.PreviewTab; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenMaster; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; @@ -275,7 +275,7 @@ public class WidgetsConfigurationScreen extends Screen implements ScreenHandlerL this.handler.onClosed(this.client.player); } handler.removeListener(this); - Scheduler.INSTANCE.schedule(PlayerListMgr::updateList, 1); + Scheduler.INSTANCE.schedule(PlayerListManager::updateList, 1); SkyblockerConfigManager.save(); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java index 2cd743ac..18d608ed 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java @@ -8,7 +8,7 @@ import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenMaster; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.WidgetPositioner; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; import de.hysky.skyblocker.utils.EnumUtils; @@ -197,7 +197,7 @@ public class PreviewTab implements Tab { private void updatePlayerListFromPreview() { if (mode == Mode.DUNGEON) { - PlayerListMgr.updateDungeons(DungeonsTabPlaceholder.get()); + PlayerListManager.updateDungeons(DungeonsTabPlaceholder.get()); return; } if (!parent.isPreviewVisible() || parent.getHandler() == null) return; @@ -240,7 +240,7 @@ public class PreviewTab implements Tab { } } } - PlayerListMgr.updateWidgetsFrom(lines.stream().map(line -> { + PlayerListManager.updateWidgetsFrom(lines.stream().map(line -> { PlayerListEntry playerListEntry = new PlayerListEntry(new GameProfile(UUID.randomUUID(), ""), false); playerListEntry.setDisplayName(line); return playerListEntry; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java index 27efe1b8..24c42d7a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java @@ -5,7 +5,7 @@ import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.CenteredWidget import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.TopAlignedWidgetPositioner; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.WidgetPositioner; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; import de.hysky.skyblocker.utils.Location; @@ -94,7 +94,7 @@ public class ScreenBuilder { } } - for (TabHudWidget widget : PlayerListMgr.tabWidgetsToShow) { + for (TabHudWidget widget : PlayerListManager.tabWidgetsToShow) { PositionRule rule = getPositionRule(widget.getInternalID()); widget.setVisible(true); if (rule == null) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java index c7929636..2517b2e2 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java @@ -11,7 +11,8 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.skyblock.tabhud.TabHud; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; +import de.hysky.skyblocker.skyblock.tabhud.widget.CommsWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.DungeonPlayerWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; @@ -29,9 +30,12 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.util.Arrays; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; public class ScreenMaster { private static final Logger LOGGER = LogUtils.getLogger(); @@ -39,20 +43,12 @@ public class ScreenMaster { private static final int VERSION = 2; private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("hud_widgets.json"); - private static Map createBuilderMap() { - EnumMap map = new EnumMap<>(Location.class); - for (Location value : Location.values()) { - map.put(value, new ScreenBuilder(value)); - } - return map; - } - - private static final Map builderMap = createBuilderMap(); + private static final Map BUILDER_MAP = new EnumMap<>(Arrays.stream(Location.values()).collect(Collectors.toMap(Function.identity(), ScreenBuilder::new))); public static final Map widgetInstances = new HashMap<>(); public static ScreenBuilder getScreenBuilder(Location location) { - return builderMap.get(location); + return BUILDER_MAP.get(location); } /** @@ -66,7 +62,7 @@ public class ScreenMaster { MinecraftClient client = MinecraftClient.getInstance(); ScreenBuilder screenBuilder = getScreenBuilder(Utils.getLocation()); if (client.options.playerListKey.isPressed()) { - if (hud || TabHud.defaultTgl.isPressed()) return; + if (hud || TabHud.shouldRenderVanilla()) return; if (TabHud.toggleSecondary.isPressed()) { screenBuilder.run(context, w, h, ScreenLayer.SECONDARY_TAB); } else { @@ -81,7 +77,7 @@ public class ScreenMaster { try (BufferedReader reader = Files.newBufferedReader(FILE)) { JsonObject object = SkyblockerMod.GSON.fromJson(reader, JsonObject.class); JsonObject positions = object.getAsJsonObject("positions"); - for (Map.Entry builderEntry : builderMap.entrySet()) { + for (Map.Entry builderEntry : BUILDER_MAP.entrySet()) { Location location = builderEntry.getKey(); ScreenBuilder screenBuilder = builderEntry.getValue(); if (positions.has(location.id())) { @@ -103,7 +99,7 @@ public class ScreenMaster { public static void saveConfig() { JsonObject output = new JsonObject(); JsonObject positions = new JsonObject(); - for (Map.Entry builderEntry : builderMap.entrySet()) { + for (Map.Entry builderEntry : BUILDER_MAP.entrySet()) { Location location = builderEntry.getKey(); ScreenBuilder screenBuilder = builderEntry.getValue(); JsonObject locationObject = new JsonObject(); @@ -137,12 +133,12 @@ public class ScreenMaster { for (Location loc : new Location[]{Location.CRYSTAL_HOLLOWS, Location.DWARVEN_MINES}) { screenBuilder = getScreenBuilder(loc); screenBuilder.setPositionRule( - "commissions", + CommsWidget.ID, new PositionRule("screen", PositionRule.Point.DEFAULT, PositionRule.Point.DEFAULT, 5, 5, ScreenMaster.ScreenLayer.HUD) ); screenBuilder.setPositionRule( "powders", - new PositionRule("commissions", new PositionRule.Point(PositionRule.VerticalPoint.BOTTOM, PositionRule.HorizontalPoint.LEFT), PositionRule.Point.DEFAULT, 0, 2, ScreenMaster.ScreenLayer.HUD) + new PositionRule(CommsWidget.ID, new PositionRule.Point(PositionRule.VerticalPoint.BOTTOM, PositionRule.HorizontalPoint.LEFT), PositionRule.Point.DEFAULT, 0, 2, ScreenMaster.ScreenLayer.HUD) ); } @@ -184,7 +180,7 @@ public class ScreenMaster { public static void addWidgetInstance(HudWidget widget) { HudWidget put = widgetInstances.put(widget.getInternalID(), widget); if (widget instanceof TabHudWidget tabHudWidget) { - PlayerListMgr.tabWidgetInstances.put(tabHudWidget.getHypixelWidgetName(), tabHudWidget); + PlayerListManager.tabWidgetInstances.put(tabHudWidget.getHypixelWidgetName(), tabHudWidget); } if (put != null) LOGGER.warn("[Skyblocker] Duplicate hud widget found: {}", widget); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListManager.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListManager.java new file mode 100644 index 00000000..c60601e9 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListManager.java @@ -0,0 +1,421 @@ +package de.hysky.skyblocker.skyblock.tabhud.util; + +import com.mojang.authlib.GameProfile; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.mixins.accessors.PlayerListHudAccessor; +import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenMaster; +import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; +import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent; +import de.hysky.skyblocker.utils.Utils; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class may be used to get data from the player list. It doesn't get its + * data every frame, instead, a scheduler is used to update the data this class + * is holding periodically. The list is sorted like in the vanilla game. + */ +public class PlayerListManager { + + public static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Regex"); + private static final Pattern PLAYERS_COLUMN_PATTERN = Pattern.compile("(^|\\s*)(Players \\(\\d+\\)|Island|Coop \\(\\d+\\))(\\s*|$)"); + private static final Pattern INFO_COLUMN_PATTERN = Pattern.compile("(^|\\s*)Info(\\s*|$)"); + + /** + * The player list in tab. + */ + private static List playerList = new ArrayList<>(); // Initialize to prevent npe. + /** + * The player list in tab, but a list of strings instead of {@link PlayerListEntry}s. + */ + private static List playerStringList = new ArrayList<>(); + @Nullable + private static String footer; + public static final Map tabWidgetInstances = new Object2ObjectOpenHashMap<>(); + public static final List tabWidgetsToShow = new ObjectArrayList<>(5); + + public static void updateList() { + + if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled) { + if (!tabWidgetsToShow.isEmpty()) { + tabWidgetsToShow.clear(); + ScreenBuilder.positionsNeedsUpdating = true; + } + return; + } + + ClientPlayNetworkHandler cpnwh = MinecraftClient.getInstance().getNetworkHandler(); + + // check is needed, else game crashes on server leave + if (cpnwh != null) { + playerList = cpnwh.getPlayerList().stream().sorted(PlayerListHudAccessor.getOrdering()).toList(); + playerStringList = playerList.stream().map(PlayerListEntry::getDisplayName).filter(Objects::nonNull).map(Text::getString).map(String::strip).toList(); + } + + if (MinecraftClient.getInstance().currentScreen instanceof WidgetsConfigurationScreen widgetsConfigurationScreen && widgetsConfigurationScreen.isPreviewVisible()) return; + + if (Utils.isInDungeons()) { + updateDungeons(null); + } else { + updateWidgetsFrom(playerList); + } + } + + /** + * Update specifically for dungeons cuz they don't use the new system I HATE THEM + * + * @param lines used for the config screen + */ + public static void updateDungeons(List lines) { + if (lines != null) { + // This is so wack I hate this + playerList = new ArrayList<>(); + for (int i = 0; i < lines.size(); i++) { + playerList.add(new PlayerListEntry(new GameProfile(UUID.randomUUID(), String.valueOf(i)), false)); + playerList.getLast().setDisplayName(lines.get(i)); + } + } + + tabWidgetsToShow.clear(); + tabWidgetsToShow.add(getTabHudWidget("Dungeon Buffs", List.of())); + tabWidgetsToShow.add(getTabHudWidget("Dungeon Deaths", List.of())); + tabWidgetsToShow.add(getTabHudWidget("Dungeon Downed", List.of())); + tabWidgetsToShow.add(getTabHudWidget("Dungeon Puzzles", List.of())); + tabWidgetsToShow.add(getTabHudWidget("Dungeon Discoveries", List.of())); + tabWidgetsToShow.add(getTabHudWidget("Dungeon Info", List.of())); + for (int i = 1; i < 6; i++) { + tabWidgetsToShow.add(getTabHudWidget("Dungeon Player " + i, List.of())); + } + + } + + /** + * Update the tab widgets using a list of text representing the lines of the in-game TAB + * + * @param lines in-game TAB + */ + public static void updateWidgetsFrom(List lines) { + final Predicate playersColumnPredicate = PLAYERS_COLUMN_PATTERN.asMatchPredicate(); + final Predicate infoColumnPredicate = INFO_COLUMN_PATTERN.asMatchPredicate(); + + tabWidgetsToShow.clear(); + boolean doingPlayers = false; + boolean playersDone = false; + IntObjectPair hypixelWidgetName = IntObjectPair.of(0xFFFF00, ""); + // These two lists should match each other. + // playerListEntries is only used for the player list widget + List contents = new ArrayList<>(); + List playerListEntries = new ArrayList<>(); + + for (PlayerListEntry playerListEntry : lines) { + Text displayName = playerListEntry.getDisplayName(); + if (displayName == null) continue; + String string = displayName.getString(); + + if (string.isBlank()) continue; + if (!playersDone) { + // check if Players (number) + if (playersColumnPredicate.test(string)) { + if (!doingPlayers) { + doingPlayers = true; + // noinspection DataFlowIssue + hypixelWidgetName = IntObjectPair.of(Formatting.AQUA.getColorValue(), "Players"); + } + continue; + } + // Check if info, if it is, dip out + if (infoColumnPredicate.test(string)) { + playersDone = true; + if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); + contents.clear(); + playerListEntries.clear(); + continue; + } + } else { + if (infoColumnPredicate.test(string)) continue; + // New widget alert!!!! + // Now check for : because of the farming contest ACTIVE + // Check for mining event minutes CUZ THEY FUCKING FORGOT THE SPACE iefzeoifzeoifomezhif + if (!string.startsWith(" ") && string.contains(":") && (!hypixelWidgetName.right().startsWith("Mining Event") || !string.toLowerCase().startsWith("ends in"))) { + if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); + contents.clear(); + playerListEntries.clear(); + Pair, ? extends Text> nameAndInfo = getNameAndInfo(displayName); + hypixelWidgetName = nameAndInfo.left(); + if (!nameAndInfo.right().getString().isBlank()) { + contents.add(trim(nameAndInfo.right())); + playerListEntries.add(playerListEntry); + } + continue; + } + } + // Add the line to the content + contents.add(trim(displayName)); + playerListEntries.add(playerListEntry); + } + if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); + if (!tabWidgetsToShow.contains(tabWidgetInstances.get("Active Effects")) && SkyblockerConfigManager.get().uiAndVisuals.tabHud.effectsFromFooter) { + tabWidgetsToShow.add(getTabHudWidget("Active Effects", List.of())); + } + ScreenBuilder.positionsNeedsUpdating = true; + } + + private static Text trim(Text text) { + List trimmedParts = new ArrayList<>(); + AtomicBoolean leadingSpaceFound = new AtomicBoolean(false); + + // leading spaces + text.visit((style, asString) -> { + String trimmed = asString; + if (!leadingSpaceFound.get()) { + trimmed = trimmed.stripLeading(); + if (!trimmed.isBlank()) leadingSpaceFound.set(true); + else return Optional.empty(); + } + trimmedParts.add(Text.literal(trimmed).setStyle(style)); + return Optional.empty(); + }, Style.EMPTY); + + // trailing spaces + for (int i = 0; i < trimmedParts.size(); i++) { + Text last = trimmedParts.removeLast(); + String trimmed = last.getString().stripTrailing(); + if (!trimmed.isBlank()) { + trimmedParts.add(Text.literal(trimmed).setStyle(last.getStyle())); + break; + } + } + + MutableText out = Text.empty(); + trimmedParts.forEach(out::append); + + return out; + } + + private static TabHudWidget getTabHudWidget(IntObjectPair hypixelWidgetName, List lines, @Nullable List playerListEntries) { + TabHudWidget tabHudWidget; + if (tabWidgetInstances.containsKey(hypixelWidgetName.right())) { + tabHudWidget = tabWidgetInstances.get(hypixelWidgetName.right()); + } else { + tabHudWidget = new DefaultTabHudWidget(hypixelWidgetName.right(), Text.literal(hypixelWidgetName.right()).formatted(Formatting.BOLD), hypixelWidgetName.firstInt()); + ScreenMaster.addWidgetInstance(tabHudWidget); + } + tabHudWidget.updateFromTab(lines, playerListEntries); + tabHudWidget.update(); + return tabHudWidget; + } + + private static TabHudWidget getTabHudWidget(String hypixelWidgetName, List lines) { + return getTabHudWidget(IntObjectPair.of(0xFFFF0000, hypixelWidgetName), lines, null); + } + + /** + * @param text a line of text that contains a : from the tab + * @return a pair containing: + *
    + *
  • an int and string pair for the color and the widget name
  • + *
  • a text with the extra info sometimes shown on the right of the :
  • + *
+ */ + private static Pair, ? extends Text> getNameAndInfo(Text text) { + ObjectObjectMutablePair toReturn = new ObjectObjectMutablePair<>("", Text.empty()); + AtomicBoolean inInfo = new AtomicBoolean(false); + AtomicInteger colorOutput = new AtomicInteger(0xFFFF00); + text.visit((style, asString) -> { + if (inInfo.get()) { + toReturn.right().append(Text.literal(asString).fillStyle(style)); + } else { + if (asString.contains(":")) { + inInfo.set(true); + String[] split = asString.split(":", 2); + toReturn.left(toReturn.left() + split[0]); + toReturn.right().append(Text.literal(split[1]).fillStyle(style)); + if (style.getColor() != null) colorOutput.set(style.getColor().getRgb()); + } else { + toReturn.left(toReturn.left() + asString); + } + } + return Optional.empty(); + }, Style.EMPTY); + + return Pair.of(IntObjectPair.of(colorOutput.get(), toReturn.left()), toReturn.right()); + } + + /** + * @return the cached player list + */ + public static List getPlayerList() { + return playerList; + } + + /** + * @return the cached player list as a list of strings + */ + public static List getPlayerStringList() { + return playerStringList; + } + + public static void updateFooter(Text f) { + if (f == null) { + footer = null; + } else { + footer = f.getString(); + if (footer.isEmpty()) { + footer = null; + } + } + } + + @Nullable + public static String getFooter() { + return footer; + } + + /** + * Get the display name at some index of the player list and apply a pattern to + * it + * + * @return the matcher if p fully matches, else null + */ + public static Matcher regexAt(int idx, Pattern p) { + + String str = PlayerListManager.strAt(idx); + + if (str == null) { + return null; + } + + Matcher m = p.matcher(str); + if (!m.matches()) { + LOGGER.error("no match: \"{}\" against \"{}\"", str, p); + return null; + } else { + return m; + } + } + + /** + * Get the display name at some index of the player list as string + * + * @return the string or null, if the display name is null, empty or whitespace + * only + */ + public static String strAt(int idx) { + + if (playerList == null) { + return null; + } + + if (playerList.size() <= idx) { + return null; + } + + Text txt = playerList.get(idx).getDisplayName(); + if (txt == null) { + return null; + } + String str = txt.getString().trim(); + if (str.isEmpty()) { + return null; + } + return str; + } + + /** + * Gets the display name at some index of the player list + * + * @return the text or null, if the display name is null + * @implNote currently designed specifically for crimson isles faction quests + * widget and the rift widgets, might not work correctly without + * modification for other stuff. you've been warned! + */ + public static Text textAt(int idx) { + + if (playerList == null) { + return null; + } + + if (playerList.size() <= idx) { + return null; + } + + Text txt = playerList.get(idx).getDisplayName(); + if (txt == null) { + return null; + } + + // Rebuild the text object to remove leading space thats in all faction quest + // stuff (also removes trailing space just in case) + MutableText newText = Text.empty(); + int size = txt.getSiblings().size(); + + for (int i = 0; i < size; i++) { + Text current = txt.getSiblings().get(i); + String textToAppend = current.getString(); + + // Trim leading & trailing space - this can only be done at the start and end + // otherwise it'll produce malformed results + if (i == 0) + textToAppend = textToAppend.stripLeading(); + if (i == size - 1) + textToAppend = textToAppend.stripTrailing(); + + newText.append(Text.literal(textToAppend).setStyle(current.getStyle())); + } + + // Avoid returning an empty component - Rift advertisements needed this + if (newText.getString().isEmpty()) { + return null; + } + + return newText; + } + + /** + * Get the display name at some index of the player list as Text as seen in the + * game + * + * @return the PlayerListEntry at that index + */ + public static PlayerListEntry getRaw(int idx) { + return playerList.get(idx); + } + + public static int getSize() { + return playerList.size(); + } + + private static final class DefaultTabHudWidget extends TabHudWidget { + public DefaultTabHudWidget(String hypixelWidgetName, MutableText title, int color) { + super(hypixelWidgetName, title, color); + } + + @Override + protected void updateContent(List lines) { + lines.forEach(text -> addComponent(new PlainTextComponent(text))); + } + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java deleted file mode 100644 index d39c5d6c..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListMgr.java +++ /dev/null @@ -1,421 +0,0 @@ -package de.hysky.skyblocker.skyblock.tabhud.util; - -import com.mojang.authlib.GameProfile; -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.mixins.accessors.PlayerListHudAccessor; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenMaster; -import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; -import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent; -import de.hysky.skyblocker.utils.Utils; -import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.ints.IntObjectPair; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.client.network.PlayerListEntry; -import net.minecraft.text.MutableText; -import net.minecraft.text.Style; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * This class may be used to get data from the player list. It doesn't get its - * data every frame, instead, a scheduler is used to update the data this class - * is holding periodically. The list is sorted like in the vanilla game. - */ -public class PlayerListMgr { - - public static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Regex"); - private static final Pattern PLAYERS_COLUMN_PATTERN = Pattern.compile("(^|\\s*)(Players \\(\\d+\\)|Island|Coop \\(\\d+\\))(\\s*|$)"); - private static final Pattern INFO_COLUMN_PATTERN = Pattern.compile("(^|\\s*)Info(\\s*|$)"); - - /** - * The player list in tab. - */ - private static List playerList = new ArrayList<>(); // Initialize to prevent npe. - /** - * The player list in tab, but a list of strings instead of {@link PlayerListEntry}s. - */ - private static List playerStringList = new ArrayList<>(); - @Nullable - private static String footer; - public static final Map tabWidgetInstances = new Object2ObjectOpenHashMap<>(); - public static final List tabWidgetsToShow = new ObjectArrayList<>(5); - - public static void updateList() { - - if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled) { - if (!tabWidgetsToShow.isEmpty()) { - tabWidgetsToShow.clear(); - ScreenBuilder.positionsNeedsUpdating = true; - } - return; - } - - ClientPlayNetworkHandler cpnwh = MinecraftClient.getInstance().getNetworkHandler(); - - // check is needed, else game crashes on server leave - if (cpnwh != null) { - playerList = cpnwh.getPlayerList().stream().sorted(PlayerListHudAccessor.getOrdering()).toList(); - playerStringList = playerList.stream().map(PlayerListEntry::getDisplayName).filter(Objects::nonNull).map(Text::getString).map(String::strip).toList(); - } - - if (MinecraftClient.getInstance().currentScreen instanceof WidgetsConfigurationScreen widgetsConfigurationScreen && widgetsConfigurationScreen.isPreviewVisible()) return; - - if (Utils.isInDungeons()) { - updateDungeons(null); - } else { - updateWidgetsFrom(playerList); - } - } - - /** - * Update specifically for dungeons cuz they don't use the new system I HATE THEM - * - * @param lines used for the config screen - */ - public static void updateDungeons(List lines) { - if (lines != null) { - // This is so wack I hate this - playerList = new ArrayList<>(); - for (int i = 0; i < lines.size(); i++) { - playerList.add(new PlayerListEntry(new GameProfile(UUID.randomUUID(), String.valueOf(i)), false)); - playerList.getLast().setDisplayName(lines.get(i)); - } - } - - tabWidgetsToShow.clear(); - tabWidgetsToShow.add(getTabHudWidget("Dungeon Buffs", List.of())); - tabWidgetsToShow.add(getTabHudWidget("Dungeon Deaths", List.of())); - tabWidgetsToShow.add(getTabHudWidget("Dungeon Downed", List.of())); - tabWidgetsToShow.add(getTabHudWidget("Dungeon Puzzles", List.of())); - tabWidgetsToShow.add(getTabHudWidget("Dungeon Discoveries", List.of())); - tabWidgetsToShow.add(getTabHudWidget("Dungeon Info", List.of())); - for (int i = 1; i < 6; i++) { - tabWidgetsToShow.add(getTabHudWidget("Dungeon Player " + i, List.of())); - } - - } - - /** - * Update the tab widgets using a list of text representing the lines of the in-game TAB - * - * @param lines in-game TAB - */ - public static void updateWidgetsFrom(List lines) { - final Predicate playersColumnPredicate = PLAYERS_COLUMN_PATTERN.asMatchPredicate(); - final Predicate infoColumnPredicate = INFO_COLUMN_PATTERN.asMatchPredicate(); - - tabWidgetsToShow.clear(); - boolean doingPlayers = false; - boolean playersDone = false; - IntObjectPair hypixelWidgetName = IntObjectPair.of(0xFFFF00, ""); - // These two lists should match each other. - // playerListEntries is only used for the player list widget - List contents = new ArrayList<>(); - List playerListEntries = new ArrayList<>(); - - for (PlayerListEntry playerListEntry : lines) { - Text displayName = playerListEntry.getDisplayName(); - if (displayName == null) continue; - String string = displayName.getString(); - - if (string.isBlank()) continue; - if (!playersDone) { - // check if Players (number) - if (playersColumnPredicate.test(string)) { - if (!doingPlayers) { - doingPlayers = true; - // noinspection DataFlowIssue - hypixelWidgetName = IntObjectPair.of(Formatting.AQUA.getColorValue(), "Players"); - } - continue; - } - // Check if info, if it is, dip out - if (infoColumnPredicate.test(string)) { - playersDone = true; - if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); - contents.clear(); - playerListEntries.clear(); - continue; - } - } else { - if (infoColumnPredicate.test(string)) continue; - // New widget alert!!!! - // Now check for : because of the farming contest ACTIVE - // Check for mining event minutes CUZ THEY FUCKING FORGOT THE SPACE iefzeoifzeoifomezhif - if (!string.startsWith(" ") && string.contains(":") && (!hypixelWidgetName.right().startsWith("Mining Event") || !string.toLowerCase().startsWith("ends in"))) { - if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); - contents.clear(); - playerListEntries.clear(); - Pair, ? extends Text> nameAndInfo = getNameAndInfo(displayName); - hypixelWidgetName = nameAndInfo.left(); - if (!nameAndInfo.right().getString().isBlank()) { - contents.add(trim(nameAndInfo.right())); - playerListEntries.add(playerListEntry); - } - continue; - } - } - // Add the line to the content - contents.add(trim(displayName)); - playerListEntries.add(playerListEntry); - } - if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); - if (!tabWidgetsToShow.contains(tabWidgetInstances.get("Active Effects")) && SkyblockerConfigManager.get().uiAndVisuals.tabHud.effectsFromFooter) { - tabWidgetsToShow.add(getTabHudWidget("Active Effects", List.of())); - } - ScreenBuilder.positionsNeedsUpdating = true; - } - - private static Text trim(Text text) { - List trimmedParts = new ArrayList<>(); - AtomicBoolean leadingSpaceFound = new AtomicBoolean(false); - - // leading spaces - text.visit((style, asString) -> { - String trimmed = asString; - if (!leadingSpaceFound.get()) { - trimmed = trimmed.stripLeading(); - if (!trimmed.isBlank()) leadingSpaceFound.set(true); - else return Optional.empty(); - } - trimmedParts.add(Text.literal(trimmed).setStyle(style)); - return Optional.empty(); - }, Style.EMPTY); - - // trailing spaces - for (int i = 0; i < trimmedParts.size(); i++) { - Text last = trimmedParts.removeLast(); - String trimmed = last.getString().stripTrailing(); - if (!trimmed.isBlank()) { - trimmedParts.add(Text.literal(trimmed).setStyle(last.getStyle())); - break; - } - } - - MutableText out = Text.empty(); - trimmedParts.forEach(out::append); - - return out; - } - - private static TabHudWidget getTabHudWidget(IntObjectPair hypixelWidgetName, List lines, @Nullable List playerListEntries) { - TabHudWidget tabHudWidget; - if (tabWidgetInstances.containsKey(hypixelWidgetName.right())) { - tabHudWidget = tabWidgetInstances.get(hypixelWidgetName.right()); - } else { - tabHudWidget = new DefaultTabHudWidget(hypixelWidgetName.right(), Text.literal(hypixelWidgetName.right()).formatted(Formatting.BOLD), hypixelWidgetName.firstInt()); - ScreenMaster.addWidgetInstance(tabHudWidget); - } - tabHudWidget.updateFromTab(lines, playerListEntries); - tabHudWidget.update(); - return tabHudWidget; - } - - private static TabHudWidget getTabHudWidget(String hypixelWidgetName, List lines) { - return getTabHudWidget(IntObjectPair.of(0xFFFF0000, hypixelWidgetName), lines, null); - } - - /** - * @param text a line of text that contains a : from the tab - * @return a pair containing: - *
    - *
  • an int and string pair for the color and the widget name
  • - *
  • a text with the extra info sometimes shown on the right of the :
  • - *
- */ - private static Pair, ? extends Text> getNameAndInfo(Text text) { - ObjectObjectMutablePair toReturn = new ObjectObjectMutablePair<>("", Text.empty()); - AtomicBoolean inInfo = new AtomicBoolean(false); - AtomicInteger colorOutput = new AtomicInteger(0xFFFF00); - text.visit((style, asString) -> { - if (inInfo.get()) { - toReturn.right().append(Text.literal(asString).fillStyle(style)); - } else { - if (asString.contains(":")) { - inInfo.set(true); - String[] split = asString.split(":", 2); - toReturn.left(toReturn.left() + split[0]); - toReturn.right().append(Text.literal(split[1]).fillStyle(style)); - if (style.getColor() != null) colorOutput.set(style.getColor().getRgb()); - } else { - toReturn.left(toReturn.left() + asString); - } - } - return Optional.empty(); - }, Style.EMPTY); - - return Pair.of(IntObjectPair.of(colorOutput.get(), toReturn.left()), toReturn.right()); - } - - /** - * @return the cached player list - */ - public static List getPlayerList() { - return playerList; - } - - /** - * @return the cached player list as a list of strings - */ - public static List getPlayerStringList() { - return playerStringList; - } - - public static void updateFooter(Text f) { - if (f == null) { - footer = null; - } else { - footer = f.getString(); - if (footer.isEmpty()) { - footer = null; - } - } - } - - @Nullable - public static String getFooter() { - return footer; - } - - /** - * Get the display name at some index of the player list and apply a pattern to - * it - * - * @return the matcher if p fully matches, else null - */ - public static Matcher regexAt(int idx, Pattern p) { - - String str = PlayerListMgr.strAt(idx); - - if (str == null) { - return null; - } - - Matcher m = p.matcher(str); - if (!m.matches()) { - LOGGER.error("no match: \"{}\" against \"{}\"", str, p); - return null; - } else { - return m; - } - } - - /** - * Get the display name at some index of the player list as string - * - * @return the string or null, if the display name is null, empty or whitespace - * only - */ - public static String strAt(int idx) { - - if (playerList == null) { - return null; - } - - if (playerList.size() <= idx) { - return null; - } - - Text txt = playerList.get(idx).getDisplayName(); - if (txt == null) { - return null; - } - String str = txt.getString().trim(); - if (str.isEmpty()) { - return null; - } - return str; - } - - /** - * Gets the display name at some index of the player list - * - * @return the text or null, if the display name is null - * @implNote currently designed specifically for crimson isles faction quests - * widget and the rift widgets, might not work correctly without - * modification for other stuff. you've been warned! - */ - public static Text textAt(int idx) { - - if (playerList == null) { - return null; - } - - if (playerList.size() <= idx) { - return null; - } - - Text txt = playerList.get(idx).getDisplayName(); - if (txt == null) { - return null; - } - - // Rebuild the text object to remove leading space thats in all faction quest - // stuff (also removes trailing space just in case) - MutableText newText = Text.empty(); - int size = txt.getSiblings().size(); - - for (int i = 0; i < size; i++) { - Text current = txt.getSiblings().get(i); - String textToAppend = current.getString(); - - // Trim leading & trailing space - this can only be done at the start and end - // otherwise it'll produce malformed results - if (i == 0) - textToAppend = textToAppend.stripLeading(); - if (i == size - 1) - textToAppend = textToAppend.stripTrailing(); - - newText.append(Text.literal(textToAppend).setStyle(current.getStyle())); - } - - // Avoid returning an empty component - Rift advertisements needed this - if (newText.getString().isEmpty()) { - return null; -