diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index b9e06fdab64..974f7ea50f3 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -56,7 +56,7 @@ public void onInitializeClient() { Scheduler.INSTANCE.scheduleCyclic(Utils::update, 20); Scheduler.INSTANCE.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 200); Scheduler.INSTANCE.scheduleCyclic(BackpackPreview::tick, 50); - Scheduler.INSTANCE.scheduleCyclic(PlayerListManager::updateList, 20); + Scheduler.INSTANCE.scheduleCyclic(PlayerListManager::tryUpdateList, 2); // 2 instead of 1 because I don't know } /** diff --git a/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java index f7f7fff6294..30a344aeb85 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java @@ -5,14 +5,9 @@ import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.FarmingConfig; -import de.hysky.skyblocker.skyblock.garden.FarmingHudWidget; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.utils.Location; -import net.azureaaron.dandelion.api.ButtonOption; import net.azureaaron.dandelion.api.ConfigCategory; import net.azureaaron.dandelion.api.Option; import net.azureaaron.dandelion.api.OptionGroup; -import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; public class FarmingCategory { @@ -33,11 +28,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.farming.farmingHud.enabled = newValue) .controller(ConfigUtils.createBooleanController()) .build()) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.farming.farmingHud.config")) - .prompt(Component.translatable("text.skyblocker.open")) - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.GARDEN, FarmingHudWidget.getInstance().getInternalID(), screen))) - .build()) .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.farming.farmingHud.counter")) .tags(CommonTags.ADDED_IN_6_4_0) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/ForagingCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/ForagingCategory.java index ae07eb7ffb6..0598ee20a6a 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/ForagingCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/ForagingCategory.java @@ -7,16 +7,11 @@ import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.skyblock.foraging.SweepOverlay; import de.hysky.skyblocker.skyblock.galatea.SeaLumiesHighlighter; -import de.hysky.skyblocker.skyblock.galatea.TreeBreakProgressHud; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.utils.Location; import net.azureaaron.dandelion.api.ConfigCategory; import net.azureaaron.dandelion.api.Option; import net.azureaaron.dandelion.api.OptionGroup; -import net.azureaaron.dandelion.api.ButtonOption; import net.azureaaron.dandelion.api.controllers.ColourController; import net.azureaaron.dandelion.api.controllers.IntegerController; -import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; public class ForagingCategory { @@ -84,11 +79,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.foraging.galatea.enableTreeBreakProgress = newValue) .controller(ConfigUtils.createBooleanController()) .build()) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.foraging.galatea.enableTreeBreakHud")) - .prompt(Component.translatable("text.skyblocker.open")) - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.GALATEA, TreeBreakProgressHud.getInstance().getInternalID(), screen))) - .build()) .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.foraging.galatea.enableTunerSolver")) .description(Component.translatable("skyblocker.config.foraging.galatea.enableTunerSolver.@Tooltip")) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java index fb8b281227f..314af544b8c 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java @@ -6,18 +6,13 @@ import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.HelperConfig; import de.hysky.skyblocker.skyblock.bazaar.BazaarHelper; -import de.hysky.skyblocker.skyblock.fishing.FishingHudWidget; import de.hysky.skyblocker.skyblock.item.SkyblockItemRarity; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.waypoint.Waypoint; -import net.azureaaron.dandelion.api.ButtonOption; import net.azureaaron.dandelion.api.ConfigCategory; import net.azureaaron.dandelion.api.Option; import net.azureaaron.dandelion.api.OptionGroup; import net.azureaaron.dandelion.api.controllers.FloatController; import net.azureaaron.dandelion.api.controllers.IntegerController; -import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; public class HelperCategory { @@ -206,11 +201,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.helpers.fishing.enableFishingHud = newValue) .controller(ConfigUtils.createBooleanController()) .build()) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.helpers.fishing.hud.screen")) - .prompt(Component.translatable("text.skyblocker.open")) - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.HUB, FishingHudWidget.getInstance().getInternalID(), screen))) - .build()) .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.helpers.fishing.fishingHookDisplay")) .description(Component.translatable("skyblocker.config.helpers.fishing.fishingHookDisplay.@Tooltip")) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/HuntingCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/HuntingCategory.java index 2501b8c8692..463c04920d0 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/HuntingCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/HuntingCategory.java @@ -5,15 +5,10 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; -import de.hysky.skyblocker.skyblock.hunting.LassoHud; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.utils.Location; -import net.azureaaron.dandelion.api.ButtonOption; import net.azureaaron.dandelion.api.ConfigCategory; import net.azureaaron.dandelion.api.Option; import net.azureaaron.dandelion.api.OptionGroup; import net.azureaaron.dandelion.api.controllers.ColourController; -import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; public class HuntingCategory { @@ -108,11 +103,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.hunting.lassoHud.enabled = newValue) .controller(ConfigUtils.createBooleanController()) .build()) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.hunting.lassoHud.hud.screen")) - .prompt(Component.translatable("text.skyblocker.open")) - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.GALATEA, LassoHud.getInstance().getInternalID(), screen))) - .build()) .build()) .build(); 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 781885c62c5..9330188e144 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java @@ -8,12 +8,8 @@ import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.MiningConfig; 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.profittrackers.PowderMiningTracker; -import de.hysky.skyblocker.skyblock.tabhud.widget.CommsWidget; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.utils.Location; import it.unimi.dsi.fastutil.objects.ObjectImmutableList; import net.azureaaron.dandelion.api.ButtonOption; import net.azureaaron.dandelion.api.ConfigCategory; @@ -67,12 +63,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .controller(ConfigUtils.createBooleanController()) .build()) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.mining.dwarvenHud.screen")) - .prompt(Component.translatable("text.skyblocker.open")) - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.DWARVEN_MINES, CommsWidget.ID, screen))) - .build()) - //Pickobulus Helper .group(OptionGroup.createBuilder() .name(Component.translatable("skyblocker.config.mining.pickobulusHelper")) @@ -209,11 +199,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.mining.crystalsHud.enabled = newValue) .controller(ConfigUtils.createBooleanController()) .build()) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.mining.crystalsHud.screen")) - .prompt(Component.translatable("text.skyblocker.open")) - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.CRYSTAL_HOLLOWS, CrystalsHudWidget.getInstance().getInternalID(), screen))) - .build()) .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.mining.crystalsHud.mapScaling")) .binding(defaults.mining.crystalsHud.mapScaling, diff --git a/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java index 3c627a1f202..d2202f37077 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java @@ -6,15 +6,12 @@ import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.skyblock.end.EndHudWidget; import de.hysky.skyblocker.skyblock.end.TheEnd; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.waypoint.Waypoint; import net.azureaaron.dandelion.api.ButtonOption; import net.azureaaron.dandelion.api.ConfigCategory; import net.azureaaron.dandelion.api.Option; import net.azureaaron.dandelion.api.OptionGroup; import net.azureaaron.dandelion.api.controllers.IntegerController; -import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; public class OtherLocationsCategory { @@ -174,11 +171,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.otherLocations.end.waypoint = newValue) .controller(ConfigUtils.createBooleanController()) .build()) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.otherLocations.end.screen")) - .prompt(Component.translatable("text.skyblocker.open")) // Reusing again lol - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.THE_END, EndHudWidget.getInstance().getInternalID(), screen))) - .build()) .option(ButtonOption.createBuilder() .name(Component.translatable("skyblocker.config.otherLocations.end.resetName")) .prompt(Component.translatable("skyblocker.config.otherLocations.end.resetText")) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java index 2d92e27d445..2f1e94bd71a 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/SlayersCategory.java @@ -5,18 +5,13 @@ import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.SlayersConfig; -import de.hysky.skyblocker.skyblock.slayers.SlayerHudWidget; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.chat.ChatFilterResult; -import net.azureaaron.dandelion.api.ButtonOption; import net.azureaaron.dandelion.api.ConfigCategory; import net.azureaaron.dandelion.api.Option; import net.azureaaron.dandelion.api.OptionGroup; import net.azureaaron.dandelion.api.controllers.ColourController; import net.azureaaron.dandelion.api.controllers.FloatController; import net.azureaaron.dandelion.api.controllers.IntegerController; -import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import java.awt.Color; @@ -64,11 +59,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.slayers.enableHud = newValue) .controller(ConfigUtils.createBooleanController()) .build()) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.slayer.slayerHud")) - .prompt(Component.translatable("text.skyblocker.open")) - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.HUB, SlayerHudWidget.getInstance().getInternalID(), screen))) - .build()) .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.slayer.bossbar")) .description(Component.translatable("skyblocker.config.slayer.bossbar.@Tooltip")) 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 b851b1b463c..bb666425a20 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java @@ -6,17 +6,16 @@ import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.configs.UIAndVisualsConfig; import de.hysky.skyblocker.skyblock.GyroOverlay; -import de.hysky.skyblocker.skyblock.ItemPickupWidget; import de.hysky.skyblocker.skyblock.fancybars.StatusBarsConfigScreen; import de.hysky.skyblocker.skyblock.item.slottext.SlotTextManager; import de.hysky.skyblocker.skyblock.item.slottext.SlotTextMode; import de.hysky.skyblocker.skyblock.radialMenu.RadialMenu; import de.hysky.skyblocker.skyblock.radialMenu.RadialMenuManager; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.LayerConfig; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; import de.hysky.skyblocker.skyblock.teleport.TeleportOverlay; import de.hysky.skyblocker.skyblock.waypoint.WaypointsScreen; -import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.container.SlotTextAdder; import de.hysky.skyblocker.utils.render.title.TitleContainerConfigScreen; import de.hysky.skyblocker.utils.waypoint.Waypoint; @@ -33,6 +32,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.input.InputQuirks; import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.ArrayUtils; import java.awt.Color; import java.util.Comparator; @@ -296,7 +296,8 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .name(Component.translatable("skyblocker.config.uiAndVisuals.tabHud")) .collapsed(true) .option(Option.createBuilder() - .name(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.tabHudEnabled")) + .name(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.enableFancyTab")) + .description(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.enableFancyTab.@Tooltip")) .binding(defaults.uiAndVisuals.tabHud.tabHudEnabled, () -> config.uiAndVisuals.tabHud.tabHudEnabled, newValue -> config.uiAndVisuals.tabHud.tabHudEnabled = newValue) @@ -305,9 +306,9 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .option(ButtonOption.createBuilder() .name(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.configScreen")) .description(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.configScreen.@Tooltip")) - .tags(Component.literal("gui")) + .tags(ArrayUtils.add(WidgetManager.WIDGET_INSTANCES.values().stream().map(w -> w.getInformation().displayName()).toArray(Component[]::new), Component.literal("gui"))) .prompt(Component.translatable("text.skyblocker.open")) - .action(WidgetsConfigurationScreen::openWidgetsConfigScreen) + .action(_ -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen())) .build()) .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.tabHudScale")) @@ -367,7 +368,7 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig () -> config.uiAndVisuals.tabHud.effectsFromFooter, newValue -> config.uiAndVisuals.tabHud.effectsFromFooter = newValue) .build()) - .option(Option.createBuilder() + .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.defaultPositioning")) .binding(defaults.uiAndVisuals.tabHud.defaultPositioning, () -> config.uiAndVisuals.tabHud.defaultPositioning, @@ -983,11 +984,6 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .group(OptionGroup.createBuilder() .name(Component.translatable("skyblocker.config.uiAndVisuals.itemPickup")) .collapsed(true) - .option(ButtonOption.createBuilder() - .name(Component.translatable("skyblocker.config.uiAndVisuals.itemPickup.hud.screen")) - .prompt(Component.translatable("text.skyblocker.open")) - .action(screen -> Minecraft.getInstance().setScreen(new WidgetsConfigurationScreen(Location.HUB, ItemPickupWidget.getInstance().getInternalID(), screen))) - .build()) .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.uiAndVisuals.itemPickup.sackNotifications")) .description(Component.translatable("skyblocker.config.uiAndVisuals.itemPickup.sackNotifications.@Tooltip")) 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 f188d4179a5..f8cb1cf790f 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java @@ -2,7 +2,7 @@ import de.hysky.skyblocker.skyblock.GyroOverlay; import de.hysky.skyblocker.skyblock.item.slottext.SlotTextMode; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.LayerConfig; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.utils.waypoint.Waypoint; import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; @@ -178,6 +178,8 @@ public String toString() { public static class TabHudConf { public boolean tabHudEnabled = true; + public boolean enableFancyWidgetsList = true; + public int tabHudScale = 100; public boolean showVanillaTabByDefault = true; @@ -192,7 +194,7 @@ public static class TabHudConf { public boolean effectsFromFooter = false; - public ScreenBuilder.DefaultPositioner defaultPositioning = ScreenBuilder.DefaultPositioner.CENTERED; + public LayerConfig.Positioner defaultPositioning = LayerConfig.Positioner.CENTERED; @Deprecated public transient boolean plainPlayerNames = false; diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPacketListenerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPacketListenerMixin.java index 86e12ab5a67..45956db6db5 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPacketListenerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPacketListenerMixin.java @@ -140,6 +140,11 @@ protected ClientPacketListenerMixin(Minecraft client, Connection connection, Com PlayerListManager.updateFooter(packet.footer()); } + @Inject(method = "handlePlayerInfoUpdate", at = @At("HEAD")) + private void skyblocker$onListChange(CallbackInfo ci) { + PlayerListManager.shouldUpdateNextTick = true; + } + @WrapWithCondition(method = "handlePlayerInfoUpdate", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V")) private boolean skyblocker$cancelPlayerListWarning(Logger instance, String format, Object arg1, Object arg2) { return !Utils.isOnHypixel(); diff --git a/src/main/java/de/hysky/skyblocker/mixins/MenuScreensConstructorMixin.java b/src/main/java/de/hysky/skyblocker/mixins/MenuScreensConstructorMixin.java index 6ce4873dc0f..2578545ee93 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/MenuScreensConstructorMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/MenuScreensConstructorMixin.java @@ -11,7 +11,7 @@ import de.hysky.skyblocker.skyblock.radialMenu.RadialMenu; import de.hysky.skyblocker.skyblock.radialMenu.RadialMenuManager; import de.hysky.skyblocker.skyblock.radialMenu.RadialMenuScreen; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; +import de.hysky.skyblocker.skyblock.tabhud.config.list.WidgetsListScreen; import de.hysky.skyblocker.utils.Utils; import java.util.Locale; import net.minecraft.client.Minecraft; @@ -99,11 +99,11 @@ public interface MenuScreensConstructorMixin { } // Excessive widgets config - case ChestMenu containerScreenHandler when SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled && WidgetsConfigurationScreen.overrideWidgetsScreen && (nameLowercase.startsWith("widgets in") || nameLowercase.startsWith("widgets on") || nameLowercase.equals("tablist widgets") || (nameLowercase.endsWith("widget settings") && !nameLowercase.startsWith("reset")) || (nameLowercase.startsWith("shown") && client.screen instanceof WidgetsConfigurationScreen)) -> { + case ChestMenu containerScreenHandler when WidgetsListScreen.overrideWidgetsScreen && (nameLowercase.startsWith("widgets in") || nameLowercase.startsWith("widgets on") || nameLowercase.equals("tablist widgets") || (nameLowercase.endsWith("widget settings") && !nameLowercase.startsWith("reset")) || (nameLowercase.startsWith("shown") && client.screen instanceof WidgetsListScreen)) -> { client.player.containerMenu = containerScreenHandler; switch (client.screen) { - case WidgetsConfigurationScreen screen -> screen.updateHandler(containerScreenHandler, nameLowercase); - case null, default -> client.setScreen(new WidgetsConfigurationScreen(containerScreenHandler, nameLowercase)); + case WidgetsListScreen screen -> screen.updateHandler(containerScreenHandler, name); + case null, default -> client.setScreen(new WidgetsListScreen(containerScreenHandler, name)); } ci.cancel(); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/ItemPickupWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/ItemPickupWidget.java index 8213ca14051..f54c86da736 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/ItemPickupWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/ItemPickupWidget.java @@ -11,7 +11,6 @@ import de.hysky.skyblocker.utils.FlexibleItemStack; import de.hysky.skyblocker.utils.Formatters; import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.NEURepoManager; import de.hysky.skyblocker.utils.scheduler.Scheduler; import io.github.moulberry.repo.data.NEUItem; @@ -27,7 +26,6 @@ import org.jspecify.annotations.Nullable; import java.util.Objects; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -49,7 +47,7 @@ public class ItemPickupWidget extends ElementBasedWidget { private final Object2ObjectOpenHashMap removedSackCount = new Object2ObjectOpenHashMap<>(); public ItemPickupWidget() { - super(Component.literal("Items"), ChatFormatting.AQUA.getColor(), "Item Pickup"); + super(Component.literal("Items"), ChatFormatting.AQUA.getColor(), new Information("item_pickup", Component.literal("Item Pickup"))); instance = this; ClientReceiveMessageEvents.ALLOW_GAME.register(instance::onChatMessage); @@ -137,7 +135,7 @@ public void updateContent() { } boolean split = SkyblockerConfigManager.get().uiAndVisuals.itemPickup.splitNotifications; if (split && !(this.addedSackCount.isEmpty() && this.removedSackCount.isEmpty())) { - this.addComponent(new SeparatorElement(Component.nullToEmpty("Sacks"))); + this.addElement(new SeparatorElement(Component.nullToEmpty("Sacks"))); for (String item : addedSackCount.keySet()) { ChangeData entry = addedSackCount.get(item); String itemName = checkNextItem(entry); @@ -175,30 +173,8 @@ public void updateContent() { } @Override - public boolean shouldRender(Location location) { - if (super.shouldRender(location)) { - //render if enabled - if (SkyblockerConfigManager.get().uiAndVisuals.itemPickup.enabled) { - //render if there are items in history - return !addedCount.isEmpty() || !removedCount.isEmpty() || !addedSackCount.isEmpty() || !removedSackCount.isEmpty(); - } - } - return false; - } - - @Override - public Set availableLocations() { - return ALL_LOCATIONS; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - SkyblockerConfigManager.update(config -> config.uiAndVisuals.itemPickup.enabled = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - return SkyblockerConfigManager.get().uiAndVisuals.itemPickup.enabled; + public boolean shouldRender() { + return !addedCount.isEmpty() || !removedCount.isEmpty() || !addedSackCount.isEmpty() || !removedSackCount.isEmpty(); } /** diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonSplitsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonSplitsWidget.java index 2c026cec431..722bb40e3e3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonSplitsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonSplitsWidget.java @@ -1,26 +1,25 @@ package de.hysky.skyblocker.skyblock.dungeon; +import com.mojang.serialization.Codec; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.RegisterWidget; -import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.DungeonEvents; import de.hysky.skyblocker.events.SkyblockEvents; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.skyblock.tabhud.widget.TableWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Element; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; +import de.hysky.skyblocker.utils.CodecUtils; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; -import de.hysky.skyblocker.utils.CodecUtils; import de.hysky.skyblocker.utils.data.ProfiledData; -import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import com.mojang.serialization.Codec; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import org.jspecify.annotations.Nullable; @@ -30,7 +29,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -174,9 +172,6 @@ public class DungeonSplitsWidget extends TableWidget { private static final Codec>> BEST_CODEC = CodecUtils.object2ObjectMapCodec(Codec.STRING, CodecUtils.object2LongMapCodec(Codec.STRING)); private static final ProfiledData>> BEST_SPLITS = new ProfiledData<>(BEST_FILE, BEST_CODEC); - - private static final Set AVAILABLE_LOCATIONS = Set.of(Location.DUNGEON); - private static @Nullable DungeonSplitsWidget instance; private final List splits = new ArrayList<>(); @@ -192,7 +187,7 @@ public class DungeonSplitsWidget extends TableWidget { public DungeonSplitsWidget() { super(Component.literal("Splits").withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD), - ChatFormatting.GOLD.getColor(), "Dungeon Splits", 3, 0, false); + ChatFormatting.GOLD.getColor(), 3, 0, false, new Information("dungeon_splits", Component.literal("Dungeon Splits"), Location.DUNGEON)); instance = this; BEST_SPLITS.init(); @@ -322,22 +317,6 @@ public boolean shouldUpdateBeforeRendering() { return true; } - @Override - public Set availableLocations() { - return AVAILABLE_LOCATIONS; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (location != Location.DUNGEON) return; - SkyblockerConfigManager.update(config -> config.dungeons.dungeonSplits = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - return location == Location.DUNGEON && SkyblockerConfigManager.get().dungeons.dungeonSplits; - } - @Override public void updateContent() { if (!(Minecraft.getInstance().screen instanceof WidgetsConfigurationScreen)) { @@ -345,12 +324,12 @@ public void updateContent() { loadFloorSplits(); } - addComponent(new PlainTextElement(Component.literal("Floor: " + floor))); + addElement(new PlainTextElement(Component.literal("Floor: " + floor))); super.updateContent(); long now = running ? System.currentTimeMillis() - startTime : (startTime == 0L ? 0L : elapsedTime); - addComponent(new PlainTextElement(Component.literal(formatTime(now)).withStyle(timerColor))); + addElement(new PlainTextElement(Component.literal(formatTime(now)).withStyle(timerColor))); } @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TerminalHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TerminalHud.java index 86ab025dc72..237600d1874 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TerminalHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TerminalHud.java @@ -8,7 +8,6 @@ import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; import de.hysky.skyblocker.utils.FunUtils; import de.hysky.skyblocker.utils.Location; -import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -19,43 +18,23 @@ import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.function.Supplier; @RegisterWidget public class TerminalHud extends ElementBasedWidget { - private static final Set AVAILABLE_LOCATIONS = Set.of(Location.DUNGEON); private static final Supplier CONFIG = () -> SkyblockerConfigManager.get().dungeons.terminalHud; private static final Minecraft CLIENT = Minecraft.getInstance(); public static TerminalHud INSTANCE; public TerminalHud() { super(FunUtils.shouldEnableFun() ? Component.literal("P3 Guide") : Component.literal("Goldor Tasks"), - CommonColors.RED, "terminal_hud"); + CommonColors.RED, new Information("terminal_hud", Component.literal("Terminal HUD"), Location.DUNGEON)); INSTANCE = this; Scheduler.INSTANCE.scheduleCyclic(this::updateFromScheduler, 50); } @Override - public Set availableLocations() { - return AVAILABLE_LOCATIONS; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!AVAILABLE_LOCATIONS.contains(location)) return; - SkyblockerConfigManager.update(config -> config.dungeons.terminalHud.enableTerminalHud = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - if (!AVAILABLE_LOCATIONS.contains(location)) return false; - return CONFIG.get().enableTerminalHud; - } - - @Override - public boolean shouldRender(Location location) { - if (!super.shouldRender(location)) return false; + public boolean shouldRender() { return GoldorWaypointsManager.isActive(); } @@ -80,7 +59,7 @@ private Component getStatusText(GoldorWaypointsManager.GoldorWaypoint waypoint) public void updateFromScheduler() { if (CLIENT.screen instanceof WidgetsConfigurationScreen && !GoldorWaypointsManager.isActive()) update(); - if (!GoldorWaypointsManager.isActive() || !shouldRender(Utils.getLocation())) return; + if (!GoldorWaypointsManager.isActive() || !shouldRender()) return; update(); } @@ -93,18 +72,18 @@ public void updateContent() { } if (CONFIG.get().showTerminals) { for (int i = 0; i < 5; i++) { - addComponent(new PlainTextElement(Component.literal("Terminal #" + (i + 1)).append(status))); + addElement(new PlainTextElement(Component.literal("Terminal #" + (i + 1)).append(status))); } } if (CONFIG.get().showDevice) { - addComponent(new PlainTextElement(Component.literal("Device").append(status))); + addElement(new PlainTextElement(Component.literal("Device").append(status))); } if (CONFIG.get().showLevers) { - addComponent(new PlainTextElement(Component.literal("Lever").append(status))); - addComponent(new PlainTextElement(Component.literal("Lever").append(status))); + addElement(new PlainTextElement(Component.literal("Lever").append(status))); + addElement(new PlainTextElement(Component.literal("Lever").append(status))); } if (CONFIG.get().showGate) { - addComponent(new PlainTextElement(Component.literal("Gate").append(status))); + addElement(new PlainTextElement(Component.literal("Gate").append(status))); } return; } @@ -128,7 +107,7 @@ public void updateContent() { displayText = waypoint.name; } - addComponent(new PlainTextElement(displayText)); + addElement(new PlainTextElement(displayText)); } if (CONFIG.get().showGate && GoldorWaypointsManager.getCurrentPhase() < 3) { @@ -143,12 +122,7 @@ public void updateContent() { } } - addComponent(new PlainTextElement(displayText)); + addElement(new PlainTextElement(displayText)); } } - - @Override - public Component getDisplayName() { - return Component.literal("Goldor Tasks"); - } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudWidget.java index 20fc1d92415..44fe1366ee1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudWidget.java @@ -10,7 +10,6 @@ import org.joml.Vector2ic; import java.util.List; -import java.util.Set; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.renderer.RenderPipelines; @@ -25,7 +24,6 @@ public class CrystalsHudWidget extends HudWidget { protected static final Identifier MAP_TEXTURE = SkyblockerMod.id("textures/gui/crystals_map.png"); private static final Identifier MAP_ICON = Identifier.withDefaultNamespace("textures/map/decorations/player.png"); private static final List SMALL_LOCATIONS = List.of("Fairy Grotto", "King Yolkar", "Corleone", "Odawa", "Key Guardian", "Xalx", "Unknown"); - private static final Set AVAILABLE_LOCATIONS = Set.of(Location.CRYSTAL_HOLLOWS); private static @Nullable CrystalsHudWidget instance = null; @@ -36,7 +34,7 @@ public static CrystalsHudWidget getInstance() { } public CrystalsHudWidget() { - super("hud_crystals"); + super(new Information("hud_crystals", Component.literal("Crystals HUD"), Location.CRYSTAL_HOLLOWS)); instance = this; } @@ -70,22 +68,6 @@ private static float yaw2Cardinal(float yaw) { return (clipped * 360f) / 16f; } - @Override - public Set availableLocations() { - return AVAILABLE_LOCATIONS; - } - - @Override - public boolean isEnabledIn(Location location) { - return location.equals(Location.CRYSTAL_HOLLOWS) && SkyblockerConfigManager.get().mining.crystalsHud.enabled; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!location.equals(Location.CRYSTAL_HOLLOWS)) return; - SkyblockerConfigManager.update(config -> config.mining.crystalsHud.enabled = enabled); - } - public void update() { if (CLIENT.player == null || CLIENT.getConnection() == null || !SkyblockerConfigManager.get().mining.crystalsHud.enabled) return; @@ -96,14 +78,13 @@ public void update() { } @Override - public void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, float delta) { float scale = SkyblockerConfigManager.get().mining.crystalsHud.mapScaling; //make sure the map renders infront of some stuff - improve this in the future with better layering (1.20.5?) //and set position and scale Matrix3x2fStack matrices = graphics.pose(); matrices.pushMatrix(); - matrices.translate(x, y); matrices.scale(scale, scale); w = h = (int) (62 * scale); @@ -152,7 +133,7 @@ public void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, } @Override - public Component getDisplayName() { - return Component.nullToEmpty("Crystals HUD"); + protected void extractWidgetRenderStateForConfig(GuiGraphicsExtractor graphics, float delta) { + extractWidgetRenderState(graphics, delta); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHudWidget.java index 94e0e219722..24c34c57a4d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHudWidget.java @@ -1,25 +1,25 @@ package de.hysky.skyblocker.skyblock.dwarven; import de.hysky.skyblocker.annotations.RegisterWidget; -import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.tabhud.widget.ElementBasedWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; import de.hysky.skyblocker.utils.Location; -import org.jspecify.annotations.Nullable; - -import java.util.Set; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import org.jspecify.annotations.Nullable; + +import java.util.EnumSet; +import java.util.Set; @RegisterWidget public class PickobulusHudWidget extends ElementBasedWidget { private static final MutableComponent TITLE = Component.literal("Pickobulus").withStyle(ChatFormatting.BLUE, ChatFormatting.BOLD); - private static final Set AVAILABLE_LOCATIONS = Set.of(Location.GOLD_MINE, Location.DEEP_CAVERNS, Location.DWARVEN_MINES, Location.CRYSTAL_HOLLOWS, Location.GLACITE_MINESHAFTS); + private static final Set AVAILABLE_LOCATIONS = EnumSet.of(Location.GOLD_MINE, Location.DEEP_CAVERNS, Location.DWARVEN_MINES, Location.CRYSTAL_HOLLOWS, Location.GLACITE_MINESHAFTS); private static @Nullable PickobulusHudWidget instance; public PickobulusHudWidget() { - super(TITLE, ChatFormatting.BLUE.getColor(), "hud_pickobulus"); + super(TITLE, ChatFormatting.BLUE.getColor(), new Information("hud_pickobulus", Component.literal("Pickobulus HUD"), AVAILABLE_LOCATIONS)); instance = this; update(); } @@ -39,44 +39,23 @@ public boolean shouldUpdateBeforeRendering() { public void updateContent() { Component errorMessage = PickobulusHelper.getErrorMessage(); if (errorMessage != null) { - addComponent(new PlainTextElement(errorMessage)); + addElement(new PlainTextElement(errorMessage)); return; } - addComponent(new PlainTextElement(Component.literal("Total Blocks: " + PickobulusHelper.getTotalBlocks()))); + addElement(new PlainTextElement(Component.literal("Total Blocks: " + PickobulusHelper.getTotalBlocks()))); int[] drops = PickobulusHelper.getDrops(); for (PickobulusHelper.MiningDrop drop : PickobulusHelper.MiningDrop.values()) { int count = drops[drop.ordinal()]; if (count > 0) { - addComponent(new PlainTextElement(Component.literal(drop.friendlyName() + ": " + count))); + addElement(new PlainTextElement(Component.literal(drop.friendlyName() + ": " + count))); } } } @Override - public Set availableLocations() { - return AVAILABLE_LOCATIONS; - } - - @Override - public boolean shouldRender(Location location) { - return super.shouldRender(location) && PickobulusHelper.shouldRender(); - } - - @Override - public boolean isEnabledIn(Location location) { - return AVAILABLE_LOCATIONS.contains(location) && SkyblockerConfigManager.get().mining.pickobulusHelper.enablePickobulusHud; - } - - @Override - public Component getDisplayName() { - return Component.translatable("skyblocker.config.mining.pickobulusHelper"); - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!AVAILABLE_LOCATIONS.contains(location)) return; - SkyblockerConfigManager.update(config -> config.mining.pickobulusHelper.enablePickobulusHud = enabled); + public boolean shouldRender() { + return PickobulusHelper.shouldRender(); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/profittrackers/PowderMiningWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/profittrackers/PowderMiningWidget.java index 599814459b8..87a0356761c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/profittrackers/PowderMiningWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/profittrackers/PowderMiningWidget.java @@ -1,12 +1,10 @@ package de.hysky.skyblocker.skyblock.dwarven.profittrackers; import de.hysky.skyblocker.annotations.RegisterWidget; -import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import de.hysky.skyblocker.utils.Formatters; import de.hysky.skyblocker.utils.Location; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import java.util.Set; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; @@ -16,61 +14,40 @@ @RegisterWidget public class PowderMiningWidget extends HudWidget { private static final Minecraft CLIENT = Minecraft.getInstance(); - private static final Set LOCATIONS = Set.of(Location.CRYSTAL_HOLLOWS); public PowderMiningWidget() { - super("powder_mining_tracker"); + super(new Information("powder_mining_tracker", Component.translatable("skyblocker.powderTracker"), Location.CRYSTAL_HOLLOWS)); } @Override - public void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, float delta) { var set = PowderMiningTracker.getShownRewards().object2IntEntrySet(); if (set.isEmpty()) { - setDimensions(0, 0); + w = h = 0; return; } - int tempY = y; + int tempY = 0; int maxWidth = 0; for (Object2IntMap.Entry entry : set) { Component price = Component.literal(Formatters.INTEGER_NUMBERS.format(entry.getIntValue())).withColor(CommonColors.WHITE); Component text = entry.getKey().copy().append(" ").append(price); - graphics.text(CLIENT.font, text, x, tempY, CommonColors.WHITE); + graphics.text(CLIENT.font, text, 0, tempY, CommonColors.WHITE); tempY += 10; int width = CLIENT.font.width(text); if (width > maxWidth) maxWidth = width; } tempY += 10; - graphics.text(CLIENT.font, Component.translatable("skyblocker.powderTracker.profit", Formatters.DOUBLE_NUMBERS.format(PowderMiningTracker.getProfit())).withStyle(ChatFormatting.GOLD), x, tempY, CommonColors.WHITE); + graphics.text(CLIENT.font, Component.translatable("skyblocker.powderTracker.profit", Formatters.DOUBLE_NUMBERS.format(PowderMiningTracker.getProfit())).withStyle(ChatFormatting.GOLD), 0, tempY, CommonColors.WHITE); - setDimensions(maxWidth, tempY - y + 10); + w = maxWidth; + h = tempY + 10; } - - @Override - public Set availableLocations() { - return LOCATIONS; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!LOCATIONS.contains(location)) return; - SkyblockerConfigManager.update(config -> config.mining.crystalHollows.enablePowderTracker = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - if (!LOCATIONS.contains(location)) return false; - return SkyblockerConfigManager.get().mining.crystalHollows.enablePowderTracker; - } - - @Override - public void update() {} - @Override - public Component getDisplayName() { - return Component.translatable("skyblocker.powderTracker"); + protected void extractWidgetRenderStateForConfig(GuiGraphicsExtractor graphics, float delta) { + extractWidgetRenderState(graphics, delta); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java index 2dbb24a42c3..97c773af196 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java @@ -18,12 +18,10 @@ import org.jspecify.annotations.Nullable; import java.util.Objects; -import java.util.Set; @RegisterWidget public class EndHudWidget extends ElementBasedWidget { private static final MutableComponent TITLE = Component.literal("The End").withStyle(ChatFormatting.LIGHT_PURPLE, ChatFormatting.BOLD); - private static final Set AVAILABLE_LOCATIONS = Set.of(Location.THE_END); private static @Nullable EndHudWidget instance = null; @@ -31,7 +29,7 @@ public class EndHudWidget extends ElementBasedWidget { private static final FlexibleItemStack POPPY = Util.make(new FlexibleItemStack(Items.POPPY), stack -> stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true)); public EndHudWidget() { - super(TITLE, ChatFormatting.DARK_PURPLE.getColor(), "hud_end"); + super(TITLE, ChatFormatting.DARK_PURPLE.getColor(), new Information("hud_end", Component.literal("End Hud"), Location.THE_END)); instance = this; this.update(); } @@ -40,53 +38,32 @@ public static EndHudWidget getInstance() { return Objects.requireNonNull(instance, "EndHudWidget not initialized"); } - @Override - public boolean isEnabledIn(Location location) { - return location.equals(Location.THE_END) && SkyblockerConfigManager.get().otherLocations.end.hudEnabled; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!location.equals(Location.THE_END)) return; - SkyblockerConfigManager.update(config -> config.otherLocations.end.hudEnabled = enabled); - } - - @Override - public Set availableLocations() { - return AVAILABLE_LOCATIONS; - } - @Override public void updateContent() { // Zealots if (SkyblockerConfigManager.get().otherLocations.end.zealotKillsEnabled) { TheEnd.EndStats endStats = TheEnd.PROFILES_STATS.computeIfAbsent(TheEnd.EndStats.EMPTY); assert endStats != null; // remove warning, even though it can't be null... - addComponent(Elements.iconTextComponent(ENDERMAN_HEAD, Component.literal("Zealots").withStyle(ChatFormatting.BOLD))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.end.hud.zealotsSinceLastEye", endStats.zealotsSinceLastEye()))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.end.hud.zealotsTotalKills", Formatters.INTEGER_NUMBERS.format(endStats.totalZealotKills())))); + addElement(Elements.iconTextComponent(ENDERMAN_HEAD, Component.literal("Zealots").withStyle(ChatFormatting.BOLD))); + addElement(new PlainTextElement(Component.translatable("skyblocker.end.hud.zealotsSinceLastEye", endStats.zealotsSinceLastEye()))); + addElement(new PlainTextElement(Component.translatable("skyblocker.end.hud.zealotsTotalKills", Formatters.INTEGER_NUMBERS.format(endStats.totalZealotKills())))); String avg = endStats.eyes() == 0 ? "???" : Formatters.DOUBLE_NUMBERS.format((float) endStats.totalZealotKills() / endStats.eyes()); - addComponent(new PlainTextElement(Component.translatable("skyblocker.end.hud.avgKillsPerEye", avg))); + addElement(new PlainTextElement(Component.translatable("skyblocker.end.hud.avgKillsPerEye", avg))); } // Endstone protector if (SkyblockerConfigManager.get().otherLocations.end.protectorLocationEnabled) { - addComponent(Elements.iconTextComponent(POPPY, Component.literal("End Stone Protector").withStyle(ChatFormatting.BOLD))); + addElement(Elements.iconTextComponent(POPPY, Component.literal("End Stone Protector").withStyle(ChatFormatting.BOLD))); if (TheEnd.stage == 5) { - addComponent(new PlainTextElement(Component.translatable("skyblocker.end.hud.stage", "IMMINENT"))); + addElement(new PlainTextElement(Component.translatable("skyblocker.end.hud.stage", "IMMINENT"))); } else { - addComponent(new PlainTextElement(Component.translatable("skyblocker.end.hud.stage", String.valueOf(TheEnd.stage)))); + addElement(new PlainTextElement(Component.translatable("skyblocker.end.hud.stage", String.valueOf(TheEnd.stage)))); } if (TheEnd.currentProtectorLocation == null) { - addComponent(new PlainTextElement(Component.translatable("skyblocker.end.hud.location", "?"))); + addElement(new PlainTextElement(Component.translatable("skyblocker.end.hud.location", "?"))); } else { - addComponent(new PlainTextElement(Component.translatable("skyblocker.end.hud.location", TheEnd.currentProtectorLocation.name()))); + addElement(new PlainTextElement(Component.translatable("skyblocker.end.hud.location", TheEnd.currentProtectorLocation.name()))); } } } - - @Override - public Component getDisplayName() { - return Component.literal("End Hud"); - } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fishing/FishingHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/fishing/FishingHudWidget.java index 354bddfbc0e..04331ef2204 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/fishing/FishingHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/fishing/FishingHudWidget.java @@ -15,15 +15,14 @@ import de.hysky.skyblocker.utils.time.SkyblockTime; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.objects.ObjectFloatPair; - -import java.util.Objects; -import java.util.Set; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; +import java.util.Objects; + @RegisterWidget public class FishingHudWidget extends ElementBasedWidget { private static final Minecraft CLIENT = Minecraft.getInstance(); @@ -36,7 +35,7 @@ public static FishingHudWidget getInstance() { } public FishingHudWidget() { - super(Component.literal("Fishing").withStyle(ChatFormatting.DARK_AQUA, ChatFormatting.BOLD), ChatFormatting.DARK_AQUA.getColor(), "hud_fishing"); + super(Component.literal("Fishing").withStyle(ChatFormatting.DARK_AQUA, ChatFormatting.BOLD), ChatFormatting.DARK_AQUA.getColor(), new Information("hud_fishing", Component.literal("Fishing HUD"))); instance = this; } @@ -46,25 +45,7 @@ public boolean shouldUpdateBeforeRendering() { } @Override - public Set availableLocations() { - return ALL_LOCATIONS; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - SkyblockerConfigManager.update(config -> config.helpers.fishing.enableFishingHud = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - return SkyblockerConfigManager.get().helpers.fishing.enableFishingHud; - } - - @Override - public boolean shouldRender(Location location) { - if (!super.shouldRender(location)) { - return false; - } + public boolean shouldRender() { // sea creature tracker if (SkyblockerConfigManager.get().helpers.fishing.enableSeaCreatureCounter && SeaCreatureTracker.isCreaturesAlive()) { if (Utils.getLocation() == Location.HUB && SkyblockerConfigManager.get().helpers.fishing.onlyShowHudInBarn) { @@ -87,8 +68,8 @@ public boolean shouldRender(Location location) { @Override public void updateContent() { if (Minecraft.getInstance().screen instanceof WidgetsConfigurationScreen) { - addComponent(Elements.progressComponent(Ico.SALMON_BUCKET, Component.nullToEmpty("Alive Creatures"), Component.nullToEmpty("3/5"), 60, ColorUtils.percentToColor(40))); - addComponent(Elements.progressComponent(Ico.CLOCK, Component.nullToEmpty("Time Left"), Component.nullToEmpty("1m"), 60f / SkyblockerConfigManager.get().helpers.fishing.timerLength * 100)); + addElement(Elements.progressComponent(Ico.SALMON_BUCKET, Component.nullToEmpty("Alive Creatures"), Component.nullToEmpty("3/5"), 60, ColorUtils.percentToColor(40))); + addElement(Elements.progressComponent(Ico.CLOCK, Component.nullToEmpty("Time Left"), Component.nullToEmpty("1m"), 60f / SkyblockerConfigManager.get().helpers.fishing.timerLength * 100)); return; } //creature counter @@ -97,8 +78,8 @@ public void updateContent() { ObjectFloatPair timer = SeaCreatureTracker.getTimerText(SeaCreatureTracker.getOldestSeaCreatureAge()); int seaCreatureCap = SeaCreatureTracker.SEA_CREATURE_CAP; float seaCreaturePercent = (float) SeaCreatureTracker.seaCreatureCount() / seaCreatureCap * 100; - addComponent(Elements.progressComponent(Ico.TROPICAL_FISH_BUCKET, Component.nullToEmpty("Alive Creatures"), Component.nullToEmpty(SeaCreatureTracker.seaCreatureCount() + "/" + seaCreatureCap), seaCreaturePercent, ColorUtils.percentToColor(100 - seaCreaturePercent))); - addComponent(Elements.progressComponent(Ico.CLOCK, Component.nullToEmpty("Time Left"), timer.left(), timer.rightFloat())); + addElement(Elements.progressComponent(Ico.TROPICAL_FISH_BUCKET, Component.nullToEmpty("Alive Creatures"), Component.nullToEmpty(SeaCreatureTracker.seaCreatureCount() + "/" + seaCreatureCap), seaCreaturePercent, ColorUtils.percentToColor(100 - seaCreaturePercent))); + addElement(Elements.progressComponent(Ico.CLOCK, Component.nullToEmpty("Time Left"), timer.left(), timer.rightFloat())); } //bobber timer if (SkyblockerConfigManager.get().helpers.fishing.enableFishingTimer && FishingHelper.startTime != 0) { @@ -112,7 +93,7 @@ public void updateContent() { maxTime = 20; } time = Math.clamp(time, 0, maxTime); - addComponent(Elements.progressComponent(Ico.CLOCK, Component.nullToEmpty("Bobber Time"), SkyblockTime.formatTime(maxTime - time), 100 - (time / maxTime) * 100)); + addElement(Elements.progressComponent(Ico.CLOCK, Component.nullToEmpty("Bobber Time"), SkyblockTime.formatTime(maxTime - time), 100 - (time / maxTime) * 100)); } // rod reel timer if (SkyblockerConfigManager.get().helpers.fishing.fishingHookDisplay == HelperConfig.Fishing.FishingHookDisplay.HUD && FishingHookDisplayHelper.fishingHookArmorStand != null) { @@ -122,11 +103,6 @@ public void updateContent() { } - @Override - public Component getDisplayName() { - return Component.literal("Fishing Hud"); - } - private static boolean isBarnFishing() { return CLIENT.player != null && CLIENT.player.distanceToSqr(BARN_LOCATION) < 2500; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/galatea/SweepDetailsHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/galatea/SweepDetailsHudWidget.java index 7b649015309..7e93eecd033 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/galatea/SweepDetailsHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/galatea/SweepDetailsHudWidget.java @@ -1,7 +1,6 @@ package de.hysky.skyblocker.skyblock.galatea; import de.hysky.skyblocker.annotations.RegisterWidget; -import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; @@ -13,14 +12,16 @@ import de.hysky.skyblocker.utils.Formatters; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; -import java.util.Map; -import java.util.Set; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.util.CommonColors; import net.minecraft.world.item.Items; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; + @RegisterWidget public class SweepDetailsHudWidget extends ElementBasedWidget { private static final Minecraft CLIENT = Minecraft.getInstance(); @@ -34,28 +35,27 @@ public class SweepDetailsHudWidget extends ElementBasedWidget { "Birch", new FlexibleItemStack(Items.BIRCH_LOG), "Oak", new FlexibleItemStack(Items.OAK_LOG) ); - public static final Set LOCATIONS = Set.of(Location.GALATEA, Location.HUB, Location.THE_PARK, Location.GARDEN); + public static final Set LOCATIONS = EnumSet.of(Location.GALATEA, Location.HUB, Location.THE_PARK, Location.GARDEN); public SweepDetailsHudWidget() { - super(Component.translatable("skyblocker.galatea.hud.sweepDetails"), 0xFF6E37CC, "sweepDetails"); + super(Component.translatable("skyblocker.galatea.hud.sweepDetails"), 0xFF6E37CC, new Information("sweep_details", Component.literal("Sweep Details"), LOCATIONS)); update(); } @Override - public boolean shouldRender(Location location) { + public boolean shouldRender() { // While in the hub only show in the forest and foraging camp return (!Utils.getLocation().equals(Location.HUB) || Utils.getArea() == Area.Hub.FOREST || Utils.getArea() == Area.Hub.FORAGING_CAMP) // While in the garden only show in unclean plots - && (!Utils.getLocation().equals(Location.GARDEN) || Utils.STRING_SCOREBOARD.stream().anyMatch(s -> s.contains("Cleanup"))) - && super.shouldRender(location); + && (!Utils.getLocation().equals(Location.GARDEN) || Utils.STRING_SCOREBOARD.stream().anyMatch(s -> s.contains("Cleanup"))); } @Override public void updateContent() { if (CLIENT.player == null || CLIENT.screen instanceof WidgetsConfigurationScreen) { - addComponent(Elements.iconTextComponent(new FlexibleItemStack(Items.STRIPPED_SPRUCE_LOG), Component.translatable("skyblocker.galatea.hud.sweepDetails.treeType", "Fig"))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.toughness", 3.5))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.sweep", 314.15))); + addElement(Elements.iconTextComponent(new FlexibleItemStack(Items.STRIPPED_SPRUCE_LOG), Component.translatable("skyblocker.galatea.hud.sweepDetails.treeType", "Fig"))); + addElement(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.toughness", 3.5))); + addElement(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.sweep", 314.15))); return; } if (!SweepDetailsListener.active || System.currentTimeMillis() > SweepDetailsListener.lastMatch + 1_000) { @@ -66,13 +66,13 @@ public void updateContent() { case GALATEA -> ItemRepository.getItemStack("FIGSTONE_AXE", new FlexibleItemStack(Items.STONE_AXE)); default -> Ico.RED_CONCRETE; }; - addComponent(Elements.iconTextComponent(axeIcon, Component.translatable("skyblocker.galatea.hud.sweepDetails.inactive"))); + addElement(Elements.iconTextComponent(axeIcon, Component.translatable("skyblocker.galatea.hud.sweepDetails.inactive"))); return; } FlexibleItemStack logItem = LOG_TO_ITEM.getOrDefault(SweepDetailsListener.lastTreeType, Ico.RED_CONCRETE); - addComponent(Elements.iconTextComponent(logItem, Component.translatable("skyblocker.galatea.hud.sweepDetails.treeType", SweepDetailsListener.lastTreeType))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.toughness", SweepDetailsListener.toughness))); + addElement(Elements.iconTextComponent(logItem, Component.translatable("skyblocker.galatea.hud.sweepDetails.treeType", SweepDetailsListener.lastTreeType))); + addElement(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.toughness", SweepDetailsListener.toughness))); Component sweepAmount; if (SweepDetailsListener.maxSweep > SweepDetailsListener.lastSweep) { @@ -82,42 +82,20 @@ public void updateContent() { } else { sweepAmount = Component.literal(Formatters.DOUBLE_NUMBERS.format(SweepDetailsListener.maxSweep)).withColor(CommonColors.GREEN); } - addComponent(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.sweep", sweepAmount))); + addElement(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.sweep", sweepAmount))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.logs", Component.literal(SweepDetailsListener.logs).withColor(CommonColors.GREEN)))); + addElement(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.logs", Component.literal(SweepDetailsListener.logs).withColor(CommonColors.GREEN)))); if (SweepDetailsListener.axePenalty) { - addComponent(Elements.iconTextComponent(Ico.BARRIER, Component.translatable("skyblocker.galatea.hud.sweepDetails.throwPenalty", SweepDetailsListener.axePenaltyAmount + "%"))); + addElement(Elements.iconTextComponent(Ico.BARRIER, Component.translatable("skyblocker.galatea.hud.sweepDetails.throwPenalty", SweepDetailsListener.axePenaltyAmount + "%"))); } if (SweepDetailsListener.stylePenalty) { - addComponent(Elements.iconTextComponent(Ico.BARRIER, Component.translatable("skyblocker.galatea.hud.sweepDetails.stylePenalty", SweepDetailsListener.stylePenaltyAmount + "%"))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.correctStyle", SweepDetailsListener.correctStyle))); + addElement(Elements.iconTextComponent(Ico.BARRIER, Component.translatable("skyblocker.galatea.hud.sweepDetails.stylePenalty", SweepDetailsListener.stylePenaltyAmount + "%"))); + addElement(new PlainTextElement(Component.translatable("skyblocker.galatea.hud.sweepDetails.correctStyle", SweepDetailsListener.correctStyle))); } } - @Override - public Set availableLocations() { - return LOCATIONS; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!availableLocations().contains(location)) return; - SkyblockerConfigManager.update(config -> config.foraging.galatea.enableSweepDetailsWidget = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - if (!availableLocations().contains(location)) return false; - return SkyblockerConfigManager.get().foraging.galatea.enableSweepDetailsWidget; - } - - @Override - public Component getDisplayName() { - return Component.translatable("skyblocker.galatea.hud.sweepDetails"); - } - @Override public boolean shouldUpdateBeforeRendering() { return true; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/galatea/TreeBreakProgressHud.java b/src/main/java/de/hysky/skyblocker/skyblock/galatea/TreeBreakProgressHud.java index 799b3c61a01..185ba050656 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/galatea/TreeBreakProgressHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/galatea/TreeBreakProgressHud.java @@ -1,7 +1,6 @@ package de.hysky.skyblocker.skyblock.galatea; import de.hysky.skyblocker.annotations.RegisterWidget; -import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.widget.ElementBasedWidget; @@ -21,13 +20,11 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; -import java.util.Set; @RegisterWidget public class TreeBreakProgressHud extends ElementBasedWidget { private static final Minecraft CLIENT = Minecraft.getInstance(); - private static final Set AVAILABLE_LOCATIONS = Set.of(Location.GALATEA); private static @Nullable TreeBreakProgressHud instance; private static final Int2ObjectMap armorstands = new Int2ObjectOpenHashMap<>(); @@ -36,7 +33,7 @@ public class TreeBreakProgressHud extends ElementBasedWidget { } public TreeBreakProgressHud() { - super(Component.literal("Tree Break Progress").withStyle(ChatFormatting.GREEN, ChatFormatting.BOLD), ChatFormatting.GREEN.getColor(), "hud_treeprogress"); + super(Component.literal("Tree Break Progress").withStyle(ChatFormatting.GREEN, ChatFormatting.BOLD), ChatFormatting.GREEN.getColor(), new Information("hud_treeprogress", Component.literal("Tree Break Progress"), Location.GALATEA)); instance = this; update(); } @@ -57,25 +54,8 @@ public static TreeBreakProgressHud getInstance() { } @Override - public Set availableLocations() { - return AVAILABLE_LOCATIONS; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!availableLocations().contains(location)) - return; - SkyblockerConfigManager.update(config -> config.foraging.galatea.enableTreeBreakProgress = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - return availableLocations().contains(location) && SkyblockerConfigManager.get().foraging.galatea.enableTreeBreakProgress; - } - - @Override - public boolean shouldRender(Location location) { - return super.shouldRender(location) && isOwnTree(getClosestTree()); + public boolean shouldRender() { + return isOwnTree(getClosestTree()); } private @Nullable ArmorStand getClosestTree() { @@ -132,9 +112,4 @@ public void updateContent() { addSimpleIcoText(woodIcon, treeName + " ", ChatFormatting.GREEN, closestName.replaceAll("[^0-9%]", "")); } - @Override - public Component getDisplayName() { - return Component.literal("Tree Break Progress HUD"); - } - } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java index f903e7520b5..1fe53fe42b1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java @@ -1,19 +1,15 @@ package de.hysky.skyblocker.skyblock.garden; -import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.WorldEvents; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; -import de.hysky.skyblocker.utils.scheduler.Scheduler; import it.unimi.dsi.fastutil.floats.FloatLongPair; import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongLongPair; import it.unimi.dsi.fastutil.longs.LongPriorityQueue; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents; @@ -35,7 +31,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommands.literal; public class FarmingHud { private static final Logger LOGGER = LoggerFactory.getLogger(FarmingHud.class); @@ -104,8 +99,6 @@ public static void init() { return true; }); - ClientCommandRegistrationCallback.EVENT.register((dispatcher, _) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("hud").then(literal("farming") - .executes(Scheduler.queueOpenScreenCommand(() -> new WidgetsConfigurationScreen(Location.GARDEN, "hud_garden", null))))))); } @SuppressWarnings("SameParameterValue") 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 58014c9a167..50b22bc92fa 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java @@ -12,9 +12,6 @@ import de.hysky.skyblocker.utils.FlexibleItemStack; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; -import java.util.Map; -import java.util.OptionalDouble; -import java.util.Set; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; @@ -24,10 +21,12 @@ import net.minecraft.world.item.ItemStack; import org.jspecify.annotations.Nullable; +import java.util.Map; +import java.util.OptionalDouble; + @RegisterWidget public class FarmingHudWidget extends ElementBasedWidget { private static final MutableComponent TITLE = Component.literal("Farming").withStyle(ChatFormatting.YELLOW, ChatFormatting.BOLD); - private static final Set AVAILABLE_LOCATIONS = Set.of(Location.GARDEN); public static final Map FARMING_TOOLS = Map.ofEntries( Map.entry("THEORETICAL_HOE_WHEAT_1", "WHEAT"), Map.entry("THEORETICAL_HOE_WHEAT_2", "WHEAT"), @@ -81,7 +80,7 @@ public static FarmingHudWidget getInstance() { private final Minecraft client = Minecraft.getInstance(); public FarmingHudWidget() { - super(TITLE, ChatFormatting.YELLOW.getColor(), "hud_farming"); + super(TITLE, ChatFormatting.YELLOW.getColor(), new Information("hud_farming", Component.literal("Farming HUD"), Location.GARDEN)); instance = this; update(); } @@ -94,7 +93,7 @@ public boolean shouldUpdateBeforeRendering() { @Override public void updateContent() { if (client.player == null || client.level == null) { - addComponent(new PlainTextElement(Component.literal("Nothing to show :p"))); + addElement(new PlainTextElement(Component.literal("Nothing to show :p"))); return; } FarmingConfig.FarmingHud config = SkyblockerConfigManager.get().farming.farmingHud; @@ -123,17 +122,17 @@ public void updateContent() { addSimpleIconTranslatableText(cropStack, "skyblocker.farming.farmingHud.blocksPerSec", ChatFormatting.YELLOW, Double.toString(blockBreaks)); if (config.experience) { //noinspection DataFlowIssue - addComponent(Elements.progressComponent(Ico.LANTERN, Component.translatable("skyblocker.farming.farmingHud.farmingLevel"), FarmingHud.farmingXpPercentProgress(), ChatFormatting.GOLD.getColor())); + addElement(Elements.progressComponent(Ico.LANTERN, Component.translatable("skyblocker.farming.farmingHud.farmingLevel"), FarmingHud.farmingXpPercentProgress(), ChatFormatting.GOLD.getColor())); addSimpleIconTranslatableText(Ico.LIME_DYE, "skyblocker.farming.farmingHud.farmingXPPerHour", ChatFormatting.YELLOW, FarmingHud.NUMBER_FORMAT.format(FarmingHud.farmingXpPerHour())); } Entity cameraEntity = client.getCameraEntity(); Component yaw = cameraEntity == null ? Component.translatable("skyblocker.farming.farmingHud.noCameraEntity") : Component.literal(String.format("%.2f", Mth.wrapDegrees(cameraEntity.getYRot()))); Component pitch = cameraEntity == null ? Component.translatable("skyblocker.farming.farmingHud.noCameraEntity") : Component.literal(String.format("%.2f", Mth.wrapDegrees(cameraEntity.getXRot()))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.farming.farmingHud.yaw", yaw).withStyle(ChatFormatting.GOLD))); - addComponent(new PlainTextElement(Component.translatable("skyblocker.farming.farmingHud.pitch", pitch).withStyle(ChatFormatting.GOLD))); + addElement(new PlainTextElement(Component.translatable("skyblocker.farming.farmingHud.yaw", yaw).withStyle(ChatFormatting.GOLD))); + addElement(new PlainTextElement(Component.translatable("skyblocker.farming.farmingHud.pitch", pitch).withStyle(ChatFormatting.GOLD))); if (LowerSensitivity.isSensitivityLowered()) { - addComponent(new PlainTextElement(Component.translatable("skyblocker.garden.hud.mouseLocked").withStyle(ChatFormatting.ITALIC))); + addElement(new PlainTextElement(Component.translatable("skyblocker.garden.hud.mouseLocked").withStyle(ChatFormatting.ITALIC))); } } @@ -219,25 +218,4 @@ else if (bazaar.isPresent()) { // Multiply by 60 to convert to hourly and divide by 100 for rounding is combined into multiplying by 0.6. return hasValidPrice ? Component.literal(FarmingHud.NUMBER_FORMAT.format((int) (priceToUse * cropsPerMinute * 0.6) * 100)).append(sourceLabel) : Component.translatable("skyblocker.farming.farmingHud.noData"); } - - @Override - public boolean isEnabledIn(Location location) { - return location.equals(Location.GARDEN) && SkyblockerConfigManager.get().farming.farmingHud.enabled; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!location.equals(Location.GARDEN)) return; - SkyblockerConfigManager.update(config -> config.farming.farmingHud.enabled = enabled); - } - - @Override - public Set availableLocations() { - return AVAILABLE_LOCATIONS; - } - - @Override - public Component getDisplayName() { - return Component.literal("Farming HUD"); - } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/hunting/LassoHud.java b/src/main/java/de/hysky/skyblocker/skyblock/hunting/LassoHud.java index e85cab1a25f..a64f7884b48 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/hunting/LassoHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/hunting/LassoHud.java @@ -1,15 +1,11 @@ package de.hysky.skyblocker.skyblock.hunting; import de.hysky.skyblocker.annotations.RegisterWidget; -import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.widget.ElementBasedWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.utils.Location; -import de.hysky.skyblocker.utils.Utils; - -import java.util.Objects; -import java.util.Set; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; @@ -19,11 +15,12 @@ import net.minecraft.world.entity.player.Player; import org.jspecify.annotations.Nullable; +import java.util.Objects; + @RegisterWidget public class LassoHud extends ElementBasedWidget { private static final Minecraft CLIENT = Minecraft.getInstance(); private static final String LASSO_COUNT_DOWN_NAME = " "; - private static final Set AVAILABLE_LOCATION = Set.of(Location.GALATEA); private static @Nullable LassoHud instance; private static int percentage = 0; @@ -38,13 +35,13 @@ public static LassoHud getInstance() { } public LassoHud() { - super(Component.literal("Lasso").withStyle(ChatFormatting.DARK_AQUA, ChatFormatting.BOLD), ChatFormatting.DARK_AQUA.getColor(), "Lasso HUD"); + super(Component.literal("Lasso").withStyle(ChatFormatting.DARK_AQUA, ChatFormatting.BOLD), ChatFormatting.DARK_AQUA.getColor(), new Information("hud_lasso", Component.literal("Lasso HUD"), Location.GALATEA)); instance = this; } public static void onEntityUpdate(ArmorStand entity) { //check to see if close to end of players lasso - if (!getInstance().isEnabledIn(Utils.getLocation()) || lassoEntity == null || entity.distanceToSqr(lassoEntity) > 16) return; + if (!WidgetManager.isWidgetInCurrentScreen(getInstance()) || lassoEntity == null || entity.distanceToSqr(lassoEntity) > 16) return; //see if it's the name we are looking for Component name = entity.getCustomName(); @@ -58,7 +55,7 @@ public static void onEntityUpdate(ArmorStand entity) { } public static void onEntityAttach(ClientboundSetEntityLinkPacket packet) { - if (!getInstance().isEnabledIn(Utils.getLocation()) || CLIENT.level == null) return; + if (!WidgetManager.isWidgetInCurrentScreen(getInstance()) || CLIENT.level == null) return; //see if lasso is coming from this player if (CLIENT.level.getEntity(packet.getDestId()) instanceof Player player) { if (player.equals(CLIENT.player)) { @@ -90,37 +87,20 @@ public boolean shouldUpdateBeforeRendering() { public void updateContent() { //if 0 percent now otherwise wait if (percentage == 0) { - addComponent(Elements.progressComponent(Ico.LEAD, Component.translatable("skyblocker.config.hunting.lassoHud.reel"), Component.translatable("skyblocker.config.hunting.lassoHud.now").withStyle(ChatFormatting.GREEN), percentage)); + addElement(Elements.progressComponent(Ico.LEAD, Component.translatable("skyblocker.config.hunting.lassoHud.reel"), Component.translatable("skyblocker.config.hunting.lassoHud.now").withStyle(ChatFormatting.GREEN), percentage)); return; } - addComponent(Elements.progressComponent(Ico.LEAD, Component.translatable("skyblocker.config.hunting.lassoHud.reel"), Component.translatable("skyblocker.config.hunting.lassoHud.wait"), percentage)); + addElement(Elements.progressComponent(Ico.LEAD, Component.translatable("skyblocker.config.hunting.lassoHud.reel"), Component.translatable("skyblocker.config.hunting.lassoHud.wait"), percentage)); } @Override - public boolean shouldRender(Location location) { + public boolean shouldRender() { //forget entity if it has died if (lassoEntity != null && !lassoEntity.isAlive()) { lassoEntity = null; } - return percentage != -1 && lassoEntity != null && super.shouldRender(location); - } - - @Override - public Set availableLocations() { - return AVAILABLE_LOCATION; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!AVAILABLE_LOCATION.contains(location)) return; - SkyblockerConfigManager.update(config -> config.hunting.lassoHud.enabled = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - if (!AVAILABLE_LOCATION.contains(location)) return false; - return SkyblockerConfigManager.get().hunting.lassoHud.enabled; + return percentage != -1 && lassoEntity != null; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerHudWidget.java index 59e98e957a0..aeab664f97d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/slayers/SlayerHudWidget.java @@ -1,7 +1,6 @@ package de.hysky.skyblocker.skyblock.slayers; import de.hysky.skyblocker.annotations.RegisterWidget; -import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.widget.ElementBasedWidget; @@ -14,18 +13,19 @@ import net.minecraft.network.chat.Component; import org.jspecify.annotations.Nullable; +import java.util.EnumSet; import java.util.Objects; import java.util.Set; @RegisterWidget public class SlayerHudWidget extends ElementBasedWidget { - private static final Set AVAILABLE_LOCATIONS = Set.of(Location.CRIMSON_ISLE, Location.HUB, Location.SPIDERS_DEN, Location.THE_END, Location.THE_PARK, Location.THE_RIFT); + private static final Set AVAILABLE_LOCATIONS = EnumSet.of(Location.CRIMSON_ISLE, Location.HUB, Location.SPIDERS_DEN, Location.THE_END, Location.THE_PARK, Location.THE_RIFT); private static final Minecraft CLIENT = Minecraft.getInstance(); private static final int TEXTURE_SIZE = 16; private static @Nullable SlayerHudWidget instance; public SlayerHudWidget() { - super(Component.literal("Slayer").withStyle(ChatFormatting.DARK_PURPLE, ChatFormatting.BOLD), ChatFormatting.DARK_PURPLE.getColor(), "hud_slayer"); + super(Component.literal("Slayer").withStyle(ChatFormatting.DARK_PURPLE, ChatFormatting.BOLD), ChatFormatting.DARK_PURPLE.getColor(), new Information("hud_slayer", Component.literal("Slayer HUD"), AVAILABLE_LOCATIONS)); instance = this; update(); } @@ -40,24 +40,8 @@ public static SlayerHudWidget getInstance() { } @Override - public Set availableLocations() { - return AVAILABLE_LOCATIONS; - } - - @Override - public void setEnabledIn(Location location, boolean enabled) { - if (!availableLocations().contains(location)) return; - SkyblockerConfigManager.update(config -> config.slayers.enableHud = enabled); - } - - @Override - public boolean isEnabledIn(Location location) { - return availableLocations().contains(location) && SkyblockerConfigManager.get().slayers.enableHud; - } - - @Override - public boolean shouldRender(Location location) { - return super.shouldRender(location) && SlayerManager.isInSlayerQuest(); + public boolean shouldRender() { + return SlayerManager.isInSlayerQuest(); } @Override @@ -67,9 +51,9 @@ public void updateContent() { SlayerTier slayerTier = SlayerTier.V; Component slayerName = Component.literal(slayerType.bossName + " " + slayerTier).withStyle(slayerTier.color); - addComponent(new TextureTextElement(slayerName, slayerType.texture, TEXTURE_SIZE, TEXTURE_SIZE)); + addElement(new TextureTextElement(slayerName, slayerType.texture, TEXTURE_SIZE, TEXTURE_SIZE)); addSimpleIcoText(Ico.EXPERIENCE_BOTTLE, "XP: ", ChatFormatting.LIGHT_PURPLE, "100,000/400,000"); - addComponent(Elements.iconTextComponent(Ico.NETHER_STAR, Component.translatable("skyblocker.slayer.hud.levelUpIn", Component.literal("200").withStyle(ChatFormatting.LIGHT_PURPLE)))); + addElement(Elements.iconTextComponent(Ico.NETHER_STAR, Component.translatable("skyblocker.slayer.hud.levelUpIn", Component.literal("200").withStyle(ChatFormatting.LIGHT_PURPLE)))); return; } @@ -82,23 +66,18 @@ public void updateContent() { int bossesNeeded = slayerQuest.bossesNeeded; Component slayerName = Component.literal(slayerType.bossName + " " + slayerTier).withStyle(slayerTier.color); - addComponent(new TextureTextElement(slayerName, slayerType.texture, TEXTURE_SIZE, TEXTURE_SIZE)); + addElement(new TextureTextElement(slayerName, slayerType.texture, TEXTURE_SIZE, TEXTURE_SIZE)); if (level == slayerType.maxLevel) { - addComponent(Elements.iconTextComponent(Ico.EXPERIENCE_BOTTLE, Component.literal("XP: ").append(Component.translatable("skyblocker.slayer.hud.levelMaxed").withStyle(ChatFormatting.GREEN)))); + addElement(Elements.iconTextComponent(Ico.EXPERIENCE_BOTTLE, Component.literal("XP: ").append(Component.translatable("skyblocker.slayer.hud.levelMaxed").withStyle(ChatFormatting.GREEN)))); } else if (level >= 0) { int nextMilestone = slayerType.levelMilestones[level]; int currentXP = nextMilestone - slayerQuest.xpRemaining; addSimpleIcoText(Ico.EXPERIENCE_BOTTLE, "XP: ", ChatFormatting.LIGHT_PURPLE, Formatters.INTEGER_NUMBERS.format(currentXP) + "/" + Formatters.INTEGER_NUMBERS.format(nextMilestone)); if (bossesNeeded > 0) { - addComponent(Elements.iconTextComponent(Ico.NETHER_STAR, Component.translatable("skyblocker.slayer.hud.levelUpIn", Component.literal(Formatters.INTEGER_NUMBERS.format(bossesNeeded)).withStyle(ChatFormatting.LIGHT_PURPLE)))); + addElement(Elements.iconTextComponent(Ico.NETHER_STAR, Component.translatable("skyblocker.slayer.hud.levelUpIn", Component.literal(Formatters.INTEGER_NUMBERS.format(bossesNeeded)).withStyle(ChatFormatting.LIGHT_PURPLE)))); } } } - - @Override - public Component getDisplayName() { - return Component.literal("Slayer Hud"); - } } 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 cb090826d64..4b97c14ad04 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/TabHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/TabHud.java @@ -4,13 +4,12 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; import de.hysky.skyblocker.utils.Utils; import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper; import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry; import net.fabricmc.fabric.api.client.rendering.v1.hud.VanillaHudElements; import net.minecraft.client.KeyMapping; -import net.minecraft.client.Minecraft; import org.lwjgl.glfw.GLFW; public class TabHud { @@ -31,7 +30,7 @@ public static void init() { SkyblockerMod.KEYBINDING_CATEGORY)); HudElementRegistry.replaceElement(VanillaHudElements.PLAYER_LIST, hudElement -> { - if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled || TabHud.shouldRenderVanilla() || Minecraft.getInstance().screen instanceof WidgetsConfigurationScreen) return hudElement; + if (!Utils.isOnSkyblock() || TabHud.shouldRenderVanilla() || !WidgetManager.hasFancyTab()) return hudElement; return (_, _) -> {}; }); } @@ -39,4 +38,8 @@ public static void init() { public static boolean shouldRenderVanilla() { return defaultTgl.isDown() != SkyblockerConfigManager.get().uiAndVisuals.tabHud.showVanillaTabByDefault; } + + public static float getScaleFactor() { + return SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudScale / 100f; + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/AddWidgetWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/AddWidgetWidget.java new file mode 100644 index 00000000000..224caa2bd06 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/AddWidgetWidget.java @@ -0,0 +1,127 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; +import java.util.Comparator; +import java.util.List; +import java.util.function.Consumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractSelectionList; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.util.ARGB; +import net.minecraft.util.CommonColors; + +public class AddWidgetWidget extends AbstractSelectionList { + + private final Consumer widgetConsumer; + private static final int MAX_ENTRIES = 10; + + public AddWidgetWidget(Minecraft client, Consumer widgetConsumer) { + super(client, 10, 10, 0, 12); + this.widgetConsumer = widgetConsumer; + visible = false; + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput builder) {} + + @Override + protected void extractListBackground(GuiGraphicsExtractor context) { + context.fill(getX(), getY(), getRight(), getBottom(), ARGB.color(100, 0)); + if (scrollAmount() > 0) { + for (int x = 0; x < this.getWidth(); x++) { + if (x % 2 == 0) { + context.fill(this.getX() + x, this.getY() - 1, this.getX() + x + 1, this.getY(), -1); + } + } + } + + if (scrollAmount() < maxScrollAmount()) { + for (int x = 0; x < this.getWidth(); x++) { + if (x % 2 == 0) { + context.fill( + this.getX() + x, this.getY() + this.getHeight(), this.getX() + x + 1, this.getY() + this.getHeight() + 1, -1 + ); + } + } + } + } + + @Override + public void extractWidgetRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks) { + super.extractWidgetRenderState(context, mouseX, mouseY, deltaTicks); + if (mouseX < getX() - 20 || mouseY < getY() - 20 || mouseX > getRight() + 20 || mouseY > getBottom() + 20) visible = false; + } + + @Override + protected void extractListSeparators(GuiGraphicsExtractor context) {} + + @Override + public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { + if (!visible) return false; + return super.mouseClicked(click, doubled); + } + + public void openWith(List widgets) { + visible = true; + replaceEntries(widgets.stream().sorted(Comparator.comparing(w -> w.getInformation().displayName().getString())).map(de.hysky.skyblocker.skyblock.tabhud.config.AddWidgetWidget.Entry::new).toList()); + setHeight(Math.min(widgets.size(), MAX_ENTRIES) * defaultEntryHeight); + setWidth(widgets.stream().mapToInt(entry -> minecraft.font.width(entry.getInformation().displayName())).max().orElse(100) + 3); + } + + @Override + public void setX(int x) { + super.setX(x); + } + + @Override + public int getRowLeft() { + return getX(); + } + + @Override + public int getRowWidth() { + return width - 2; + } + + @Override + protected void extractScrollbar(GuiGraphicsExtractor context, int mouseX, int mouseY) { + if (this.scrollable()) { + int x = this.scrollBarX(); + int y = this.scrollBarY(); + int h = this.scrollerHeight(); + context.fill(x, y, x + 2, y + h, CommonColors.WHITE); + } + } + + @Override + protected int scrollBarX() { + return getRight() - 2; + } + + protected class Entry extends AbstractSelectionList.Entry { + + private final HudWidget hudWidget; + + private Entry(HudWidget widget) { + this.hudWidget = widget; + } + + @Override + public void extractContent(GuiGraphicsExtractor context, int mouseX, int mouseY, boolean hovered, float tickProgress) { + if (hovered) { + context.fill(getX(), getY(), getX() + getWidth(), getY() + getHeight(), ARGB.white(0.1f)); + } + context.textRenderer().accept(getX(), getY() + 1, hudWidget.getInformation().displayName()); + //ClickableWidget.drawScrollableText(context, client.textRenderer, hudWidget.getInformation().displayName(), x, y, x + entryWidth, y + entryHeight, Colors.WHITE); + } + + @Override + public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { + widgetConsumer.accept(hudWidget); + visible = false; + return true; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/CopyToPopup.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/CopyToPopup.java new file mode 100644 index 00000000000..18b5fe06fad --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/CopyToPopup.java @@ -0,0 +1,140 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.mixins.accessors.CheckboxAccessor; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.LayerConfig; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.PositionedWidget; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetConfig; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.render.gui.AbstractPopupScreen; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Checkbox; +import net.minecraft.client.gui.components.ScrollableLayout; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.layouts.SpacerElement; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.util.ARGB; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +class CopyToPopup extends AbstractPopupScreen { + private final LinearLayout layout = LinearLayout.vertical().spacing(2); + private final Set selectedLocations; + private final PositionedWidget copiedWidget; + private final Location location; + private final WidgetManager.ScreenLayer layer; + private boolean copyPosition = true; + private ScrollableLayout scrollable; + + CopyToPopup(Screen backgroundScreen, PositionedWidget copiedWidget, Location location, WidgetManager.ScreenLayer layer) { + super(Component.literal("Edit hidden widgets"), backgroundScreen); + this.copiedWidget = copiedWidget; + this.selectedLocations = WidgetManager.getCopyTracker().get(layer) + .get(copiedWidget.widget.getInternalID()) + .flatMap(s -> s.whereHas(location)) + .map(EnumSet::copyOf) + .orElseGet(() -> EnumSet.noneOf(Location.class)); + this.location = location; + this.layer = layer; + + this.layout.defaultCellSetting().alignHorizontallyCenter(); + } + + @Override + protected void init() { + layout.addChild(Checkbox.builder(Component.literal("Copy Position"), font) + .selected(selectedLocations.isEmpty()) // automatically select if it's empty + .tooltip(Tooltip.create(Component.literal("If unchecked will not copy the position and only affect locations where the widget is already present."))) + .onValueChange((_, value) -> copyPosition = value).build() + ); + layout.addChild(new StringWidget(Component.literal("Target locations").withStyle(ChatFormatting.BOLD), font), settings -> settings.paddingVertical(4)); + LinearLayout content = LinearLayout.vertical().spacing(2); + + List checkboxes = new ArrayList<>(); + Checkbox selectAll = content.addChild(Checkbox.builder(Component.literal("Select All").withStyle(ChatFormatting.BOLD), font) + .maxWidth(200) + .selected(selectedLocations.containsAll(WidgetManager.ALLOWED_LOCATIONS)) + .onValueChange((_, value) -> { + if (value) { + selectedLocations.clear(); + selectedLocations.addAll(WidgetManager.ALLOWED_LOCATIONS); + } else { + selectedLocations.clear(); + } + checkboxes.forEach(checkbox -> ((CheckboxAccessor) checkbox).setSelected(value)); + }) + .build()); + for (Location location : WidgetManager.ALLOWED_LOCATIONS) { + if (location == this.location) continue; + checkboxes.add(content.addChild( + Checkbox.builder(Component.literal(location.toString()), font) + .maxWidth(200) + .selected(selectedLocations.contains(location)) + .onValueChange((_, value) -> { + if (value) { + selectedLocations.add(location); + } else { + selectedLocations.remove(location); + } + + ((CheckboxAccessor) selectAll).setSelected(checkboxes.stream().allMatch(Checkbox::selected)); + }) + .build() + )); + } + content.arrangeElements(); + int maxHeight = Math.min(200, height - 150); + this.scrollable = new ScrollableLayout(minecraft, content, maxHeight); + scrollable.setMaxHeight(maxHeight); + scrollable.setMinWidth(150); + layout.addChild(scrollable); + layout.addChild(SpacerElement.height(10)); + layout.addChild(Button.builder(CommonComponents.GUI_DONE, _ -> { + apply(); + onClose(); + }).build()); + layout.addChild(Button.builder(CommonComponents.GUI_CANCEL, _ -> onClose()).build()); + layout.visitWidgets(this::addRenderableWidget); + super.init(); + } + + private void apply() { + JsonObject widgetConfig = new JsonObject(); + copiedWidget.widget.save(widgetConfig); + for (Location loc : selectedLocations) { + LayerConfig config = WidgetManager.getScreenConfig(loc).get(layer); + if (copyPosition) { + config.widgets.put(copiedWidget.widget.getInternalID(), new WidgetConfig(widgetConfig, copiedWidget.rule)); + } else { + config.widgets.computeIfPresent(copiedWidget.widget.getInternalID(), (_, oldConfig) -> new WidgetConfig(Optional.of(widgetConfig), oldConfig.position())); + } + } + selectedLocations.add(location); + WidgetManager.getCopyTracker().get(layer).getOrCreate(copiedWidget.widget.getInternalID()).track(selectedLocations); + } + + @Override + protected void repositionElements() { + super.repositionElements(); + layout.arrangeElements(); + layout.setPosition((width - layout.getWidth()) / 2, (height - layout.getHeight()) / 2); + } + + @Override + public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractBackground(graphics, mouseX, mouseY, a); + extractPopupBackground(graphics, layout.getX(), layout.getY(), layout.getWidth(), layout.getHeight()); + graphics.fill(scrollable.getX(), scrollable.getY(), scrollable.getX() + scrollable.getWidth(), scrollable.getY() + scrollable.getHeight(), ARGB.black(0.1f)); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/CustomDropdownWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/CustomDropdownWidget.java new file mode 100644 index 00000000000..5fef7b24196 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/CustomDropdownWidget.java @@ -0,0 +1,91 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.utils.render.gui.DropdownWidget; +import java.util.List; +import java.util.function.Consumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.Identifier; +import net.minecraft.util.ARGB; +import net.minecraft.util.CommonColors; + +class CustomDropdownWidget extends DropdownWidget { + private static final Identifier TEXTURE = SkyblockerMod.id("menu_outer_space"); + private final T firstEntry; + + CustomDropdownWidget(int x, int y, int width, int maxHeight, List entries, Consumer selectCallback, T selected) { + super(Minecraft.getInstance(), x, y, width, maxHeight, 12, entries, selectCallback, selected, _ -> {}); + headerHeight = 15; + firstEntry = entries.getFirst(); + } + + + @Override + protected void extractHeader(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTicks) { + int y = getY() - 1; + int y2 = y + headerHeight; + TopBarWidget.drawButtonBorder(graphics, getX(), y, y2); + TopBarWidget.drawButtonBorder(graphics, getRight(), y, y2); + + if (isHovered() && mouseY < y2) { + graphics.fill(getX(), y, getRight() + 1, y2, ARGB.color(100, 0)); + } else { + graphics.fill(getX(), y, getRight() + 1, y2, ARGB.color(50, 0)); + } + graphics.text(client.font, ">", getX() + 4, getY() + (headerHeight - client.font.lineHeight) / 2 + 1, CommonColors.LIGHTER_GRAY, true); // +1 on the y coordinate cuz drawScrollableText does so too + graphics.textRenderer().acceptScrolling(formatter.apply(selected), + getX() + getWidth() / 2, + getX() + 4 + 6, + getRight() - 2, + getY() + 2, + getY() + headerHeight - 2 + ); + } + + @Override + protected DropdownWidget.DropdownList createDropdown() { + return new CustomDropdownList(client); + } + + @Override + protected DropdownWidget.Entry createEntry(T element) { + return new CustomEntry(element); + } + + class CustomDropdownList extends DropdownList { + protected CustomDropdownList(Minecraft minecraftClient) { + super(minecraftClient); + } + + @Override + protected void extractListBackground(GuiGraphicsExtractor context) { + context.enableScissor(this.getX(), this.getY() - 1, this.getRight(), this.getBottom() + 2); + context.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURE, this.getX(), this.getY() - 3, this.getWidth(), this.getHeight() + 5); + context.disableScissor(); + } + } + + class CustomEntry extends Entry { + protected CustomEntry(T element) { + super(element); + } + + @Override + public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + if (entry != firstEntry) { + graphics.horizontalLine(this.getX(), this.getX() + this.getWidth(), this.getY(), ARGB.color(15, 0)); + } + graphics.textRenderer().acceptScrollingWithDefaultCenter( + formatter.apply(entry).copy().withStyle(Style.EMPTY.withUnderlined(hovered)), + this.getX(), + this.getX() + this.getWidth(), + this.getY(), + this.getY() + 11 + ); + + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/GlobalOptionsScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/GlobalOptionsScreen.java new file mode 100644 index 00000000000..7122aacd75e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/GlobalOptionsScreen.java @@ -0,0 +1,161 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.config.configs.UIAndVisualsConfig; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenConfig; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; +import de.hysky.skyblocker.utils.Formatters; +import de.hysky.skyblocker.utils.render.gui.AbstractPopupScreen; +import de.hysky.skyblocker.utils.render.gui.RangedSliderWidget; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Checkbox; +import net.minecraft.client.gui.components.CycleButton; +import net.minecraft.client.gui.components.ScrollableLayout; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.layouts.GridLayout; +import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import java.util.function.Consumer; + +class GlobalOptionsScreen extends Screen { + private final Tooltip STYLE_TOOLTIP = Tooltip.create(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.style.@Tooltip[0]").append("\n") + .append(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.style.@Tooltip[1]")).append("\n") + .append(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.style.@Tooltip[2]")).append("\n") + .append(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.style.@Tooltip[3]"))); + private final Component YES = CommonComponents.GUI_YES.copy().withStyle(ChatFormatting.GREEN); + private final Component NO = CommonComponents.GUI_NO.copy().withStyle(ChatFormatting.RED); + private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this); + private final WidgetsConfigurationScreen parent; + + GlobalOptionsScreen(WidgetsConfigurationScreen parent) { + super(Component.translatable("skyblocker.config.hud.globalOptionsScreen.title")); + this.parent = parent; + } + + @Override + protected void init() { + layout.addToHeader(new StringWidget(title, font)); + LinearLayout body = layout.addToContents(LinearLayout.vertical().spacing(5)); + body.addChild(new StringWidget(Component.literal("Global Options"), font)); + GridLayout.RowHelper globalOptions = body.addChild(new GridLayout().spacing(2)).createRowHelper(2); + layout.addToFooter(Button.builder(CommonComponents.GUI_DONE, _ -> onClose()).build()); + + UIAndVisualsConfig.TabHudConf conf = SkyblockerConfigManager.get().uiAndVisuals.tabHud; + globalOptions.addChild(CycleButton.builder(style -> Component.translatable(style.toString()), conf.style) + .withValues(UIAndVisualsConfig.TabHudStyle.values()) + .withTooltip(_ -> STYLE_TOOLTIP) + .create( + 0, + 0, + Button.DEFAULT_WIDTH * 2 + 2, + Button.DEFAULT_HEIGHT, + Component.translatable("skyblocker.config.uiAndVisuals.tabHud.style"), + (_, value) -> updateConfig(config -> config.style = value)), + 2); + globalOptions.addChild(RangedSliderWidget.builder() + .optionFormatter(Component.translatable("skyblocker.config.hud.globalScale"), d -> Component.literal(Formatters.INTEGER_NUMBERS.format(d) + '%')) + .defaultValue(conf.tabHudScale) + .step(1) + .minMax(10, 200) + .callback(d -> updateConfig(config -> config.tabHudScale = (int) Math.round(d))) + .build()); + // TODO turn these two into per widget things maybe? + globalOptions.addChild(CycleButton.booleanBuilder(YES, NO, conf.displayIcons) + .create(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.displayIcons"), (_, value) -> updateConfig(config -> config.displayIcons = value)) + ); + globalOptions.addChild(CycleButton.booleanBuilder(YES, NO, conf.compactWidgets) + .withTooltip(_ -> Tooltip.create(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.compactWidgets.@Tooltip"))) + .create(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.compactWidgets"), (_, value) -> updateConfig(config -> config.compactWidgets = value)) + ); + globalOptions.addChild(CycleButton.booleanBuilder(YES, NO, conf.enableFancyWidgetsList) + .withTooltip(_ -> Tooltip.create(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.fancyWidgetsList.@Tooltip"))) + .create(Component.translatable("skyblocker.config.uiAndVisuals.tabHud.fancyWidgetsList"), (_, value) -> updateConfig(config -> config.enableFancyWidgetsList = value)) + ); + + body.addChild(new StringWidget(Component.literal(parent.getCurrentLocation() + "'s options"), font)); + GridLayout.RowHelper screenOptions = body.addChild(new GridLayout().spacing(2)).createRowHelper(2); + screenOptions.addChild(Button.builder(Component.literal("Edit visible Fancy TAB widgets"), _ -> minecraft.setScreen(new HiddenWidgetsPopup(this, parent.getScreenConfig()))).build(), 2).active = SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled; + + layout.visitWidgets(this::addRenderableWidget); + repositionElements(); + } + + private void updateConfig(Consumer consumer) { + SkyblockerConfigManager.updateOnly(config -> consumer.accept(config.uiAndVisuals.tabHud)); + } + + @Override + protected void repositionElements() { + layout.arrangeElements(); + } + + + @Override + public void onClose() { + SkyblockerConfigManager.update(_ -> {}); + minecraft.setScreen(parent); + } + + private static class HiddenWidgetsPopup extends AbstractPopupScreen { + private final ScreenConfig screenConfig; + private final LinearLayout layout = LinearLayout.vertical().spacing(10); + + private HiddenWidgetsPopup(Screen backgroundScreen, ScreenConfig screenConfig) { + super(Component.literal("Edit hidden widgets"), backgroundScreen); + this.screenConfig = screenConfig; + } + + @Override + protected void init() { + LinearLayout content = LinearLayout.vertical().spacing(2); + + for (String widgetId : PlayerListManager.getCurrentWidgets()) { + HudWidget widget = PlayerListManager.getTabWidget(widgetId); + if (widget == null) continue; + content.addChild( + Checkbox.builder(widget.getInformation().displayName(), font) + .maxWidth(200) + .selected(screenConfig.hiddenTabWidgets.contains(widget.getInternalID())) + .onValueChange((_, value) -> { + if (value) { + screenConfig.hiddenTabWidgets.add(widget.getInternalID()); + } else { + screenConfig.hiddenTabWidgets.remove(widget.getInternalID()); + } + }) + .build() + ); + } + content.arrangeElements(); + int maxHeight = Math.min(200, height - 150); + ScrollableLayout scrollable = new ScrollableLayout(minecraft, content, maxHeight); + scrollable.setMaxHeight(maxHeight); + scrollable.setMinWidth(150); + layout.addChild(scrollable); + layout.addChild(Button.builder(CommonComponents.GUI_DONE, _ -> onClose()).build()); + layout.visitWidgets(this::addRenderableWidget); + super.init(); + } + + @Override + protected void repositionElements() { + super.repositionElements(); + layout.arrangeElements(); + layout.setPosition((width - layout.getWidth()) / 2, (height - layout.getHeight()) / 2); + } + + @Override + public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractBackground(graphics, mouseX, mouseY, a); + extractPopupBackground(graphics, layout.getX(), layout.getY(), layout.getWidth(), layout.getHeight()); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/OptionWidgetCollector.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/OptionWidgetCollector.java new file mode 100644 index 00000000000..a877a398d6c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/OptionWidgetCollector.java @@ -0,0 +1,27 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.CycleButton; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import java.util.List; +import java.util.function.Consumer; + +public class OptionWidgetCollector { + private final List collectorList; + + public OptionWidgetCollector(List collector) { + this.collectorList = collector; + } + + public T addWidget(T widget) { + collectorList.add(widget); + return widget; + } + + public void yesNoButton(Component label, Consumer callback, boolean initialValue) { + addWidget(CycleButton.booleanBuilder(CommonComponents.GUI_YES, CommonComponents.GUI_NO, initialValue) + .create(label, (_, value) -> callback.accept(value))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/PositionRuleWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/PositionRuleWidget.java new file mode 100644 index 00000000000..0fbbd5f7f71 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/PositionRuleWidget.java @@ -0,0 +1,235 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.PositionedWidget; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetPositioner; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; +import de.hysky.skyblocker.utils.render.GuiHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractContainerWidget; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.navigation.ScreenPosition; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.util.CommonColors; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +class PositionRuleWidget extends AbstractContainerWidget { + + // TODO editable coords maybe? + + LinearLayout layout = LinearLayout.vertical(); + List widgets = new ArrayList<>(); + private final StringWidget coordsDisplay; + private final Button parentButton; + private final PositionedWidget modifiedWidget; + + private final WidgetsConfigurationScreen widgetConfig; + + PositionRuleWidget(WidgetsConfigurationScreen config, PositionedWidget modifiedWidget) { + super(0, 0, 0, 0, Component.literal("hi"), defaultSettings(0)); + this.widgetConfig = config; + this.modifiedWidget = modifiedWidget; + layout.defaultCellSetting().alignHorizontallyCenter(); + parentButton = Button.builder(Component.translatable("skyblocker.config.hud.position.parent", getParentName()), _ -> config.promptSelectWidget(this::onWidgetSelected, false)).build(); + coordsDisplay = new StringWidget(Component.literal("hi"), Minecraft.getInstance().font); + coordsDisplay.setHeight(coordsDisplay.getHeight() + 6); + AnchorSelectionWidget parentPoint = new AnchorSelectionWidget(Component.translatable("skyblocker.config.hud.position.pointParent"), true); + AnchorSelectionWidget thisPoint = new AnchorSelectionWidget(Component.translatable("skyblocker.config.hud.position.pointThis"), false); + add(new StringWidget(Component.literal("Positioning"), Minecraft.getInstance().font)); + add(parentButton); + add(coordsDisplay); + add(parentPoint); + add(thisPoint); + layout.arrangeElements(); + setHeight(layout.getHeight()); + } + + private void add(AbstractWidget widget) { + widgets.add(widget); + layout.addChild(widget); + } + + private void onWidgetSelected(@Nullable HudWidget selectedWidget) { + PositionRule oldRule = modifiedWidget.rule; + + HudWidget editedWidget = widgetConfig.getEditedWidget(); + int thisAnchorX = (int) (editedWidget.getX() + oldRule.thisPoint().horizontalPoint().getPercentage() * editedWidget.getWidth()); + int thisAnchorY = (int) (editedWidget.getY() + oldRule.thisPoint().verticalPoint().getPercentage() * editedWidget.getHeight()); + + int otherAnchorX = selectedWidget == null ? 0 : selectedWidget.getX(); + int otherAnchorY = selectedWidget == null ? 0 : selectedWidget.getY(); + + PositionRule newRule = new PositionRule( + Optional.ofNullable(selectedWidget).map(HudWidget::getInternalID), + PositionRule.Point.DEFAULT, + oldRule.thisPoint(), + thisAnchorX - otherAnchorX, + thisAnchorY - otherAnchorY + ); + if (selectedWidget != null) { + parentButton.setMessage(Component.translatable("skyblocker.config.hud.position.parent", selectedWidget.getInformation().displayName())); + } else { + parentButton.setMessage(Component.translatable("skyblocker.config.hud.position.parent", Component.translatable("skyblocker.config.hud.position.parent.screen"))); + } + modifiedWidget.rule = newRule; + } + + @Override + public void setWidth(int width) { + super.setWidth(width); + for (AbstractWidget widget : widgets) { + widget.setWidth(width); + } + coordsDisplay.setMaxWidth(width, StringWidget.TextOverflow.SCROLLING); + layout.arrangeElements(); + setHeight(layout.getHeight()); + } + + @Override + public void setX(int x) { + super.setX(x); + layout.setX(x); + } + + @Override + public void setY(int y) { + super.setY(y); + layout.setY(y); + } + + private Component getParentName() { + PositionRule rule = modifiedWidget.rule; + if (rule.parent().isEmpty()) { + return Component.translatable("skyblocker.config.hud.position.parent.screen"); + } else { + HudWidget widget = WidgetManager.getWidgetOrPlaceholder(rule.parent().get()); + return widget.getInformation().displayName(); + } + } + + @Override + public List children() { + return widgets; + } + + @Override + protected int contentHeight() { + return getHeight(); + } + + @Override + protected double scrollRate() { + return 0; + } + + @Override + protected void extractWidgetRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks) { + coordsDisplay.setMessage(Component.literal("x: " + modifiedWidget.rule.relativeX() + ", y: " + modifiedWidget.rule.relativeY())); + layout.arrangeElements(); + for (AbstractWidget widget : widgets) { + widget.extractRenderState(context, mouseX, mouseY, deltaTicks); + } + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + return false; + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput builder) {} + + private class AnchorSelectionWidget extends AbstractWidget { + private final boolean parent; + private PositionRule.@Nullable Point hoveredPoint = null; + + private AnchorSelectionWidget(Component text, boolean parent) { + super(0, 0, 20, 40, text); + this.parent = parent; + } + + @Override + protected void extractWidgetRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) { + hoveredPoint = null; + context.text(Minecraft.getInstance().font, getMessage(), getX(), getY(), CommonColors.WHITE, true); + context.pose().pushMatrix(); + context.pose().translate(getX(), getY() + 10); + // Rectangle thing + int x = getWidth() / 6; + int w = (int) (4 * getWidth() / 6f); + int y = 5; // 30 / 6 + int h = 20; + + GuiHelper.border(context, x, y + 1, w, h, CommonColors.WHITE); + PositionRule rule = modifiedWidget.rule; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + int squareX = x + (i * getWidth()) / 3; + int squareY = y + (j * 30) / 3; + PositionRule.Point point = (parent ? rule.parentPoint() : rule.thisPoint()); + boolean selectedAnchor = point.horizontalPoint().ordinal() == i && point.verticalPoint().ordinal() == j; + boolean hoveredAnchor = mouseX >= getX() + i * getWidth() / 3 && + mouseX < getX() + (i + 1) * getWidth() / 3 && + mouseY >= getY() + 10 + j * 10 && + mouseY < getY() + 10 + (j + 1) * 10; + + if (hoveredAnchor) { + PositionRule.VerticalPoint[] verticalPoints = PositionRule.VerticalPoint.values(); + PositionRule.HorizontalPoint[] horizontalPoints = PositionRule.HorizontalPoint.values(); + hoveredPoint = new PositionRule.Point(verticalPoints[j], horizontalPoints[i]); + } + + context.fill(squareX - 1, squareY - 1, squareX + 2, squareY + 2, hoveredAnchor ? CommonColors.RED : selectedAnchor ? CommonColors.YELLOW : CommonColors.WHITE); + } + } + context.pose().popMatrix(); + } + + @Override + public void onClick(MouseButtonEvent click, boolean doubled) { + HudWidget editedWidget = widgetConfig.getEditedWidget(); + if (hoveredPoint != null) { + PositionRule oldRule = modifiedWidget.rule; + // Get the x, y of the parent's point + ScreenPosition startPos = WidgetPositioner.getStartPosition(oldRule.parent().orElse(null), widgetConfig.getScreenWidth(), widgetConfig.getScreenHeight(), parent ? hoveredPoint : oldRule.parentPoint()); + // Same but for the affected widget + PositionRule.Point thisPoint = parent ? oldRule.thisPoint() : hoveredPoint; + ScreenPosition endPos = new ScreenPosition( + (int) (editedWidget.getX() + thisPoint.horizontalPoint().getPercentage() * editedWidget.getWidth()), + (int) (editedWidget.getY() + thisPoint.verticalPoint().getPercentage() * editedWidget.getHeight()) + ); + + if (parent) { + modifiedWidget.rule = new PositionRule( + oldRule.parent(), + hoveredPoint, + oldRule.thisPoint(), + endPos.x() - startPos.x(), + endPos.y() - startPos.y()); + } else { + modifiedWidget.rule = new PositionRule( + oldRule.parent(), + oldRule.parentPoint(), + hoveredPoint, + endPos.x() - startPos.x(), + endPos.y() - startPos.y()); + } + } + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput builder) {} + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/SeparatorEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/SeparatorEntry.java deleted file mode 100644 index 65e04ce85db..00000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/SeparatorEntry.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.hysky.skyblocker.skyblock.tabhud.config; - -import de.hysky.skyblocker.skyblock.tabhud.config.entries.WidgetsListEntry; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.network.chat.Component; -import net.minecraft.util.CommonColors; - -import java.util.List; - -public final class SeparatorEntry extends WidgetsListEntry { - @Override - public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float deltaTicks) { - graphics.centeredText(Minecraft.getInstance().font, Component.nullToEmpty("- Skyblocker Widgets -"), this.getX() + this.getWidth() / 2, this.getY() + (this.getHeight() - 9) / 2, CommonColors.WHITE); - } - - @Override - public List children() { - return List.of(); - } - - @Override - public void extractBorder(GuiGraphicsExtractor graphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {} -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/SidePanelWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/SidePanelWidget.java new file mode 100644 index 00000000000..7ce5773f4e7 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/SidePanelWidget.java @@ -0,0 +1,217 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.PositionedWidget; +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractContainerWidget; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.layouts.SpacerElement; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +class SidePanelWidget extends AbstractContainerWidget { + private static final Identifier TEXTURE = SkyblockerMod.id("menu_outer_space"); + private static final int TOP_MARGIN = 16; // this is kinda horribly used but this widget will only be used here so it is whatever. + private static final int SCROLLBAR_AREA = SCROLLBAR_WIDTH + 1; // 1 for padding + + private final Minecraft client = Minecraft.getInstance(); + private final List optionWidgets = new ArrayList<>(); + + private LinearLayout layout = LinearLayout.vertical(); + boolean rightSide = false; + private int targetX = 0; + private float animation = 0.0f; + private int animationStart = 0; + private int animationEnd = 0; + private boolean isOpen = false; + + private final WidgetsConfigurationScreen configScreen; + + private @Nullable PositionedWidget positionedWidget; + + SidePanelWidget(int width, int height, WidgetsConfigurationScreen configScreen) { + super(0, TOP_MARGIN, width, height - TOP_MARGIN, Component.literal("Side Panel"), defaultSettings(5)); + visible = false; + this.configScreen = configScreen; + } + + @Override + public List children() { + return optionWidgets; + } + + @Override + protected int contentHeight() { + return layout.getHeight(); + } + + private boolean isNotVisible(int top, int bottom) { + return !(bottom - this.scrollAmount() >= this.getY()) || !(top - this.scrollAmount() <= this.getY() + this.getHeight()); + } + + @Override + protected void extractWidgetRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks) { + if (animation >= 0) { + if (animation < 1.0f) { + setX(animationStart + (int) ((animationEnd - animationStart) * animation)); + animation += Minecraft.getInstance().getDeltaTracker().getRealtimeDeltaTicks() * 50 * 7.5f / 1000.f; + } else { + setX(animationEnd); + animation = -1f; + } + } + context.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURE, getX() - 4, getY() - 4 - TOP_MARGIN, getWidth() + 8, getHeight() + 8 + TOP_MARGIN); + context.enableScissor(this.getX(), this.getY(), this.getRight(), this.getBottom()); + + for (AbstractWidget clickableWidget : this.optionWidgets) { + if (isNotVisible(clickableWidget.getY(), clickableWidget.getBottom())) continue; + clickableWidget.extractRenderState(context, mouseX, mouseY, deltaTicks); + } + context.disableScissor(); + this.extractScrollbar(context, mouseX, mouseY); + } + + public void open() { + if (isOpen()) return; + visible = true; + animation = 0.0f; + animationEnd = targetX; + animationStart = targetX + (rightSide ? (getWidth() + 8) : (-getWidth() - 8)); + isOpen = true; + } + + public void open(PositionedWidget positionedWidget, boolean rightSide) { + this.positionedWidget = positionedWidget; + HudWidget hudWidget = positionedWidget.widget; + layout = LinearLayout.vertical().spacing(5); + layout.defaultCellSetting().alignHorizontallyCenter(); + optionWidgets.clear(); + add(new StringWidget(0, 15, hudWidget.getInformation().displayName().copy().withStyle(ChatFormatting.UNDERLINE), client.font) { + @Override + public void setWidth(int width) { + setMaxWidth(width, TextOverflow.SCROLLING); + } + }); + if (!positionedWidget.fromTab) { + add(Button.builder(Component.translatable("skyblocker.config.hud.widget.remove"), _ -> configScreen.removeWidget(positionedWidget)).build()); + } + + add(Button.builder(Component.literal("Copy to..."), _ -> configScreen.openPopup(screen -> new CopyToPopup( + screen, + positionedWidget, + screen.getCurrentLocation(), + screen.getCurrentScreenLayer() + ))) + .tooltip(Tooltip.create(Component.literal("Copy this widget to other locations."))).build()); + + layout.addChild(SpacerElement.height(10)); + + int availableWidth = getWidth() - SCROLLBAR_AREA; + + if (!positionedWidget.fromTab) { + add(new PositionRuleWidget(configScreen, positionedWidget)); + layout.addChild(SpacerElement.height(10)); + } + + List collector = new ArrayList<>(); + hudWidget.getOptionWidgets(new OptionWidgetCollector(collector)); + collector.forEach(this::add); + + layout.addChild(SpacerElement.height(10)); + + // Position everything + for (AbstractWidget widget : optionWidgets) { + widget.setWidth(availableWidth); + } + + layout.setPosition(getX(), getY() - (int) scrollAmount()); + layout.arrangeElements(); + int openX = rightSide ? configScreen.width - getWidth() : 0; + if (isOpen() && (openX != targetX || rightSide != this.rightSide)) { + isOpen = false; + } + this.rightSide = rightSide; + targetX = openX; + open(); + } + + public void close() { + if (!isOpen()) return; + animationStart = getX(); + animationEnd = getX() + (rightSide ? (getWidth() + 8) : (-getWidth() - 8)); + animation = 0; + isOpen = false; + } + + public boolean isOpen() { + return isOpen; + } + + @Override + public void setScrollAmount(double scrollY) { + super.setScrollAmount(scrollY); + layout.setY(getY() - (int) scrollAmount()); + } + + @Override + public void setX(int x) { + super.setX(x); + if (rightSide) { + layout.setX(x); + } else { + layout.setX(x + SCROLLBAR_AREA); + } + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + if (this.getChildAt(mouseX, mouseY).filter(element -> element.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)).isPresent()) return true; + return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + @Override + public void setWidth(int width) { + super.setWidth(width); + for (AbstractWidget widget : optionWidgets) { + widget.setWidth(getWidth() - SCROLLBAR_AREA); // remove 6 for scrollbar and one for a liiiitle padding + } + layout.arrangeElements(); + } + + @Override + public void setHeight(int height) { + super.setHeight(height - TOP_MARGIN); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput output) {} + + public @Nullable PositionedWidget getPositionedWidget() { + return positionedWidget; + } + + private T add(T widget) { + optionWidgets.add(widget); + return layout.addChild(widget); + } + + + @Override + protected int scrollBarX() { + return rightSide ? super.scrollBarX() : 0; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/StyledButtonWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/StyledButtonWidget.java new file mode 100644 index 00000000000..920996d77d3 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/StyledButtonWidget.java @@ -0,0 +1,30 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import com.mojang.blaze3d.platform.cursor.CursorTypes; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.util.ARGB; + +class StyledButtonWidget extends Button { + StyledButtonWidget(int width, int height, net.minecraft.network.chat.Component message, OnPress onPress) { + super(0, 0, width, height, message, onPress, DEFAULT_NARRATION); + } + + @Override + protected void extractContents(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks) { + int y = getY() - 1; + int y2 = y + getHeight(); + TopBarWidget.drawButtonBorder(context, getX(), y, y2); + TopBarWidget.drawButtonBorder(context, getRight(), y, y2); + + if (isHovered()) { + context.fill(getX(), y, getRight() + 1, y2, ARGB.color(100, 0)); + } else { + context.fill(getX(), y, getRight() + 1, y2, ARGB.color(50, 0)); + } + this.extractDefaultLabel(context.textRendererForWidget(this, GuiGraphicsExtractor.HoveredTextEffects.NONE)); + if (this.isHovered()) { + context.requestCursor(this.isActive() ? CursorTypes.POINTING_HAND : CursorTypes.NOT_ALLOWED); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/ToggleButtonWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/ToggleButtonWidget.java new file mode 100644 index 00000000000..fdb513f8bbe --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/ToggleButtonWidget.java @@ -0,0 +1,80 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import de.hysky.skyblocker.utils.render.GuiHelper; +import it.unimi.dsi.fastutil.booleans.BooleanConsumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractButton; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.input.InputWithModifiers; +import net.minecraft.network.chat.Component; +import net.minecraft.util.ARGB; +import net.minecraft.util.Mth; + +class ToggleButtonWidget extends AbstractButton { + + private final BooleanConsumer onPress; + private boolean state; + + ToggleButtonWidget(int width, int height, Component message, BooleanConsumer onPress) { + super(0, 0, width, height, message); + this.onPress = onPress; + } + + @Override + protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float deltaTicks) { + Minecraft minecraftClient = Minecraft.getInstance(); + int y = getY() - 1; + int y2 = y + getHeight(); + TopBarWidget.drawButtonBorder(graphics, getX(), y, y2); + TopBarWidget.drawButtonBorder(graphics, getRight(), y, y2); + + if (isHovered()) { + graphics.fill(getX(), y, getRight() + 1, y2, ARGB.color(100, 0)); + } else { + graphics.fill(getX(), y, getRight() + 1, y2, ARGB.color(50, 0)); + } + int color = (this.active ? 16777215 : 10526880) | Mth.ceil(this.alpha * 255.0F) << 24; + int textWidth = minecraftClient.font.width(getMessage()); + int startX, endX; + int squareX; + int paddedTextWidth = textWidth + 9 + 2; + if (paddedTextWidth > getWidth() - 6) { + squareX = getRight() - 3 - 9; // margin + square size + endX = squareX - 2; + startX = getX() + 3; + } else { + int centerX = getX() + getWidth() / 2; + squareX = centerX + paddedTextWidth / 2 - 9; + startX = centerX - paddedTextWidth / 2; + endX = squareX - 2; + } + graphics.textRendererForWidget(this, GuiGraphicsExtractor.HoveredTextEffects.NONE).acceptScrollingWithDefaultCenter( + getMessage(), + startX, + endX, + getY(), + getBottom() + ); + int squareY = getY() + (getHeight() - 9) / 2; + GuiHelper.border(graphics, squareX, squareY, 9, 9, color); + if (state) graphics.fill(squareX + 2, squareY + 2, squareX + 7, squareY + 7, color); + } + + public void setState(boolean state) { + this.state = state; + } + + public boolean getState() { + return state; + } + + @Override + public void onPress(InputWithModifiers input) { + state = !state; + onPress.accept(state); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput builder) {} +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/TopBarWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/TopBarWidget.java new file mode 100644 index 00000000000..b77ba47c214 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/TopBarWidget.java @@ -0,0 +1,177 @@ +package de.hysky.skyblocker.skyblock.tabhud.config; + +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; +import de.hysky.skyblocker.utils.Location; +import de.hysky.skyblocker.utils.Utils; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractContainerWidget; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.PopupScreen; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.layouts.AbstractLayout; +import net.minecraft.client.gui.layouts.FrameLayout; +import net.minecraft.client.gui.layouts.LayoutElement; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.util.ARGB; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +class TopBarWidget extends AbstractContainerWidget { + private static final Identifier TEXTURE = SkyblockerMod.id("menu_outer_space"); + private static final int HEIGHT = 15; + private final CustomDropdownWidget locationDropdown; + private final CustomDropdownWidget screenLayerDropdown; + private final Layout layout; + private final List widgets; + + TopBarWidget(int width, WidgetsConfigurationScreen parent) { + super(0, 0, width, HEIGHT, Component.literal("hi"), defaultSettings(0)); + + layout = new Layout(); + + LinearLayout leftButtons = LinearLayout.horizontal(); + StyledButtonWidget optionsButton = new StyledButtonWidget(60, HEIGHT, Component.translatable("skyblocker.config.hud.topBar.options"), _ -> parent.openPopup(GlobalOptionsScreen::new)); + optionsButton.setTooltip(Tooltip.create(Component.translatable("skyblocker.config.hud.topBar.options.@Tooltip"))); + StyledButtonWidget helpButton = new StyledButtonWidget(60, HEIGHT, Component.literal("(?) ").append(Component.translatable("skyblocker.config.hud.topBar.help")), _ -> parent.openPopup(screen -> new PopupScreen.Builder(screen, Component.literal("Help")) + .addMessage(Component.translatable("skyblocker.config.hud.helpText")) + .addButton(CommonComponents.GUI_OK, PopupScreen::onClose) + .build())); + leftButtons.addChild(optionsButton); + leftButtons.addChild(helpButton); + layout.add(leftButtons); + + locationDropdown = new CustomDropdownWidget<>(width / 2 - 100 - 5, 0, 100, 200, List.copyOf(WidgetManager.ALLOWED_LOCATIONS), parent::setCurrentLocation, Utils.getLocation()); + locationDropdown.setFormatter(l -> Component.literal(l.toString())); + screenLayerDropdown = new CustomDropdownWidget<>(width / 2 + 5, 0, 100, 200, List.of(WidgetManager.ScreenLayer.values()), parent::setCurrentScreenLayer, WidgetManager.ScreenLayer.HUD); + + LinearLayout dropdownsLayout = LinearLayout.horizontal().spacing(2); + dropdownsLayout.addChild(locationDropdown); + dropdownsLayout.addChild(screenLayerDropdown); + layout.add(dropdownsLayout); + + /*ToggleButtonWidget snappingToggle = new ToggleButtonWidget(80, HEIGHT, Text.literal("Snapping"), b -> parent.snapping = b); + snappingToggle.setState(true); + snappingToggle.setTooltip(Tooltip.of(Text.literal("Automatically snap widgets to other widgets")));*/ + ToggleButtonWidget autoAnchorToggle = new ToggleButtonWidget(100, HEIGHT, Component.translatable("skyblocker.config.hud.topBar.autoAnchor"), b -> parent.autoAnchor = b); + autoAnchorToggle.setState(true); + autoAnchorToggle.setTooltip(Tooltip.create(Component.translatable("skyblocker.config.hud.topBar.autoAnchor.@Tooltip"))); + + LinearLayout rightButtons = LinearLayout.horizontal(); + //rightButtons.add(snappingToggle); + rightButtons.addChild(autoAnchorToggle); + layout.add(rightButtons); + + widgets = List.of(optionsButton, helpButton, locationDropdown, screenLayerDropdown, autoAnchorToggle); + layout.arrangeElements(); + } + + @Override + public int getHeight() { + return Math.max(super.getHeight(), Math.max(locationDropdown.getHeight(), screenLayerDropdown.getHeight())); + } + + @Override + public List children() { + return widgets; + } + + @Override + public void setWidth(int width) { + super.setWidth(width); + layout.arrangeElements(); + } + + @Override + protected void extractWidgetRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks) { + context.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURE, getX() - 2, getY() - 2, getWidth() + 4, HEIGHT + 2); + for (AbstractWidget widget : widgets) { + widget.extractRenderState(context, mouseX, mouseY, deltaTicks); + } + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + if (!visible) return false; + if (this.getChildAt(mouseX, mouseY).filter(element -> element.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)).isPresent()) return true; + return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput builder) {} + + @Override + protected int contentHeight() { + return 0; + } + + static void drawButtonBorder(GuiGraphicsExtractor context, int x, int y, int y2) { + context.verticalLine(x - 1, y, y2, ARGB.color(15, -1)); + context.verticalLine(x, y, y2, ARGB.color(100, 0)); + context.verticalLine(x + 1, y, y2, ARGB.color(15, -1)); + } + + private class Layout extends AbstractLayout { + private final List widgets = new ArrayList<>(4); + + Layout() { + super(0, 0, 0, 0); + } + + private void add(LayoutElement widget) { + widgets.add(widget); + } + + @Override + public void visitChildren(Consumer consumer) { + widgets.forEach(consumer); + } + + @Override + public int getX() { + return TopBarWidget.this.getX(); + } + + @Override + public int getY() { + return TopBarWidget.this.getY(); + } + + @Override + public int getWidth() { + return TopBarWidget.this.getWidth(); + } + + @Override + public int getHeight() { + return TopBarWidget.this.getHeight(); + } + + @Override + public void arrangeElements() { + super.arrangeElements(); + LayoutElement first = widgets.getFirst(); + first.setPosition(getX(), getY()); + int low = first.getX() + first.getWidth(); + LayoutElement last = widgets.getLast(); + last.setPosition(getX() + getWidth() - last.getWidth(), getY()); + int high = last.getX(); + for (int i = 1; i < widgets.size() - 1; i++) { + LayoutElement widget = widgets.get(i); + widget.setY(getY()); + FrameLayout.alignInDimension(0, getWidth(), widget.getWidth(), widget::setX, 0.5f); + if (widget.getX() + widget.getWidth() > high) { + FrameLayout.alignInDimension(low, high - low, widget.getWidth(), widget::setX, 0.5f); + } + } + } + } +} 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 eecc6484b4e..f842c5e9097 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 @@ -1,352 +1,532 @@ package de.hysky.skyblocker.skyblock.tabhud.config; -import com.mojang.brigadier.Command; import com.mojang.logging.LogUtils; import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.annotations.Init; -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.ScreenBuilder; +import de.hysky.skyblocker.skyblock.tabhud.TabHud; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.EditableScreenBuilder; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.LayerConfig; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.PositionedWidget; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenConfig; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetConfig; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetPositioner; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; +import de.hysky.skyblocker.skyblock.tabhud.widget.PlaceholderWidget; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; -import de.hysky.skyblocker.utils.render.gui.DropdownWidget; -import de.hysky.skyblocker.utils.scheduler.MessageScheduler; +import de.hysky.skyblocker.utils.render.GuiHelper; import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.ClientCommands; -import net.minecraft.client.gui.components.tabs.TabManager; -import net.minecraft.client.gui.components.tabs.TabNavigationBar; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.PopupScreen; +import net.minecraft.client.gui.navigation.ScreenAxis; +import net.minecraft.client.gui.navigation.ScreenDirection; +import net.minecraft.client.gui.navigation.ScreenPosition; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.ChestMenu; -import net.minecraft.world.inventory.ContainerListener; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import org.apache.commons.lang3.ArrayUtils; +import net.minecraft.util.CommonColors; +import org.joml.Matrix3x2f; +import org.joml.Matrix3x2fStack; import org.jspecify.annotations.Nullable; +import org.lwjgl.glfw.GLFW; import org.slf4j.Logger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; -public class WidgetsConfigurationScreen extends Screen implements ContainerListener { - public static final Logger LOGGER = LogUtils.getLogger(); - - private @Nullable ChestMenu handler; - private String titleLowercase; - public final boolean noHandler; - private WidgetManager.@Nullable ScreenLayer widgetsLayer = null; - private @Nullable Screen parent = null; - - private boolean tabPreview = false; - private PreviewTab previewTab; - - private final Map nameToLocation = Map.ofEntries( - Map.entry("private islands", Location.PRIVATE_ISLAND), - Map.entry("the hub", Location.HUB), - Map.entry("the dungeon hub", Location.DUNGEON_HUB), - Map.entry("the farming islands", Location.THE_FARMING_ISLAND), - Map.entry("garden", Location.GARDEN), - Map.entry("the park", Location.THE_PARK), - Map.entry("the gold mine", Location.GOLD_MINE), - Map.entry("deep caverns", Location.DEEP_CAVERNS), - Map.entry("dwarven mines", Location.DWARVEN_MINES), - Map.entry("crystal hollows", Location.CRYSTAL_HOLLOWS), - Map.entry("the mineshaft", Location.GLACITE_MINESHAFTS), - Map.entry("spider's den", Location.SPIDERS_DEN), - Map.entry("the end", Location.THE_END), - Map.entry("crimson isle", Location.CRIMSON_ISLE), - Map.entry("kuudra", Location.KUUDRAS_HOLLOW), - Map.entry("the rift", Location.THE_RIFT), - Map.entry("jerry's workshop", Location.WINTER_ISLAND), - Map.entry("galatea", Location.GALATEA), - Map.entry("backwater bayou", Location.BACKWATER_BAYOU), - Map.entry("lotus atoll", Location.LOTUS_ATOLL) - ); - private Location currentLocation = Utils.getLocation(); +public class WidgetsConfigurationScreen extends Screen { + private static final Logger LOGGER = LogUtils.getLogger(); - public Location getCurrentLocation() { - return currentLocation; - } - - public boolean isPreviewVisible() { - return tabPreview; - } - - // Tabs and stuff - private final TabManager tabManager = new TabManager(this::addRenderableWidget, this::removeWidget); - private @Nullable TabNavigationBar tabNavigation; - private @Nullable WidgetsListTab widgetsListTab; - - public static boolean overrideWidgetsScreen = false; - - /** - * Register the /skyblocker hud command, which will open /widgets if on Skyblock and Fancy Tab Hud is enabled. - * Otherwise, it'll open the widgets config screen. - */ @Init public static void initCommands() { - ClientCommandRegistrationCallback.EVENT.register((dispatcher, _) -> { - dispatcher.register(ClientCommands.literal(SkyblockerMod.NAMESPACE).then(ClientCommands.literal("hud").executes(_ -> { - openWidgetsConfigScreen(null); - return Command.SINGLE_SUCCESS; - }))); - }); - } - - public static void openWidgetsConfigScreen(@Nullable Screen screen) { - if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled) { - overrideWidgetsScreen = true; - MessageScheduler.INSTANCE.sendMessageAfterCooldown("/widgets", true); - } else { - Location currentLocation = Utils.isOnSkyblock() ? Utils.getLocation() : Location.HUB; - MessageScheduler.queueOpenScreen(new WidgetsConfigurationScreen(currentLocation, WidgetManager.ScreenLayer.MAIN_TAB, screen)); - } + ClientCommandRegistrationCallback.EVENT.register((dispatcher, _) -> dispatcher.register( + ClientCommands.literal(SkyblockerMod.NAMESPACE).then(ClientCommands.literal("hud").executes(Scheduler.queueOpenScreenCommand(WidgetsConfigurationScreen::new))) + )); } /** - * Creates the screen to configure, putting the handler at null will hide the first tab. Putting it to null is used in the config - * - * @param handler the container handler - * @param titleLowercase the title in lowercase + * The currently edited location. {@link Location#UNKNOWN} if editing the global skyblock screen. */ - private WidgetsConfigurationScreen(@Nullable ChestMenu handler, String titleLowercase, Location targetLocation, WidgetManager.@Nullable ScreenLayer widgetLayerToGoTo) { - super(Component.literal("Widgets Configuration")); - this.handler = handler; - this.titleLowercase = titleLowercase; - this.noHandler = handler == null; - if (!noHandler) { - this.handler.addSlotListener(this); - parseLocation(); - } else { - currentLocation = targetLocation; - widgetsLayer = widgetLayerToGoTo; - } - WidgetManager.getScreenBuilder(currentLocation).backupPositioning(); - } + private Location currentLocation; + private WidgetManager.ScreenLayer currentScreenLayer; + + private final EditableScreenBuilder screenBuilder = new EditableScreenBuilder(); + private ScreenConfig screenConfig; + private EditableScreenBuilder.EditableLayer layer; + private SidePanelWidget sidePanelWidget; + private AddWidgetWidget addWidgetWidget; + private TopBarWidget topBarWidget; + + private @Nullable PositionedWidget hoveredWidget; + private @Nullable PositionedWidget selectedWidget; /** - * Create the screen for when backed by hypixel's widgets menu - * - * @param handler the container handler - * @param titleLowercase the title in lowercase, to figure out where you are + * Where the user started dragging relative to the dragged widget's position. Null if not dragging. */ - public WidgetsConfigurationScreen(ChestMenu handler, String titleLowercase) { - this(handler, titleLowercase, Location.UNKNOWN, null); + private @Nullable ScreenPosition dragRelative = null; + private boolean openPanelAfterDragging = false; + boolean autoAnchor = true; + + private @Nullable SelectWidgetPrompt selectWidgetPrompt = null; + + public WidgetsConfigurationScreen() { + super(Component.literal("Widgets Config Screen")); + currentLocation = Utils.getLocation(); + currentScreenLayer = WidgetManager.ScreenLayer.HUD; + screenConfig = WidgetManager.getScreenConfig(currentLocation); + screenBuilder.setConfig(screenConfig); + layer = screenBuilder.getLayer(currentScreenLayer); + layer.update(); + screenBuilder.updateFancyTab(); } - /** - * Create the screen specifically for the config screen, the widgets tab will be unavailable - * - * @param targetLocation open the preview to this location - * @param widgetLayerToGoTo go to this widget's layer - */ - public WidgetsConfigurationScreen(Location targetLocation, String widgetLayerToGoTo, @Nullable Screen parent) { - this(null, "", targetLocation, WidgetManager.getScreenBuilder(targetLocation).getPositionRuleOrDefault(widgetLayerToGoTo).screenLayer()); - this.parent = parent; + public void setCurrentLocation(Location newLocation) { + layer.editor().serializeConfig(); + this.currentLocation = newLocation; + screenConfig = WidgetManager.getScreenConfig(currentLocation); + screenBuilder.setConfig(screenConfig); + layer = screenBuilder.getLayer(currentScreenLayer); + layer.update(); + screenBuilder.updateFancyTab(); } - /** - * Create the screen specifically for the config screen, the widgets tab will be unavailable - * - * @param targetLocation open the preview to this location - * @param layerToGo go to this layer - */ - public WidgetsConfigurationScreen(Location targetLocation, WidgetManager.ScreenLayer layerToGo, @Nullable Screen parent) { - this(null, "", targetLocation, layerToGo); - this.parent = parent; + public void setCurrentScreenLayer(WidgetManager.ScreenLayer newScreenLayer) { + layer.editor().serializeConfig(); + this.currentScreenLayer = newScreenLayer; + layer = screenBuilder.getLayer(newScreenLayer); + layer.update(); } @Override protected void init() { - previewTab = new PreviewTab(this.minecraft, this, noHandler ? PreviewTab.Mode.EDITABLE_LOCATION : PreviewTab.Mode.NORMAL); - PreviewTab previewDungeons = new PreviewTab(this.minecraft, this, PreviewTab.Mode.DUNGEON); - if (noHandler) { - previewTab.goToLayer(widgetsLayer); - } - widgetsListTab = new WidgetsListTab(this.minecraft, this.handler); - this.tabNavigation = TabNavigationBar.builder(this.tabManager, this.width) - .addTabs(this.widgetsListTab, this.previewTab, previewDungeons) - .build(); - widgetsListTab.setShouldShowCustomWidgetEntries(titleLowercase.startsWith("widgets ") || noHandler); - updateCustomWidgets(); + super.init(); + sidePanelWidget = new SidePanelWidget(width / 4, height, this); + addWidgetWidget = new AddWidgetWidget(minecraft, this::addWidget); + topBarWidget = new TopBarWidget(width, this); + addWidget(addWidgetWidget); + addWidget(topBarWidget); + addWidget(sidePanelWidget); + repositionElements(); + } - this.tabNavigation.selectTab(0, false); - this.addRenderableWidget(tabNavigation); - this.repositionElements(); + private void addWidget(HudWidget widget) { + layer.editor().add(widget).rule = new PositionRule( + "screen", + PositionRule.Point.DEFAULT, + PositionRule.Point.DEFAULT, + (int) (minecraft.mouseHandler.getScaledXPos(minecraft.getWindow()) / TabHud.getScaleFactor()), + (int) (minecraft.mouseHandler.getScaledYPos(minecraft.getWindow()) / TabHud.getScaleFactor()) + ); } @Override protected void repositionElements() { - if (this.tabNavigation != null) { - this.tabNavigation.updateWidth(this.width); - this.tabNavigation.arrangeElements(); - int i = this.tabNavigation.getRectangle().bottom(); - ScreenRectangle screenRect = new ScreenRectangle(0, i, this.width, this.height - i - 5); - this.tabManager.setTabArea(screenRect); - } + sidePanelWidget.setWidth(width / 4); + sidePanelWidget.setHeight(height); + if (sidePanelWidget.isOpen()) sidePanelWidget.setX(sidePanelWidget.rightSide ? width - sidePanelWidget.getWidth() : 0); + topBarWidget.setWidth(width); } - public void updateHandler(ChestMenu newHandler, String titleLowercase) { - if (handler == null) return; - handler.removeSlotListener(this); - handler = newHandler; - handler.addSlotListener(this); - this.titleLowercase = titleLowercase; - parseLocation(); - widgetsListTab.updateHandler(handler); + @Override + public void extractBackground(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks) { + super.extractBackground(context, mouseX, mouseY, deltaTicks); + Component text = Component.translatable("skyblocker.config.hud.screen.rightClick"); + int textWidth = font.width(text); + // FIXME transparency and shadow + context.textRenderer().accept((width - textWidth) / 2, (height - font.lineHeight) / 2, text); } - public void updateCustomWidgets() { - List entries = new ArrayList<>(); - for (HudWidget value : WidgetManager.widgetInstances.values()) { - if (!value.availableLocations().contains(currentLocation)) continue; - entries.add(new WidgetEntry(value, currentLocation)); - } - widgetsListTab.setCustomWidgetEntries(entries); + @Override + protected void extractBlurredBackground(GuiGraphicsExtractor graphics) { + if (minecraft.level != null && !minecraft.hasControlDown()) super.extractBlurredBackground(graphics); } - public void setCurrentLocation(Location location) { - Location old = this.currentLocation; - currentLocation = location; - if (old != currentLocation) { - WidgetManager.getScreenBuilder(currentLocation).backupPositioning(); - updateCustomWidgets(); + @Override + protected void extractMenuBackground(GuiGraphicsExtractor graphics) { + if (minecraft.level != null && !minecraft.hasControlDown()) super.extractMenuBackground(graphics); + } + + @Override + public void extractRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks) { + super.extractRenderState(context, mouseX, mouseY, deltaTicks); + Matrix3x2fStack matrices = context.pose(); + float scale = TabHud.getScaleFactor(); + matrices.pushMatrix(); + matrices.scale(scale); + layer.builder().extractRenderStates(context, getScreenWidth(), getScreenHeight(), true); + matrices.popMatrix(); + hoveredWidget = null; + double scaledMouseX = mouseX / scale; + double scaledMouseY = mouseY / scale; + for (PositionedWidget hudWidget : layer.builder().getRendered()) { + if (hudWidget.widget.isMouseOver(scaledMouseX, scaledMouseY)) { + hoveredWidget = hudWidget; + break; + } + } + + Matrix3x2f scaleMatrix = new Matrix3x2f().scale(scale); + if (hoveredWidget != null) { + ScreenRectangle rect = hoveredWidget.widget.getRectangle().transformAxisAligned(scaleMatrix); + GuiHelper.border(context, rect.left() - 1, rect.top() - 1, rect.width() + 2, rect.height() + 2, CommonColors.YELLOW); } + if (selectedWidget != null) { + ScreenRectangle rect = selectedWidget.widget.getRectangle().transformAxisAligned(scaleMatrix); + GuiHelper.border(context, rect.left() - 1, rect.top() - 1, rect.width() + 2, rect.height() + 2, CommonColors.GREEN); + } + topBarWidget.visible = selectedWidget == null || selectedWidget.widget.getY() >= 16; + + sidePanelWidget.extractRenderState(context, mouseX, mouseY, deltaTicks); + // Render on top of everything + topBarWidget.extractRenderState(context, mouseX, mouseY, deltaTicks); + addWidgetWidget.extractRenderState(context, mouseX, mouseY, deltaTicks); } - private void parseLocation() { - boolean b = titleLowercase.startsWith("widgets "); - if (widgetsListTab != null) widgetsListTab.setShouldShowCustomWidgetEntries(b); - String trim = this.titleLowercase - .replace("widgets in", "") - .replace("widgets on", "") - .trim(); - - if (nameToLocation.containsKey(trim)) { - setCurrentLocation(nameToLocation.get(trim)); - } else { - //currentLocation = Utils.getLocation(); - if (b) - LOGGER.warn("[Skyblocker] Couldn't find location for {} (trimmed: {})", this.titleLowercase, trim); + @Override + public boolean mouseDragged(MouseButtonEvent click, double deltaX, double deltaY) { + if (super.mouseDragged(click, deltaX, deltaY)) return true; + double mouseX = click.x(); + double mouseY = click.y(); + if (selectedWidget != null && !selectedWidget.fromTab && dragRelative != null) { + PositionRule oldRule = selectedWidget.rule; + mouseX /= TabHud.getScaleFactor(); + mouseY /= TabHud.getScaleFactor(); + + PositionRule.Point parentPoint; + PositionRule.Point thisPoint; + if (autoAnchor && oldRule.parent().isEmpty()) { + parentPoint = thisPoint = getPoint(selectedWidget.widget, (int) mouseX - dragRelative.x(), (int) mouseY - dragRelative.y()); + } else { + parentPoint = oldRule.parentPoint(); + thisPoint = oldRule.thisPoint(); + } + String newParent = null; + OptionalInt relativeX = OptionalInt.empty(); + OptionalInt relativeY = OptionalInt.empty(); + if (minecraft.hasShiftDown()) { + final ScreenDirection[] directions = ScreenDirection.values(); + + ScreenRectangle selectedRect = new ScreenRectangle((int) mouseX - dragRelative.x(), (int) mouseY - dragRelative.y(), selectedWidget.widget.getWidth(), selectedWidget.widget.getHeight()); + ScreenRectangle[] selectedSnapBoxes = Arrays.stream(directions).map(dir -> getBorder(selectedRect, dir)).toArray(ScreenRectangle[]::new); + + int distanceToCursor = Integer.MAX_VALUE; + for (PositionedWidget positionedWidget : layer.builder().getRendered()) { + if (positionedWidget == selectedWidget) continue; + if (selectedWidget.widget.getInternalID().equals(positionedWidget.rule.parent().orElse(null))) continue; + ScreenRectangle otherRect = positionedWidget.widget.getRectangle(); + for (ScreenDirection direction : directions) { + ScreenRectangle otherSnapBox = getBorder(otherRect, direction); + ScreenRectangle selectedSnapBox = selectedSnapBoxes[direction.getOpposite().ordinal()]; + + int dist = direction.getAxis() == ScreenAxis.HORIZONTAL ? Math.abs((int) mouseX - otherSnapBox.getBorder(direction).getCenterInAxis(ScreenAxis.HORIZONTAL)) : Math.abs((int) mouseY - otherSnapBox.getBorder(direction).getCenterInAxis(ScreenAxis.VERTICAL)); + if (!selectedSnapBox.overlaps(otherSnapBox) || dist > distanceToCursor) continue; + PositionRule.Point point = getPoint(positionedWidget.widget); + switch (direction) { + case LEFT -> { + relativeX = OptionalInt.of(-2); + relativeY = OptionalInt.empty(); + parentPoint = new PositionRule.Point(point.verticalPoint(), PositionRule.HorizontalPoint.LEFT); + thisPoint = new PositionRule.Point(point.verticalPoint(), PositionRule.HorizontalPoint.RIGHT); + } + case RIGHT -> { + relativeX = OptionalInt.of(1); + relativeY = OptionalInt.empty(); + parentPoint = new PositionRule.Point(point.verticalPoint(), PositionRule.HorizontalPoint.RIGHT); + thisPoint = new PositionRule.Point(point.verticalPoint(), PositionRule.HorizontalPoint.LEFT); + } + case UP -> { + relativeY = OptionalInt.of(-2); + relativeX = OptionalInt.empty(); + parentPoint = new PositionRule.Point(PositionRule.VerticalPoint.TOP, point.horizontalPoint()); + thisPoint = new PositionRule.Point(PositionRule.VerticalPoint.BOTTOM, point.horizontalPoint()); + } + case DOWN -> { + relativeY = OptionalInt.of(1); + relativeX = OptionalInt.empty(); + parentPoint = new PositionRule.Point(PositionRule.VerticalPoint.BOTTOM, point.horizontalPoint()); + thisPoint = new PositionRule.Point(PositionRule.VerticalPoint.TOP, point.horizontalPoint()); + } + } + newParent = positionedWidget.widget.getInternalID(); + distanceToCursor = dist; + } + } + } + ScreenPosition startPosition = WidgetPositioner.getStartPosition(newParent, getScreenWidth(), getScreenHeight(), parentPoint); + selectedWidget.rule = new PositionRule( + Optional.ofNullable(newParent), + parentPoint, + thisPoint, + relativeX.orElse((int) mouseX - dragRelative.x() - startPosition.x() + (int) (selectedWidget.widget.getWidth() * thisPoint.horizontalPoint().getPercentage())), + relativeY.orElse((int) mouseY - dragRelative.y() - startPosition.y() + (int) (selectedWidget.widget.getHeight() * thisPoint.verticalPoint().getPercentage())) + ); + updateBuilderPositions(); + ScreenRectangle sidePanel = new ScreenRectangle(sidePanelWidget.getX(), sidePanelWidget.getY(), sidePanelWidget.getWidth(), sidePanelWidget.getHeight()); + ScreenRectangle selected = new ScreenRectangle(selectedWidget.widget.getX(), selectedWidget.widget.getY(), selectedWidget.widget.getWidth(), selectedWidget.widget.getHeight()); + if (sidePanelWidget.isOpen() && sidePanel.overlaps(selected)) { + sidePanelWidget.close(); + openPanelAfterDragging = true; + } + return true; } + return false; } - public @Nullable ChestMenu getHandler() { - return handler; + private PositionRule.Point getPoint(HudWidget widget) { + return getPoint(widget, widget.getX(), widget.getY()); } - private @Nullable ItemStack slotThirteenBacklog = null; + private PositionRule.Point getPoint(HudWidget widget, int x, int y) { + int widgetCenterX = x + widget.getWidth() / 2 - getScreenWidth() / 2; + int widgetCenterY = y + widget.getHeight() / 2 - getScreenHeight() / 2; + PositionRule.HorizontalPoint hPoint = widgetCenterX < -25 ? PositionRule.HorizontalPoint.LEFT : widgetCenterX > 25 ? PositionRule.HorizontalPoint.RIGHT : PositionRule.HorizontalPoint.CENTER; + PositionRule.VerticalPoint vPoint = widgetCenterY < -25 ? PositionRule.VerticalPoint.TOP : widgetCenterY > 25 ? PositionRule.VerticalPoint.BOTTOM : PositionRule.VerticalPoint.CENTER; + return new PositionRule.Point(vPoint, hPoint); + } @Override - public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack stack) { - if (this.handler == null) return; - if (slotId == 4) { - tabPreview = stack.is(Items.PLAYER_HEAD); - } - if (widgetsListTab == null) { - if (slotId == 13) slotThirteenBacklog = stack.copy(); - return; + public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { + if (super.mouseClicked(click, doubled)) return true; + double mouseX = click.x(); + double mouseY = click.y(); + if (selectWidgetPrompt != null) { + if (hoveredWidget != null && !selectWidgetPrompt.allowItself() && hoveredWidget.equals(selectedWidget)) return true; + selectWidgetPrompt.callback().accept(hoveredWidget == null ? null : hoveredWidget.widget); + selectWidgetPrompt = null; + sidePanelWidget.open(); + return true; } - if (slotId == 13) { - if (stack.is(Items.HOPPER)) { - widgetsListTab.hopper(stack.skyblocker$getLoreStrings()); - } else { - widgetsListTab.hopper(null); + if (hoveredWidget == null) { + if (sidePanelWidget.isOpen()) sidePanelWidget.close(); + selectedWidget = null; + if (click.button() == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { + List availableWidgets = new ArrayList<>(WidgetManager.getWidgetsAvailableIn(currentLocation)); + availableWidgets.removeAll(layer.builder().getRendered().stream().map(w -> w.widget).toList()); // remove already present widgets + addWidgetWidget.openWith(availableWidgets); + addWidgetWidget.setX(Math.clamp((int) mouseX, 5, width - addWidgetWidget.getWidth() - 5)); + addWidgetWidget.setY(Math.clamp((int) mouseY, 5, height - addWidgetWidget.getHeight() - 5)); + addWidgetWidget.refreshScrollAmount(); // refreshes the positions of the entries } + return true; + } + if (!hoveredWidget.equals(selectedWidget)) { + selectedWidget = hoveredWidget; + } + mouseX /= TabHud.getScaleFactor(); + mouseY /= TabHud.getScaleFactor(); + if (!selectedWidget.fromTab) dragRelative = new ScreenPosition((int) (mouseX - selectedWidget.widget.getX()), (int) (mouseY - selectedWidget.widget.getY())); + if (click.button() == GLFW.GLFW_MOUSE_BUTTON_RIGHT && (!sidePanelWidget.isOpen() || !selectedWidget.equals(sidePanelWidget.getPositionedWidget()))) { + openSidePanel(); + } else if (click.button() == GLFW.GLFW_MOUSE_BUTTON_LEFT && sidePanelWidget.isOpen() && !selectedWidget.equals(sidePanelWidget.getPositionedWidget())) { + openSidePanel(); } - if (slotId > (titleLowercase.startsWith("tablist widgets") ? 9 : 18) && slotId < this.handler.getRowCount() * 9 - 9 || slotId == 45 || slotId == 53 || slotId == 50 || slotId == 51) { - widgetsListTab.onSlotChange(slotId, stack); + return true; + } + + @Override + public boolean mouseReleased(MouseButtonEvent click) { + dragRelative = null; + if (openPanelAfterDragging) { + openPanelAfterDragging = false; + openSidePanel(); } + return super.mouseReleased(click); } - private void getBackOnTheScreenYouScallywagsAngryEmoji() { - if (isDragging() || !(tabManager.getCurrentTab() instanceof PreviewTab tab)) return; - ScreenBuilder builder = WidgetManager.getScreenBuilder(tab.getCurrentLocation()); - List widgets = builder.getHudWidgets(tab.getCurrentScreenLayer()); - boolean needReposition = false; - float scale = SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudScale / 100.f; - int padding = 2; - ScreenRectangle screenRect = new ScreenRectangle(padding, padding, (int) (width / scale) - padding * 2, (int) (height / scale) - padding * 2); - for (HudWidget widget : widgets) { - PositionRule rule = builder.getPositionRule(widget.getInternalID()); - if (rule != null && !widget.getRectangle().intersects(screenRect)) { - needReposition = true; - builder.setPositionRule(widget.getInternalID(), new PositionRule( - "screen", - PositionRule.Point.DEFAULT, - PositionRule.Point.DEFAULT, - 5, - 5, - rule.screenLayer() - )); + @Override + public boolean keyPressed(KeyEvent keyInput) { + if (selectedWidget != null) { + if (selectedWidget == hoveredWidget) { + boolean move = true; + int x = 0, y = 0; + if (keyInput.isLeft()) x = -1; + else if (keyInput.isRight()) x = 1; + else if (keyInput.isUp()) y = -1; + else if (keyInput.isDown()) y = 1; + else move = false; + + if (move) { + PositionRule oldRule = selectedWidget.rule; + selectedWidget.rule = new PositionRule( + oldRule.parent(), + oldRule.parentPoint(), + oldRule.thisPoint(), + oldRule.relativeX() + x, + oldRule.relativeY() + y + ); + updateBuilderPositions(); + return true; + } + } + if (keyInput.key() == GLFW.GLFW_KEY_DELETE) { + removeWidget(selectedWidget); + return true; } } - if (needReposition) tab.updateWidgets(); + return super.keyPressed(keyInput); + } + + private void openSidePanel() { + if (selectedWidget == null) return; + boolean rightSide = selectedWidget.widget.getX() + selectedWidget.widget.getWidth() / 2 < getScreenWidth() / 2; + sidePanelWidget.open(selectedWidget, rightSide); } @Override public void tick() { - super.tick(); - getBackOnTheScreenYouScallywagsAngryEmoji(); - if (noHandler) return; - if (slotThirteenBacklog != null && widgetsListTab != null) { - widgetsListTab.hopper(slotThirteenBacklog.skyblocker$getLoreStrings()); - slotThirteenBacklog = null; - } - assert this.minecraft.player != null; - if (!this.minecraft.player.isAlive() || this.minecraft.player.isRemoved()) { - this.minecraft.player.closeContainer(); + if (selectedWidget == null && sidePanelWidget.isOpen()) sidePanelWidget.close(); + for (PositionedWidget widget : layer.builder().getRendered()) { + if (widget.widget.getRectangle().intersects(new ScreenRectangle(0, 0, getScreenWidth(), getScreenHeight())) || widget.rule.parent().isPresent()) continue; + widget.rule = PositionRule.DEFAULT; + updateBuilderPositions(); } } @Override - public void onClose() { - if (handler != null) { - assert this.minecraft.player != null; - this.minecraft.player.closeContainer(); - super.onClose(); - } else { - minecraft.setScreen(parent); + public void removed() { + layer.editor().serializeConfig(); + WidgetManager.SCREEN_BUILDER.hud().update(); + } + + private static ScreenRectangle getBorder(ScreenRectangle rect, ScreenDirection side) { + int extraX = rect.width() / 2; + int extraY = rect.height() / 2; + final int thickness = 5 + (side.getAxis() == ScreenAxis.HORIZONTAL ? extraX : extraY); + int i = rect.getBoundInDirection(side); + ScreenAxis otherAxis = side.getAxis().orthogonal(); + int j = rect.getBoundInDirection(otherAxis.getNegative()); + int k = rect.getLength(otherAxis); + ScreenRectangle screenRect = ScreenRectangle.of(side.getAxis(), i, j, thickness, k); + int offsetX = side.getAxis() == ScreenAxis.HORIZONTAL ? (side.isPositive() ? -extraX : -5) : 0; + int offsetY = side.getAxis() == ScreenAxis.VERTICAL ? (side.isPositive() ? -extraY : -5) : 0; + return new ScreenRectangle(screenRect.left() + offsetX, screenRect.top() + offsetY, screenRect.width(), screenRect.height()); + } + + public void promptSelectWidget(Consumer<@Nullable HudWidget> callback, boolean allowItself) { + selectWidgetPrompt = new SelectWidgetPrompt(callback, allowItself); + sidePanelWidget.close(); + } + + public void removeWidget(PositionedWidget widget) { + layer.editor().remove(widget); + PositionRule deleted = widget.rule; + for (PositionedWidget positionedWidget : layer.builder().getRendered()) { + PositionRule rule = positionedWidget.rule; + if (rule.parent().isEmpty()) continue; + if (rule.parent().get().equals(widget.widget.getInternalID())) { + positionedWidget.rule = new PositionRule( + deleted.parent(), + deleted.parentPoint(), + rule.thisPoint(), + deleted.relativeX() + rule.relativeX(), + deleted.relativeY() + rule.relativeY() + ); + } + } + Location location = getCurrentLocation(); + Optional> locationsWithCopies = WidgetManager.getCopyTracker() + .get(currentScreenLayer) + .get(widget.widget.getInternalID()) + .flatMap(sets -> sets.whereHas(location)); + locationsWithCopies.ifPresent(set -> openPopup(screen -> + new PopupScreen.Builder(screen, Component.literal("Delete Copies")) + .addMessage(Component.literal("Do you want to delete copies of this widget in other locations?")) + .addButton(CommonComponents.GUI_YES, popup -> { + set.clear(); + removeCopies(set, widget.widget.getInternalID()); + popup.onClose(); + }) + .addButton(CommonComponents.GUI_NO, popup -> { + set.remove(location); + popup.onClose(); + }) + .build() + )); + if (selectedWidget == widget) { + sidePanelWidget.close(); + selectedWidget = null; } } - @Override - public void dataChanged(AbstractContainerMenu handler, int property, int value) {} + private void removeCopies(Set locations, String widgetId) { + for (Location location : locations) { + LayerConfig config = WidgetManager.getScreenConfig(location).get(currentScreenLayer); + WidgetConfig deletedConfig = config.widgets.remove(widgetId); + if (deletedConfig == null) continue; + // fix up widgets that had the deleted one as parent + for (Map.Entry entry : config.widgets.entrySet()) { + WidgetConfig widgetConfig = entry.getValue(); + Optional posOpt = widgetConfig.position(); + if (posOpt.isEmpty()) continue; + PositionRule rule = posOpt.get(); + if (rule.parent().filter(widgetId::equals).isEmpty()) continue; + PositionRule newRule; + if (deletedConfig.position().isPresent()) { + PositionRule oldPosition = deletedConfig.position().get(); + newRule = new PositionRule( + oldPosition.parent(), + oldPosition.parentPoint(), + oldPosition.thisPoint(), + oldPosition.relativeX() + rule.relativeX(), + oldPosition.relativeY() + rule.relativeY() + ); + } else { + newRule = PositionRule.DEFAULT; + } + entry.setValue(new WidgetConfig(widgetConfig.config(), Optional.of(newRule))); + } + } + } - @Override - public void removed() { - if (handler == null) return; - overrideWidgetsScreen = false; - if (this.minecraft.player != null) { - this.handler.removed(this.minecraft.player); + private void updateBuilderPositions() { + layer.builder().updatePositions(getScreenWidth(), getScreenHeight()); + } + + public HudWidget getEditedWidget() { + if (selectedWidget == null) { + LOGGER.warn("Trying to edit selected widget but nothing is selected?", new Throwable()); + return new PlaceholderWidget(""); // this shouldn't cause issues } - handler.removeSlotListener(this); - Scheduler.INSTANCE.schedule(PlayerListManager::updateList, 1); + return selectedWidget.widget; } - @Override - public boolean isPauseScreen() { - return false; + public WidgetManager.ScreenLayer getCurrentScreenLayer() { + return currentScreenLayer; + } + + public Location getCurrentLocation() { + return currentLocation; + } + + public ScreenConfig getScreenConfig() { + return screenConfig; } - public DropdownWidget createLocationDropdown(Consumer onLocationChanged) { - List locations = Arrays.asList(ArrayUtils.removeElements(Location.values(), Location.UNKNOWN, Location.DUNGEON)); // there's already a tab for dungeons - return new DropdownWidget<>(minecraft, 0, 0, 50, 50, locations, location -> { - setCurrentLocation(location); - onLocationChanged.accept(location); - }, - locations.contains(currentLocation) ? currentLocation : Location.HUB, - isOpen -> previewTab.locationDropdownOpened(isOpen)); + public int getScreenWidth() { + return (int) (width / TabHud.getScaleFactor()); } + + public int getScreenHeight() { + return (int) (height / TabHud.getScaleFactor()); + } + + public void openPopup(Function popupCreator) { + minecraft.setScreen(popupCreator.apply(this)); + } + + private record SelectWidgetPrompt(Consumer<@Nullable HudWidget> callback, boolean allowItself) {} } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsListTab.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsListTab.java deleted file mode 100644 index 0a20c489ba7..00000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsListTab.java +++ /dev/null @@ -1,280 +0,0 @@ -package de.hysky.skyblocker.skyblock.tabhud.config; - -import de.hysky.skyblocker.skyblock.tabhud.config.entries.WidgetEntry; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.slot.BooleanSlotEntry; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.slot.DefaultSlotEntry; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.slot.EditableSlotEntry; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.slot.WidgetSlotEntry; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.slot.WidgetsListSlotEntry; -import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.scheduler.Scheduler; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectSet; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.function.Consumer; - -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.StringWidget; -import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.gui.components.tabs.Tab; -import net.minecraft.client.gui.components.toasts.SystemToast; -import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.network.chat.Component; -import net.minecraft.world.inventory.ChestMenu; -import net.minecraft.world.inventory.ContainerInput; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import org.jspecify.annotations.Nullable; - -// TODO: recommend disabling spacing and enabling wrapping -public class WidgetsListTab implements Tab { - public static final SystemToast.SystemToastId SYSTEM_TOAST_ID = new SystemToast.SystemToastId(1_000); - - private final WidgetsElementList widgetsElementList; - private final Button back; - private final Button previousPage; - private final Button nextPage; - private final Button thirdColumnButton; - private final Button resetButton; - private final StringWidget waitingForServerText; - private final Minecraft client; - - private final Int2ObjectMap entries = new Int2ObjectOpenHashMap<>(); - private final List customWidgetEntries = new ArrayList<>(); - - private @Nullable ChestMenu handler; - private boolean waitingForServer = false; - private boolean shouldShowCustomWidgetEntries = false; - private int resetSlotId = -1; - private boolean shouldResetScroll = false; - - public void setCustomWidgetEntries(Collection entries) { - this.customWidgetEntries.clear(); - this.customWidgetEntries.addAll(entries); - widgetsElementList.updateList(); - } - - public List getCustomWidgetEntries() { - return customWidgetEntries; - } - - public ObjectSet> getEntries() { - return entries.int2ObjectEntrySet(); - } - - public WidgetsListTab(Minecraft client, @Nullable ChestMenu handler) { - widgetsElementList = new WidgetsElementList(this, client, 0, 0, 0); - this.client = client; - this.handler = handler; - back = Button.builder(Component.translatable("gui.back"), _ -> { - clickAndWaitForServer(48, 0); - this.resetScrollOnLoad(); - }).size(64, 15).build(); - widgetsElementList.setBackButton(back); - thirdColumnButton = Button.builder(Component.literal("3rd Column:"), _ -> clickAndWaitForServer(50, 0)) - .size(120, 15) - .build(); - thirdColumnButton.setTooltip(Tooltip.create(Component.literal("It is recommended to have this enabled, to have more info be displayed!"))); - previousPage = Button.builder(Component.translatable("book.page_button.previous"), _ -> { - clickAndWaitForServer(45, 0); - resetScrollOnLoad(); - }) - .size(90, 15) - .build(); - nextPage = Button.builder(Component.translatable("book.page_button.next"), _ -> { - clickAndWaitForServer(53, 0); - resetScrollOnLoad(); - }) - .size(90, 15) - .build(); - resetButton = Button.builder(Component.literal("Reset"), _ -> { - if (resetSlotId == -1) return; - clickAndWaitForServer(resetSlotId, 0); - }).size(60, 15).build(); - waitingForServerText = new StringWidget(Component.literal("Waiting for server..."), client.font); - waitingForServerText.setWidth(client.font.width(waitingForServerText.getMessage())); - if (handler == null) { - back.visible = false; - previousPage.visible = false; - nextPage.visible = false; - thirdColumnButton.visible = false; - resetButton.visible = false; - waitingForServerText.visible = false; - } - } - - @Override - public Component getTabTitle() { - return Component.literal("Widgets"); - } - - @Override - public void visitChildren(Consumer consumer) { - consumer.accept(previousPage); - consumer.accept(nextPage); - consumer.accept(thirdColumnButton); - consumer.accept(resetButton); - consumer.accept(widgetsElementList); - consumer.accept(waitingForServerText); - } - - public void resetScrollOnLoad() { - this.shouldResetScroll = true; - } - - public boolean isWaitingForServer() { - return waitingForServer; - } - - public void clickAndWaitForServer(int slot, int button) { - if (waitingForServer || handler == null) return; - if (client.gameMode == null || this.client.player == null) return; - client.gameMode.handleContainerInput(handler.containerId, slot, button, ContainerInput.PICKUP, this.client.player); - waitingForServer = true; - waitingForServerText.visible = true; - } - - public void shiftClickAndWaitForServer(int slot, int button) { - if (waitingForServer || handler == null) return; - if (client.gameMode == null || this.client.player == null) return; - client.gameMode.handleContainerInput(handler.containerId, slot, button, ContainerInput.QUICK_MOVE, this.client.player); - // When moving a widget down it gets stuck sometimes - Scheduler.INSTANCE.schedule(() -> { - this.waitingForServer = false; - waitingForServerText.visible = false; - }, 4); - waitingForServer = true; - waitingForServerText.visible = true; - } - - public void updateHandler(@Nullable ChestMenu newHandler) { - this.handler = newHandler; - back.visible = handler != null; - entries.clear(); - widgetsElementList.updateList(); - resetButton.visible = false; - if (this.shouldResetScroll) { - this.shouldResetScroll = false; - this.widgetsElementList.setScrollAmount(0); - } - } - - public void hopper(@Nullable List hopperTooltip) { - if (hopperTooltip == null) { - widgetsElementList.setEditingPosition(false, -1, -1); - return; - } - int start = -1; - int editing = 1; - int end = -1; - - for (int i = 0; i < hopperTooltip.size(); i++) { - String string = hopperTooltip.get(i); - if (string.contains("▶")) { - if (start == -1) start = i; - if (i > end) end = i; - } - if (string.contains("(EDITING)")) { - editing = i; - } - } - widgetsElementList.setEditingPosition(true, editing - start, end - start); - } - - public void onSlotChange(int slot, ItemStack stack) { - waitingForServer = false; - waitingForServerText.visible = false; - widgetsElementList.updateList(); - switch (slot) { - case 45 -> { - widgetsElementList.setIsOnSecondPage(previousPage.visible = stack.is(Items.ARROW)); - return; - } - case 51, 53 -> { - if (slot == 53) nextPage.visible = stack.is(Items.ARROW); - if (stack.is(Items.PLAYER_HEAD)) { - String stackName = stack.getHoverName().getString().toLowerCase(Locale.ENGLISH); - if (!stackName.startsWith("reset")) return; - Component buttonText = Component.literal("Reset ALL").withStyle(style -> style.withColor(ChatFormatting.RED).withUnderlined(true)); - if (slot == 51) { - buttonText = Component.literal("Reset").withStyle(ChatFormatting.RED); - } - resetButton.visible = true; - resetButton.setMessage(buttonText); - resetSlotId = slot; - } - return; - } - case 50 -> { - thirdColumnButton.visible = stack.is(Items.BOOKSHELF) || stack.is(Items.STONE_BUTTON); - if (thirdColumnButton.visible) { - if (stack.is(Items.STONE_BUTTON)) - thirdColumnButton.setMessage(Component.literal("Apply to all locations")); - else if (ItemUtils.getLoreLineIf(stack, s -> s.contains("DISABLED")) == null) - thirdColumnButton.setMessage(Component.literal("3rd Column: ").append(WidgetsListSlotEntry.ENABLED_TEXT)); - else - thirdColumnButton.setMessage(Component.literal("3rd Column: ").append(WidgetsListSlotEntry.DISABLED_TEXT)); - } - return; - } - } - - if (stack.isEmpty() || stack.is(Items.BLACK_STAINED_GLASS_PANE)) { - entries.remove(slot); - return; - } - - String lowerCase = stack.getHoverName().getString().trim().toLowerCase(Locale.ENGLISH); - List lore = stack.skyblocker$getLoreStrings(); - String lastLowerCase = lore.getLast().toLowerCase(Locale.ENGLISH); - - WidgetsListSlotEntry entry; - if (lowerCase.startsWith("widgets on") || lowerCase.startsWith("widgets in") || lastLowerCase.contains("click to edit") || stack.is(Items.RED_STAINED_GLASS_PANE)) { - entry = new EditableSlotEntry(this, slot, stack); - } else if (lowerCase.endsWith("widget")) { - entry = new WidgetSlotEntry(this, slot, stack); - } else if (lastLowerCase.contains("enable") || lastLowerCase.contains("disable")) { - entry = new BooleanSlotEntry(this, slot, stack); - } else { - entry = new DefaultSlotEntry(this, slot, stack); - } - - entries.put(slot, entry); - } - - @Override - public void doLayout(ScreenRectangle tabArea) { - back.setPosition(16, tabArea.top() + 4); - widgetsElementList.setY(tabArea.top()); - widgetsElementList.setSize(tabArea.width(), tabArea.height() - 20); - widgetsElementList.refreshScrollAmount(); - - int bottomButtonY = widgetsElementList.getBottom() + 4; - thirdColumnButton.setPosition((tabArea.width() - thirdColumnButton.getWidth()) / 2, bottomButtonY); - previousPage.setPosition(thirdColumnButton.getX() - previousPage.getWidth() - 5, bottomButtonY); - nextPage.setPosition(thirdColumnButton.getRight() + 5, bottomButtonY); - resetButton.setPosition(tabArea.right() - resetButton.getWidth() - 4, bottomButtonY); - waitingForServerText.setPosition(tabArea.width() - waitingForServerText.getWidth() - 5, tabArea.height() - client.font.lineHeight - 2); - } - - public boolean shouldShowCustomWidgetEntries() { - return shouldShowCustomWidgetEntries; - } - - public void setShouldShowCustomWidgetEntries(boolean shouldShowCustomWidgetEntries) { - this.shouldShowCustomWidgetEntries = shouldShowCustomWidgetEntries; - } - - @Override - public Component getTabExtraNarration() { - return Component.empty(); - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/WidgetEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/WidgetEntry.java deleted file mode 100644 index 9cfcd21fbd3..00000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/WidgetEntry.java +++ /dev/null @@ -1,48 +0,0 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.entries; - -import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; -import de.hysky.skyblocker.utils.Location; -import java.util.List; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.util.CommonColors; - -public class WidgetEntry extends WidgetsListEntry { - private final HudWidget widget; - private final Location currentLocation; - private final Button enableButton; - - public WidgetEntry(HudWidget widget, Location currentLocation) { - this.widget = widget; - this.currentLocation = currentLocation; - - boolean enabled = widget.isEnabledIn(currentLocation); - enableButton = Button.builder(enabled ? ENABLED_TEXT : DISABLED_TEXT, button -> { - boolean enabledIn = this.widget.isEnabledIn(this.currentLocation); - this.widget.setEnabledIn(currentLocation, !enabledIn); - button.setMessage(!enabledIn ? ENABLED_TEXT : DISABLED_TEXT); - }) - .size(64, 12) - .build(); - } - - @Override - public void extractTooltip(GuiGraphicsExtractor graphics, int x, int y, int entryWidth, int entryHeight, int mouseX, int mouseY) { - - } - - @Override - public List children() { - return List.of(enableButton); - } - - @Override - public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float deltaTicks) { - int textY = this.getY() + (this.getHeight() - 9) / 2; - enableButton.setPosition(this.getX() + this.getWidth() - 110, this.getY() + (this.getHeight() - 12) / 2); - enableButton.extractRenderState(graphics, mouseX, mouseY, deltaTicks); - graphics.text(Minecraft.getInstance().font, widget.getDisplayName(), this.getX() + 2, textY, CommonColors.WHITE); - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/package-info.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/package-info.java deleted file mode 100644 index b4e708c1b58..00000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package de.hysky.skyblocker.skyblock.tabhud.config.entries; - -import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/package-info.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/package-info.java deleted file mode 100644 index f0f0061a460..00000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package de.hysky.skyblocker.skyblock.tabhud.config.entries.slot; - -import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsElementList.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/WidgetsElementList.java similarity index 90% rename from src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsElementList.java rename to src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/WidgetsElementList.java index 37461dc08dd..c2035d68f74 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/WidgetsElementList.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/WidgetsElementList.java @@ -1,8 +1,8 @@ -package de.hysky.skyblocker.skyblock.tabhud.config; +package de.hysky.skyblocker.skyblock.tabhud.config.list; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.WidgetsListEntry; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.slot.WidgetSlotEntry; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.slot.WidgetsListSlotEntry; +import de.hysky.skyblocker.skyblock.tabhud.config.list.entries.WidgetsListEntry; +import de.hysky.skyblocker.skyblock.tabhud.config.list.entries.slot.WidgetSlotEntry; +import de.hysky.skyblocker.skyblock.tabhud.config.list.entries.slot.WidgetsListSlotEntry; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; @@ -27,7 +27,7 @@ public class WidgetsElementList extends ContainerObjectSelectionList[] previewColumns = new List[3]; + + private final Int2ObjectMap entries = new Int2ObjectOpenHashMap<>(); + + private ChestMenu handler; + private boolean waitingForServer = false; + private int resetSlotId = -1; + private boolean shouldResetScroll = false; + + private String titleLowercase; + private boolean overflowing = false; + private boolean previewVisible = false; + private boolean thirdColumnEnabled = false; + + public ObjectSet> getEntries() { + return entries.int2ObjectEntrySet(); + } + + public WidgetsListScreen(ChestMenu handler, Component name) { + super(name); + widgetsElementList = new WidgetsElementList(this, minecraft, 0, 0, 0); + this.handler = handler; + titleLowercase = name.getString().toLowerCase(Locale.ENGLISH); + this.handler.addSlotListener(this); + } + + @Init + public static void initCommands() { + ClientCommandRegistrationCallback.EVENT.register((dispatcher, _) -> { + dispatcher.register(ClientCommands.literal(SkyblockerMod.NAMESPACE) + .then(ClientCommands.literal("hypixelWidgets").executes(_ -> { + if (Utils.isOnSkyblock()) { + overrideWidgetsScreen = true; + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/widgets", true); + } + return Command.SINGLE_SUCCESS; + }))); + }); + } + + @Override + protected void init() { + super.init(); + layout.addToHeader(headerLayout); + widgetsElementList = layout.addToContents(new WidgetsElementList(this, minecraft, 0, 0, 0)); + back = headerLayout.addChild(Button.builder(Component.translatable("gui.back"), _ -> { + clickAndWaitForServer(48, 0); + this.resetScrollOnLoad(); + }) + .size(60, 15) + .build(), l -> l.alignHorizontallyLeft().paddingLeft(PADDING)); + thirdColumnButton = headerLayout.addChild(Button.builder(Component.translatable("gui.back"), _ -> clickAndWaitForServer(50, 0)) + .size(120, 15) + .build(), l -> l.alignHorizontallyRight().paddingRight(PADDING)); + infoText = headerLayout.addChild(new MultiLineTextWidget(Component.empty(), font).setCentered(true), l -> l.paddingVertical(4)); + thirdColumnButton.setTooltip(Tooltip.create(Component.literal("It is recommended to have this enabled, to have more info be displayed!"))); + LinearLayout footer = LinearLayout.horizontal().spacing(10); + previousPage = footer.addChild(Button.builder(Component.translatable("book.page_button.previous"), _ -> clickAndWaitForServer(45, 0)) + .size(100, 15) + .build()); + nextPage = footer.addChild(Button.builder(Component.translatable("book.page_button.next"), _ -> clickAndWaitForServer(53, 0)) + .size(100, 15) + .build()); + layout.addToFooter(footer); + waitingForServerText = new StringWidget(Component.literal("Waiting for server..."), font); + waitingForServerText.setWidth(font.width(waitingForServerText.getMessage())); + resetButton = layout.addToFooter(Button.builder(Component.literal("Reset"), _ -> { + if (resetSlotId == -1) return; + clickAndWaitForServer(resetSlotId, 0); + }).size(60, 15).build(), l -> l.alignHorizontallyRight().paddingRight(PADDING)); + layout.visitWidgets(this::addRenderableWidget); + addRenderableWidget(waitingForServerText); + repositionElements(); + } + + @Override + protected void repositionElements() { + infoText.setMaxWidth(width - thirdColumnButton.getWidth() * 2 - PADDING * 3); + + headerLayout.setMinWidth(width); + headerLayout.arrangeElements(); + layout.setHeaderHeight(headerLayout.getHeight()); + widgetsElementList.updateSize(width, layout); + layout.arrangeElements(); + waitingForServerText.setPosition(width - waitingForServerText.getWidth() - 5, height - font.lineHeight - 2); + } + + public void resetScrollOnLoad() { + this.shouldResetScroll = true; + } + + public boolean isWaitingForServer() { + return waitingForServer; + } + + public void clickAndWaitForServer(int slot, int button) { + if (waitingForServer) return; + if (minecraft.gameMode == null || this.minecraft.player == null) return; + minecraft.gameMode.handleContainerInput(handler.containerId, slot, button, ContainerInput.PICKUP, this.minecraft.player); + waitingForServer = true; + waitingForServerText.visible = true; + } + + public void shiftClickAndWaitForServer(int slot, int button) { + if (waitingForServer) return; + if (minecraft.gameMode == null || this.minecraft.player == null) return; + minecraft.gameMode.handleContainerInput(handler.containerId, slot, button, ContainerInput.QUICK_MOVE, this.minecraft.player); + // When moving a widget down it gets stuck sometimes + Scheduler.INSTANCE.schedule(() -> { + this.waitingForServer = false; + waitingForServerText.visible = false; + }, 4); + waitingForServer = true; + waitingForServerText.visible = true; + } + + public void updateHandler(ChestMenu newHandler, Component name) { + titleLowercase = name.getString().toLowerCase(Locale.ENGLISH); + this.handler.removeSlotListener(this); + newHandler.addSlotListener(this); + this.handler = newHandler; + back.visible = true; + entries.clear(); + widgetsElementList.updateList(); + resetButton.visible = false; + if (this.shouldResetScroll) { + this.shouldResetScroll = false; + this.widgetsElementList.setScrollAmount(0); + } + } + + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractRenderState(graphics, mouseX, mouseY, a); + if (minecraft.hasControlDown()) { + int max = Arrays.stream(previewColumns).flatMap(Collection::stream).mapToInt(font::width).max().orElse(0); + if (max <= 0) return; + int colWidth = Math.min(previewColumns.length * max, width - 20) / previewColumns.length; + int lineCount = 0; + for (List column : previewColumns) { + lineCount = Math.max(lineCount, column.size()); + } + int columnSpacing = 4; + int totalWidth = colWidth * previewColumns.length + columnSpacing * (previewColumns.length - 1); + int totalHeight = lineCount * font.lineHeight; + int startX = (width - totalWidth) / 2; + int startY = (height - totalHeight) / 2; + + graphics.fill(startX - 5, startY - 5, startX + totalWidth + 5, startY + totalHeight + 5, 0xA0_00_00_00); + for (int i = 0; i < previewColumns.length; i++) { + int colX = startX + i * (colWidth + columnSpacing); + graphics.fill(colX, startY, colX + colWidth, startY + totalHeight, 0x20FFFFFF); + List column = previewColumns[i]; + if (column.isEmpty()) { + List split = font.split(Component.translatable("skyblocker.widgetsList.playerColumn"), colWidth); + for (int j = 0; j < split.size(); j++) { + graphics.text(font, split.get(j), colX, startY + j * font.lineHeight, -1); + } + } else { + for (int j = 0; j < column.size(); j++) { + Component component = column.get(j); + FormattedCharSequence trimmed = font.width(component) >= colWidth ? ComponentRenderUtils.clipText(component, font, colWidth) : component.getVisualOrderText(); + graphics.text(font, trimmed, colX, startY + j * font.lineHeight, -1); + } + } + } + } + } + + public void hopper(@Nullable List hopperTooltip) { + if (hopperTooltip == null) { + widgetsElementList.setEditingPosition(false, -1, -1); + return; + } + int start = -1; + int editing = 1; + int end = -1; + + for (int i = 0; i < hopperTooltip.size(); i++) { + String string = hopperTooltip.get(i); + if (string.contains("▶")) { + if (start == -1) start = i; + if (i > end) end = i; + } + if (string.contains("(EDITING)")) { + editing = i; + } + } + widgetsElementList.setEditingPosition(true, editing - start, end - start); + } + + public void onSlotChange(int slot, ItemStack stack) { + waitingForServer = false; + waitingForServerText.visible = false; + widgetsElementList.updateList(); + switch (slot) { + case 45 -> { + widgetsElementList.setIsOnSecondPage(previousPage.visible = stack.is(Items.ARROW)); + return; + } + case 51, 53 -> { + if (slot == 53) nextPage.visible = stack.is(Items.ARROW); + if (stack.is(Items.PLAYER_HEAD)) { + String stackName = stack.getHoverName().getString().toLowerCase(Locale.ENGLISH); + if (!stackName.startsWith("reset")) return; + Component buttonText = Component.literal("Reset ALL").withStyle(style -> style.withColor(ChatFormatting.RED).withUnderlined(true)); + if (slot == 51) { + buttonText = Component.literal("Reset").withStyle(ChatFormatting.RED); + } + resetButton.visible = true; + resetButton.setMessage(buttonText); + resetSlotId = slot; + } + return; + } + case 50 -> { + thirdColumnButton.visible = stack.is(Items.BOOKSHELF) || stack.is(Items.STONE_BUTTON); + if (thirdColumnButton.visible) { + if (stack.is(Items.STONE_BUTTON)) + thirdColumnButton.setMessage(Component.literal("Apply to all locations")); + else if (ItemUtils.getLoreLineIf(stack, s -> s.contains("DISABLED")) == null) { + thirdColumnEnabled = true; + thirdColumnButton.setMessage(Component.literal("3rd Column: ").append(WidgetsListSlotEntry.ENABLED_TEXT)); + updateInfoText(); + } else { + thirdColumnEnabled = false; + thirdColumnButton.setMessage(Component.literal("3rd Column: ").append(WidgetsListSlotEntry.DISABLED_TEXT)); + updateInfoText(); + } + } + return; + } + } + + if (stack.isEmpty() || stack.is(Items.BLACK_STAINED_GLASS_PANE)) { + entries.remove(slot); + return; + } + + String lowerCase = stack.getHoverName().getString().trim().toLowerCase(Locale.ENGLISH); + List lore = stack.skyblocker$getLoreStrings(); + String lastLowerCase = lore.getLast().toLowerCase(Locale.ENGLISH); + + WidgetsListSlotEntry entry; + if (lowerCase.startsWith("widgets on") || lowerCase.startsWith("widgets in") || lastLowerCase.contains("click to edit") || stack.is(Items.RED_STAINED_GLASS_PANE)) { + entry = new EditableSlotEntry(this, slot, stack); + } else if (lowerCase.endsWith("widget")) { + entry = new WidgetSlotEntry(this, slot, stack); + } else if (lastLowerCase.contains("enable") || lastLowerCase.contains("disable")) { + entry = new BooleanSlotEntry(this, slot, stack); + } else { + entry = new DefaultSlotEntry(this, slot, stack); + } + + entries.put(slot, entry); + } + + @Override + public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack stack) { + if (slotId >= 3 && slotId <= 5) { + if (!stack.getHoverName().getString().endsWith("Widgets Preview")) { + previewVisible = false; + updateInfoText(); + return; + } else { + previewVisible = true; + } + if (slotId == 5) { + List list = stack.skyblocker$getLoreStrings().stream() + .map(s -> s.substring(s.indexOf('⬛') + 1).isBlank()) + .toList(); + overflowing = list.size() >= 2 && !(list.getLast() && list.get(list.size() - 2)); + updateInfoText(); + } + //noinspection deprecation + previewColumns[slotId - 3] = ItemUtils.getLore(stack).stream() + .filter(c -> c.getString().startsWith("⬛")) + .toList(); + + } + if (slotId == 13) { + if (stack.is(Items.HOPPER)) { + hopper(stack.skyblocker$getLoreStrings()); + } else { + hopper(null); + } + } + if (slotId > (titleLowercase.startsWith("tablist widgets") ? 9 : 18) && slotId < this.handler.getRowCount() * 9 - 9 || slotId == 45 || slotId == 53 || slotId == 50 || slotId == 51) { + onSlotChange(slotId, stack); + } + } + + private void updateInfoText() { + if (!previewVisible) { + infoText.setMessage(CommonComponents.EMPTY); + repositionElements(); + return; + } + MutableComponent text = Component.translatable("skyblocker.widgetsList.info.preview"); + if (overflowing) { + MutableComponent overflowWarning = Component.translatable("skyblocker.widgetsList.info.overflowWarning").withStyle(ChatFormatting.RED).append("\n"); + if (!thirdColumnEnabled) overflowWarning.append(Component.translatable("skyblocker.widgetsList.info.overflowWarning.columnTip")).append("\n"); + overflowWarning.append(Component.translatable("skyblocker.widgetsList.info.overflowWarning.wrappingSpacingTip")); + text.append("\n"); + text.append(overflowWarning); + } + infoText.setMessage(text); + repositionElements(); + } + + @Override + public void removed() { + if (this.minecraft.player != null) this.handler.removed(this.minecraft.player); + this.handler.removeSlotListener(this); + } + + @Override + public void onClose() { + if (this.minecraft.player != null) this.minecraft.player.closeContainer(); + overrideWidgetsScreen = false; + } + + @Override + public void dataChanged(AbstractContainerMenu container, int id, int value) {} +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/WidgetsListEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/WidgetsListEntry.java similarity index 96% rename from src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/WidgetsListEntry.java rename to src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/WidgetsListEntry.java index 1c9ddd5aa28..73d7af0db70 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/WidgetsListEntry.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/WidgetsListEntry.java @@ -1,4 +1,4 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.entries; +package de.hysky.skyblocker.skyblock.tabhud.config.list.entries; import java.util.List; import net.minecraft.ChatFormatting; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/package-info.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/package-info.java new file mode 100644 index 00000000000..d0d04427621 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.hysky.skyblocker.skyblock.tabhud.config.list.entries; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/BooleanSlotEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/BooleanSlotEntry.java similarity index 88% rename from src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/BooleanSlotEntry.java rename to src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/BooleanSlotEntry.java index b6b782c5cf3..aefac266983 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/BooleanSlotEntry.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/BooleanSlotEntry.java @@ -1,6 +1,6 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.entries.slot; +package de.hysky.skyblocker.skyblock.tabhud.config.list.entries.slot; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsListTab; +import de.hysky.skyblocker.skyblock.tabhud.config.list.WidgetsListScreen; import de.hysky.skyblocker.utils.ItemUtils; import java.util.List; import java.util.Locale; @@ -14,7 +14,7 @@ public class BooleanSlotEntry extends WidgetsListSlotEntry { private final Button enableButton; - public BooleanSlotEntry(WidgetsListTab parent, int slotId, ItemStack icon) { + public BooleanSlotEntry(WidgetsListScreen parent, int slotId, ItemStack icon) { super(parent, slotId, icon); boolean enabled = !icon.skyblocker$getLoreStrings().getLast().toLowerCase(Locale.ENGLISH).contains("enable"); enableButton = Button.builder(enabled ? ENABLED_TEXT : DISABLED_TEXT, _ -> this.parent.clickAndWaitForServer(this.slotId, 0)) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/DefaultSlotEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/DefaultSlotEntry.java similarity index 89% rename from src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/DefaultSlotEntry.java rename to src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/DefaultSlotEntry.java index 2c005357792..f42ae88434f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/DefaultSlotEntry.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/DefaultSlotEntry.java @@ -1,6 +1,6 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.entries.slot; +package de.hysky.skyblocker.skyblock.tabhud.config.list.entries.slot; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsListTab; +import de.hysky.skyblocker.skyblock.tabhud.config.list.WidgetsListScreen; import de.hysky.skyblocker.utils.ItemUtils; import java.util.List; import net.minecraft.client.Minecraft; @@ -14,7 +14,7 @@ public class DefaultSlotEntry extends WidgetsListSlotEntry { private final Button leftClick; private final Button rightClick; - public DefaultSlotEntry(WidgetsListTab parent, int slotId, ItemStack icon) { + public DefaultSlotEntry(WidgetsListScreen parent, int slotId, ItemStack icon) { super(parent, slotId, icon); leftClick = Button.builder(Component.literal("LEFT"), _ -> this.parent.clickAndWaitForServer(this.slotId, 0)) .size(32, 12) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/EditableSlotEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/EditableSlotEntry.java similarity index 90% rename from src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/EditableSlotEntry.java rename to src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/EditableSlotEntry.java index daf4b7f659d..029c82ca294 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/EditableSlotEntry.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/EditableSlotEntry.java @@ -1,6 +1,6 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.entries.slot; +package de.hysky.skyblocker.skyblock.tabhud.config.list.entries.slot; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsListTab; +import de.hysky.skyblocker.skyblock.tabhud.config.list.WidgetsListScreen; import de.hysky.skyblocker.utils.ItemUtils; import java.util.List; import net.minecraft.client.Minecraft; @@ -17,7 +17,7 @@ public class EditableSlotEntry extends WidgetsListSlotEntry { private final Button editButton; private final boolean locked; - public EditableSlotEntry(WidgetsListTab parent, int slotId, ItemStack icon) { + public EditableSlotEntry(WidgetsListScreen parent, int slotId, ItemStack icon) { super(parent, slotId, icon); editButton = Button.builder(Component.literal("EDIT"), _ -> { this.parent.clickAndWaitForServer(this.slotId, 0); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/WidgetSlotEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/WidgetSlotEntry.java similarity index 93% rename from src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/WidgetSlotEntry.java rename to src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/WidgetSlotEntry.java index 4372c5cf98b..6c9ac3b6bad 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/WidgetSlotEntry.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/WidgetSlotEntry.java @@ -1,7 +1,7 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.entries.slot; +package de.hysky.skyblocker.skyblock.tabhud.config.list.entries.slot; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsElementList; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsListTab; +import de.hysky.skyblocker.skyblock.tabhud.config.list.WidgetsElementList; +import de.hysky.skyblocker.skyblock.tabhud.config.list.WidgetsListScreen; import de.hysky.skyblocker.utils.ItemUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; @@ -23,7 +23,7 @@ public class WidgetSlotEntry extends WidgetsListSlotEntry { private final Button enableButton; private final boolean alwaysEnabled; - public WidgetSlotEntry(WidgetsListTab parent, int slotId, ItemStack icon) { + public WidgetSlotEntry(WidgetsListScreen parent, int slotId, ItemStack icon) { super(parent, slotId, icon); editButton = Button.builder(Component.literal("EDIT"), _ -> { this.parent.clickAndWaitForServer(this.slotId, 1); @@ -83,7 +83,7 @@ public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY } private static void addToast(Component message) { - Minecraft.getInstance().getToastManager().addToast(new SystemToast(WidgetsListTab.SYSTEM_TOAST_ID, message, null)); + Minecraft.getInstance().getToastManager().addToast(new SystemToast(WidgetsListScreen.SYSTEM_TOAST_ID, message, null)); } @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/WidgetsListSlotEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/WidgetsListSlotEntry.java similarity index 60% rename from src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/WidgetsListSlotEntry.java rename to src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/WidgetsListSlotEntry.java index 4ce2f863da0..4e1776f5472 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/entries/slot/WidgetsListSlotEntry.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/WidgetsListSlotEntry.java @@ -1,7 +1,7 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.entries.slot; +package de.hysky.skyblocker.skyblock.tabhud.config.list.entries.slot; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsListTab; -import de.hysky.skyblocker.skyblock.tabhud.config.entries.WidgetsListEntry; +import de.hysky.skyblocker.skyblock.tabhud.config.list.WidgetsListScreen; +import de.hysky.skyblocker.skyblock.tabhud.config.list.entries.WidgetsListEntry; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.world.item.ItemStack; @@ -10,10 +10,10 @@ */ public abstract class WidgetsListSlotEntry extends WidgetsListEntry { protected final int slotId; - protected final WidgetsListTab parent; + protected final WidgetsListScreen parent; protected final ItemStack icon; - public WidgetsListSlotEntry(WidgetsListTab parent, int slotId, ItemStack icon) { + public WidgetsListSlotEntry(WidgetsListScreen parent, int slotId, ItemStack icon) { this.parent = parent; this.slotId = slotId; this.icon = icon; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/package-info.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/package-info.java new file mode 100644 index 00000000000..65b10d7c2da --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/entries/slot/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.hysky.skyblocker.skyblock.tabhud.config.list.entries.slot; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/package-info.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/package-info.java new file mode 100644 index 00000000000..2aacb412d6f --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/list/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.hysky.skyblocker.skyblock.tabhud.config.list; + +import org.jspecify.annotations.NullMarked; 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 deleted file mode 100644 index fe2f393727d..00000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java +++ /dev/null @@ -1,572 +0,0 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.preview; - -import com.mojang.authlib.GameProfile; -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.tabhud.config.DungeonsTabPlaceholder; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; -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.PlayerListManager; -import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; -import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; -import de.hysky.skyblocker.utils.EnumUtils; -import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.Location; -import de.hysky.skyblocker.utils.render.GuiHelper; -import de.hysky.skyblocker.utils.render.gui.DropdownWidget; -import de.hysky.skyblocker.utils.render.gui.NoopInput; -import de.hysky.skyblocker.utils.scheduler.Scheduler; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.gui.components.AbstractScrollArea; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.StringWidget; -import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.gui.components.tabs.Tab; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.navigation.ScreenPosition; -import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.client.input.MouseButtonEvent; -import net.minecraft.client.input.MouseButtonInfo; -import net.minecraft.client.multiplayer.PlayerInfo; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.Style; -import net.minecraft.network.chat.numbers.BlankFormat; -import net.minecraft.util.CommonColors; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.scores.Objective; -import net.minecraft.world.scores.ScoreHolder; -import net.minecraft.world.scores.Scoreboard; -import net.minecraft.world.scores.criteria.ObjectiveCriteria; -import org.jspecify.annotations.Nullable; - -public class PreviewTab implements Tab { - public static final int RIGHT_SIDE_WIDTH = 120; - - final Minecraft client; - private final PreviewWidget previewWidget; - final WidgetsConfigurationScreen parent; - private final WidgetOptionsScrollable widgetOptions; - private final Mode mode; - private final Button restorePositioning; - private WidgetManager.ScreenLayer currentScreenLayer = WidgetManager.ScreenLayer.MAIN_TAB; - private final Button[] layerButtons; - private final StringWidget textWidget; - final Objective placeHolderObjective; - final DropdownWidget locationDropdown; - - public PreviewTab(Minecraft client, WidgetsConfigurationScreen parent, Mode mode) { - this.client = client; - this.parent = parent; - this.mode = mode; - this.textWidget = new StringWidget( - Component.literal("This tab is specifically for dungeons, as it currently doesn't have hypixel's system"), - client.font - ); - - previewWidget = new PreviewWidget(this); - widgetOptions = new WidgetOptionsScrollable(); - widgetOptions.setWidth(RIGHT_SIDE_WIDTH - 10); - - WidgetManager.ScreenLayer[] values = WidgetManager.ScreenLayer.values(); - layerButtons = new Button[3]; - for (int i = 0; i < 3; i++) { - WidgetManager.ScreenLayer screenLayer = values[i]; - layerButtons[i] = Button.builder(Component.literal(screenLayer.toString()), button -> { - this.currentScreenLayer = screenLayer; - for (Button layerButton : this.layerButtons) { - layerButton.active = !layerButton.equals(button); - } - }) - .size(RIGHT_SIDE_WIDTH - 20, 15) - .build(); - if (screenLayer == currentScreenLayer) layerButtons[i].active = false; - } - - restorePositioning = Button.builder(Component.literal("Restore Positioning"), _ -> { - WidgetManager.getScreenBuilder(getCurrentLocation()).restorePositioningFromBackup(); - updateWidgets(); - onHudWidgetSelected(previewWidget.selectedWidget); - }) - .width(100) - .tooltip(Tooltip.create(Component.literal("Reset positions to before you opened this screen!"))) - .build(); - - placeHolderObjective = new Objective( - new Scoreboard(), - "temp", - ObjectiveCriteria.DUMMY, - Component.literal("SKYBLOCK"), - ObjectiveCriteria.RenderType.INTEGER, - true, - BlankFormat.INSTANCE - ); - Scoreboard scoreboard = placeHolderObjective.getScoreboard(); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("Random text!")), placeHolderObjective).set(0); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("To fill in")), placeHolderObjective).set(-1); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("The place!")), placeHolderObjective).set(-2); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("...")), placeHolderObjective).set(-3); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("yea")), placeHolderObjective).set(-4); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("so how's your")), placeHolderObjective).set(-5); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("day? great that's")), placeHolderObjective).set(-6); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("nice to hear.")), placeHolderObjective).set(-7); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("this should be")), placeHolderObjective).set(-8); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("enough lines bye")), placeHolderObjective).set(-9); - scoreboard.getOrCreatePlayerScore(createHolder(Component.literal("NEVER GONNA GIVE Y-")), placeHolderObjective).set(-10); - - locationDropdown = parent.createLocationDropdown(_ -> updateWidgets()); - updateWidgets(); - } - - private ScoreHolder createHolder(Component name) { - return new ScoreHolder() { - @Override - public String getScoreboardName() { - return name.getString().replace(' ', '_'); - } - - @Override - public @Nullable Component getDisplayName() { - return name; - } - }; - } - - public void goToLayer(WidgetManager.ScreenLayer layer) { - if (layer == WidgetManager.ScreenLayer.DEFAULT) layer = WidgetManager.ScreenLayer.HUD; - layerButtons[layer.ordinal()].onPress(NoopInput.INSTANCE); - } - - @Override - public Component getTabTitle() { - return Component.literal(mode == Mode.DUNGEON ? "Dungeons Editing" : "Preview"); - } - - @Override - public void visitChildren(Consumer consumer) { - if (mode == Mode.EDITABLE_LOCATION) consumer.accept(locationDropdown); - consumer.accept(previewWidget); - for (Button layerButton : layerButtons) { - consumer.accept(layerButton); - } - consumer.accept(widgetOptions); - consumer.accept(restorePositioning); - if (mode == Mode.DUNGEON) consumer.accept(textWidget); - } - - @Override - public void doLayout(ScreenRectangle tabArea) { - float ratio = Math.min((tabArea.height() - 35) / (float) (parent.height), (tabArea.width() - RIGHT_SIDE_WIDTH - 5) / (float) (parent.width)); - - previewWidget.setPosition(5, tabArea.top() + 5); - previewWidget.setWidth((int) (parent.width * ratio)); - previewWidget.setHeight((int) (parent.height * ratio)); - previewWidget.ratio = ratio; - updateWidgets(); - - int startY = tabArea.top() + 10; - if (mode == Mode.EDITABLE_LOCATION) { - locationDropdown.setWidth(RIGHT_SIDE_WIDTH - 5); - locationDropdown.setPosition(tabArea.right() - locationDropdown.getWidth() - 2, startY); - locationDropdown.setMaxHeight(Math.min(180, tabArea.height() - 20)); - startY += DropdownWidget.ENTRY_HEIGHT + 4 + 10; - } - - for (int i = 0; i < layerButtons.length; i++) { - Button layerButton = layerButtons[i]; - layerButton.setPosition(tabArea.width() - layerButton.getWidth() - 10, startY + i * 15); - } - int optionsY = startY + layerButtons.length * 15 + 5; - widgetOptions.setPosition(tabArea.width() - widgetOptions.getWidth() - 5, optionsY); - widgetOptions.setHeight(tabArea.height() - optionsY - 5); - textWidget.setPosition((tabArea.width() - textWidget.getWidth()) / 2, tabArea.bottom() - 9); - restorePositioning.setPosition(10, tabArea.bottom() - 25); - - visitChildren(clickableWidget -> clickableWidget.visible = mode == Mode.DUNGEON || parent.isPreviewVisible() || parent.noHandler); - locationDropdownOpened(locationDropdown.isOpen()); - } - - @SuppressWarnings("deprecation") - private void updatePlayerListFromPreview() { - if (mode == Mode.DUNGEON) { - PlayerListManager.updateDungeons(DungeonsTabPlaceholder.get()); - return; - } - if (!parent.isPreviewVisible() || parent.getHandler() == null) return; - List lines = new ArrayList<>(); - - // Preview doesn't include any players, so adding this as default - lines.add(Component.literal("Players (6)")); - lines.add(Component.literal("[PIG").withStyle(ChatFormatting.LIGHT_PURPLE) - .append(Component.literal("+++").withStyle(ChatFormatting.AQUA)) - .append(Component.literal("] Technoblade").withStyle(ChatFormatting.LIGHT_PURPLE)) - ); - lines.add(Component.literal("Kevinthegreat1")); - lines.add(Component.literal("AzureAaron")); - lines.add(Component.literal("LifeIsAParadox")); - lines.add(Component.literal("Rime")); - lines.add(Component.literal("Vic is a Cat")); - lines.add(Component.literal("that's right i ")); - lines.add(Component.literal("don't care about")); - lines.add(Component.literal("spaces MWAHAHA")); - lines.add(Component.literal("[MVP--] sixteencharacter")); - - for (int i = 3; i <= 5; i++) { - ItemStack stack = parent.getHandler().getSlot(i).getItem(); - - for (Component text : ItemUtils.getLore(stack)) { - MutableComponent mutableText = Component.empty(); - AtomicBoolean foundSquare = new AtomicBoolean(false); - - text.visit((style, asString) -> { - if (!asString.startsWith("⬛")) { - mutableText.append(Component.literal(asString).withStyle(style)); - } else foundSquare.set(true); - return Optional.empty(); - }, Style.EMPTY); - - if (foundSquare.get()) { - lines.add(mutableText); - //System.out.println(mutableText.getString()); - //System.out.println(mutableText); - } - } - } - PlayerListManager.updateWidgetsFrom(lines.stream().map(line -> { - PlayerInfo playerListEntry = new PlayerInfo(new GameProfile(UUID.randomUUID(), ""), false); - playerListEntry.setTabListDisplayName(line); - return playerListEntry; - }).toList()); - } - - public void updateWidgets() { - ScreenBuilder screenBuilder = WidgetManager.getScreenBuilder(getCurrentLocation()); - updatePlayerListFromPreview(); - float scale = SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudScale / 100.f; - screenBuilder.updateWidgetLists(true); - screenBuilder.updateWidgets(currentScreenLayer); - screenBuilder.positionWidgets((int) (parent.width / scale), (int) (parent.height / scale)); - } - - public enum Mode { - NORMAL, - DUNGEON, - EDITABLE_LOCATION - } - - void onHudWidgetSelected(@Nullable HudWidget hudWidget) { - widgetOptions.clearWidgets(); - if (hudWidget == null) return; - if (locationDropdown.isOpen()) locationDropdown.mouseClicked(new MouseButtonEvent(locationDropdown.getX(), locationDropdown.getY(), new MouseButtonInfo(0, 0)), false); - ScreenBuilder screenBuilder = WidgetManager.getScreenBuilder(getCurrentLocation()); - PositionRule positionRule = screenBuilder.getPositionRule(hudWidget.getInternalID()); - int width = widgetOptions.getWidth() - 6; - - // Normal hud widgets don't have auto. - if (positionRule == null && !(hudWidget instanceof TabHudWidget)) { - screenBuilder.setPositionRule(hudWidget.getInternalID(), PositionRule.DEFAULT); - positionRule = PositionRule.DEFAULT; - } - - // TODO localization - - widgetOptions.addWidget(new StringWidget(width, 9, hudWidget.getDisplayName().copy().withStyle(ChatFormatting.BOLD, ChatFormatting.UNDERLINE), client.font)); - if (positionRule == null) { - widgetOptions.addWidget(Button.builder(Component.literal("Positioning: Auto"), _ -> { - PositionRule rule = new PositionRule( - "screen", - PositionRule.Point.DEFAULT, - PositionRule.Point.DEFAULT, - hudWidget.getX() - 5, - hudWidget.getY() - 5, - WidgetManager.ScreenLayer.DEFAULT); - screenBuilder.setPositionRule(hudWidget.getInternalID(), rule); - updateWidgets(); - onHudWidgetSelected(hudWidget); - }) - .width(width) - .build()); - } else { - // Normal hud widgets don't have auto. - if (hudWidget instanceof TabHudWidget) { - widgetOptions.addWidget(Button.builder(Component.literal("Positioning: Custom"), _ -> { - screenBuilder.setPositionRule(hudWidget.getInternalID(), null); - updateWidgets(); - onHudWidgetSelected(hudWidget); - }) - .width(width) - .build()); - } - - String ye = "Layer: " + positionRule.screenLayer().toString(); - - widgetOptions.addWidget(Button.builder(Component.literal(ye), button -> { - ScreenBuilder builder = WidgetManager.getScreenBuilder(getCurrentLocation()); - PositionRule rule = builder.getPositionRuleOrDefault(hudWidget.getInternalID()); - WidgetManager.ScreenLayer newLayer = EnumUtils.cycle(rule.screenLayer()); - - PositionRule newRule = new PositionRule( - rule.parent(), - rule.parentPoint(), - rule.thisPoint(), - rule.relativeX(), - rule.relativeY(), - newLayer - ); - builder.setPositionRule(hudWidget.getInternalID(), newRule); - button.setMessage(Component.literal("Layer: " + newRule.screenLayer().toString())); - updateWidgets(); - if (newLayer != WidgetManager.ScreenLayer.DEFAULT) { - layerButtons[newLayer.ordinal()].onPress(NoopInput.INSTANCE); - } - - }).width(width).build()); - - Component parentName; - HudWidget parent; - if (positionRule.parent().equals("screen")) { - parentName = Component.literal("Screen"); - } else if ((parent = WidgetManager.widgetInstances.get(positionRule.parent())) == null) { - parentName = Component.literal("Unloaded Widget"); - } else { - parentName = parent.getDisplayName(); - } - - widgetOptions.addWidget(Button.builder(Component.literal("Parent: ").append(parentName), button -> { - this.previewWidget.pickParent = true; - button.setMessage(Component.literal("Click on a widget")); - }).width(width).build()); - - widgetOptions.addWidget(new AnchorSelectionWidget(width, Component.literal("This anchor"), false)); - widgetOptions.addWidget(new AnchorSelectionWidget(width, Component.literal("Parent anchor"), true)); - - // apply to all locations - if (mode == Mode.DUNGEON) return; - // padding thing - widgetOptions.addWidget(new AbstractWidget(0, 0, width, 20, Component.empty()) { - @Override - protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput builder) { - } - }); - } - - widgetOptions.addWidget(Button.builder(Component.literal("Apply Everywhere"), button -> { - if (this.previewWidget.selectedWidget == null) return; - PositionRule toCopy = WidgetManager.getScreenBuilder(getCurrentLocation()).getPositionRule(this.previewWidget.selectedWidget.getInternalID()); - if (toCopy == null && !(this.previewWidget.selectedWidget instanceof TabHudWidget)) return; - for (Location value : Location.values()) { - if (value == getCurrentLocation() || value == Location.DUNGEON || value == Location.UNKNOWN) continue; - WidgetManager.getScreenBuilder(value).setPositionRule( - this.previewWidget.selectedWidget.getInternalID(), - toCopy - ); - } - button.setMessage(Component.literal("Applied!")); - Scheduler.INSTANCE.schedule(() -> button.setMessage(Component.literal("Apply Everywhere")), 15); - }).width(width).tooltip(Tooltip.create(Component.literal("Apply positioning to all locations. This cannot be restored!"))).build() - ); - } - - public Location getCurrentLocation() { - return mode == Mode.DUNGEON ? Location.DUNGEON : parent.getCurrentLocation(); - } - - public WidgetManager.ScreenLayer getCurrentScreenLayer() { - return currentScreenLayer; - } - - /** - * Hide Layer Buttons when the location dropdown is opened. - */ - public void locationDropdownOpened(boolean isOpen) { - onHudWidgetSelected(null); - previewWidget.selectedWidget = null; - for (Button layerButton : layerButtons) { - layerButton.visible = !isOpen; - } - } - - private static class WidgetOptionsScrollable extends AbstractScrollArea { - private final List widgets = new ArrayList<>(); - private int height = 0; - - private WidgetOptionsScrollable() { - super(0, 0, 0, 0, Component.literal("Widget Options Scrollable"), AbstractScrollArea.defaultSettings(8)); - } - - @Override - protected int contentHeight() { - return height; - } - - @Override - protected double scrollRate() { - return 6; - } - - /** - * A widget is not visible if it is half above the top of the frame, or half below. - * - * @param i Y of the widget - * @param j Bottom of the widget - * @param h Height of the widget - */ - protected boolean isNotVisible(int i, int j, int h) { - return !((double) j - ((double) h / 2) >= (double) this.getY()) || // Bottom of this widget is out of view, above the frame - !((double) i + ((double) h / 2) <= (double) (this.getBottom())); // Top of this widget is out of view, below the frame - } - - @Override - protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { - this.extractScrollbar(graphics, mouseX, mouseY); - height = 0; - for (AbstractWidget widget : widgets) { - widget.setX(getX() + 1); - widget.setY((int) (getY() + 1 + height - scrollAmount())); - - height += widget.getHeight() + 1; - if (isNotVisible(widget.getY(), widget.getBottom(), widget.getHeight())) continue; - widget.extractRenderState(graphics, mouseX, mouseY, delta); - } - } - - @Override - public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { - boolean bl = updateScrolling(click); - for (AbstractWidget widget : widgets) { - if (isNotVisible(widget.getY(), widget.getBottom(), widget.getHeight())) continue; - if (widget.mouseClicked(click, doubled)) return true; - } - return super.mouseClicked(click, doubled) || bl; - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput builder) { - } - - private void clearWidgets() { - widgets.clear(); - } - - private void addWidget(AbstractWidget clickableWidget) { - widgets.add(clickableWidget); - } - } - - private class AnchorSelectionWidget extends AbstractWidget { - private final boolean other; - private PositionRule.@Nullable Point hoveredPoint = null; - - private AnchorSelectionWidget(int width, Component text, boolean other) { - super(0, 0, width, 40, text); - this.other = other; - } - - @Override - protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { - hoveredPoint = null; - graphics.text(client.font, getMessage(), getX(), getY(), CommonColors.WHITE, true); - graphics.pose().pushMatrix(); - graphics.pose().translate(getX(), getY() + 10); - // Rectangle thing - int x = getWidth() / 6; - int w = (int) (4 * getWidth() / 6f); - int y = 5; // 30 / 6 - int h = 20; - - GuiHelper.border(graphics, x, y + 1, w, h, CommonColors.WHITE); - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - int squareX = x + (i * getWidth()) / 3; - int squareY = y + (j * 30) / 3; - boolean selectedAnchor = false; - if (previewWidget.selectedWidget != null) { - String internalID = previewWidget.selectedWidget.getInternalID(); - PositionRule positionRule = WidgetManager.getScreenBuilder(getCurrentLocation()).getPositionRule(internalID); - if (positionRule != null) { - PositionRule.Point point = other ? positionRule.parentPoint() : positionRule.thisPoint(); - selectedAnchor = point.horizontalPoint().ordinal() == i && point.verticalPoint().ordinal() == j; - } - } - - boolean hoveredAnchor = mouseX >= getX() + i * getWidth() / 3 && - mouseX < getX() + (i + 1) * getWidth() / 3 && - mouseY >= getY() + 10 + j * 10 && - mouseY < getY() + 10 + (j + 1) * 10; - - if (hoveredAnchor) { - PositionRule.VerticalPoint[] verticalPoints = PositionRule.VerticalPoint.values(); - PositionRule.HorizontalPoint[] horizontalPoints = PositionRule.HorizontalPoint.values(); - hoveredPoint = new PositionRule.Point(verticalPoints[j], horizontalPoints[i]); - } - - graphics.fill(squareX - 1, squareY - 1, squareX + 2, squareY + 2, hoveredAnchor ? CommonColors.RED : selectedAnchor ? CommonColors.YELLOW : CommonColors.WHITE); - } - } - graphics.pose().popMatrix(); - } - - @Override - public void onClick(MouseButtonEvent click, boolean doubled) { - HudWidget affectedWidget = previewWidget.selectedWidget; - if (hoveredPoint != null && affectedWidget != null) { - ScreenBuilder screenBuilder = WidgetManager.getScreenBuilder(getCurrentLocation()); - String internalID = affectedWidget.getInternalID(); - PositionRule oldRule = screenBuilder.getPositionRuleOrDefault(internalID); - // Get the x, y of the parent's point - float scale = SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudScale / 100.f; - ScreenPosition startPos = WidgetPositioner.getStartPosition(oldRule.parent(), (int) (parent.width / scale), (int) (parent.height / scale), other ? hoveredPoint : oldRule.parentPoint()); - if (startPos == null) startPos = new ScreenPosition(0, 0); - // Same but for the affected widget - PositionRule.Point thisPoint = other ? oldRule.thisPoint() : hoveredPoint; - ScreenPosition endPos = new ScreenPosition( - (int) (affectedWidget.getX() + thisPoint.horizontalPoint().getPercentage() * affectedWidget.getWidth()), - (int) (affectedWidget.getY() + thisPoint.verticalPoint().getPercentage() * affectedWidget.getHeight()) - ); - - if (other) { - screenBuilder.setPositionRule(internalID, new PositionRule( - oldRule.parent(), - hoveredPoint, - oldRule.thisPoint(), - endPos.x() - startPos.x(), - endPos.y() - startPos.y(), - oldRule.screenLayer())); - } else { - screenBuilder.setPositionRule(internalID, new PositionRule( - oldRule.parent(), - oldRule.parentPoint(), - hoveredPoint, - endPos.x() - startPos.x(), - endPos.y() - startPos.y(), - oldRule.screenLayer())); - } - } - updateWidgets(); - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput builder) { - } - } - - @Override - public Component getTabExtraNarration() { - return Component.empty(); - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewWidget.java deleted file mode 100644 index 353ef2f9fd1..00000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewWidget.java +++ /dev/null @@ -1,308 +0,0 @@ -package de.hysky.skyblocker.skyblock.tabhud.config.preview; - -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.mixins.accessors.GuiInvoker; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; -import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; -import de.hysky.skyblocker.utils.render.GuiHelper; -import org.joml.Matrix3x2fStack; -import org.jspecify.annotations.Nullable; -import org.lwjgl.glfw.GLFW; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.navigation.ScreenPosition; -import net.minecraft.client.input.KeyEvent; -import net.minecraft.client.input.MouseButtonEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.util.CommonColors; - -/** - * The preview widget that captures clicks and displays the current state of the widgets. - */ -public class PreviewWidget extends AbstractWidget { - private final PreviewTab tab; - float ratio = 1f; - private float scaledRatio = 1f; - private float scaledScreenWidth; - private float scaledScreenHeight; - /** - * The widget the user is hovering with the mouse - */ - private @Nullable HudWidget hoveredWidget = null; - /** - * The selected widget, settings for it show on the right, and it can be dragged around - */ - @Nullable HudWidget selectedWidget = null; - /** - * The original pos, of the selected widget, it is set when you click on it. So when it's done being dragged it can be compared. - * Effectively, if this ain't null, the user is dragging a widget around. - */ - private @Nullable ScreenPosition selectedOriginalPos = null; - protected boolean pickParent = false; - - public PreviewWidget(PreviewTab tab) { - super(0, 0, 0, 0, Component.literal("Preview widget")); - this.tab = tab; - scaledScreenWidth = tab.parent.width; - scaledScreenHeight = tab.parent.height; - } - - @Override - protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { - hoveredWidget = null; - float scale = SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudScale / 100.f; - scaledRatio = ratio * scale; - scaledScreenWidth = tab.parent.width / scale; - scaledScreenHeight = tab.parent.height / scale; - - ScreenBuilder screenBuilder = WidgetManager.getScreenBuilder(tab.getCurrentLocation()); - GuiHelper.border(graphics, getX() - 1, getY() - 1, getWidth() + 2, getHeight() + 2, -1); - graphics.enableScissor(getX(), getY(), getRight(), getBottom()); - Matrix3x2fStack matrices = graphics.pose(); - matrices.pushMatrix(); - matrices.translate(getX(), getY()); - matrices.scale(scaledRatio, scaledRatio); - - screenBuilder.extractWidgetsRenderState(graphics, tab.getCurrentScreenLayer()); - - float localMouseX = (mouseX - getX()) / scaledRatio; - float localMouseY = (mouseY - getY()) / scaledRatio; - - if (selectedWidget != null && selectedWidget.isMouseOver(localMouseX, localMouseY)) { - hoveredWidget = selectedWidget; - } else for (HudWidget hudWidget : screenBuilder.getHudWidgets(tab.getCurrentScreenLayer())) { - if (hudWidget.isMouseOver(localMouseX, localMouseY)) { - hoveredWidget = hudWidget; - break; - } - } - - // HOVERED - if (hoveredWidget != null && !hoveredWidget.equals(selectedWidget)) { - GuiHelper.border( - graphics, - hoveredWidget.getX() - 1, - hoveredWidget.getY() - 1, - hoveredWidget.getWidth() + 2, - hoveredWidget.getHeight() + 2, - CommonColors.SOFT_YELLOW); - } - - // SELECTED - if (selectedWidget != null) { - //noinspection DataFlowIssue - GuiHelper.border( - graphics, - selectedWidget.getX() - 1, - selectedWidget.getY() - 1, - selectedWidget.getWidth() + 2, - selectedWidget.getHeight() + 2, - ChatFormatting.GREEN.getColor() | 0xFF000000); - - PositionRule rule = screenBuilder.getPositionRule(selectedWidget.getInternalID()); - if (rule != null) { - // This is the difference between the position before dragging and the current position - int deltaX = 0; - int deltaY = 0; - if (selectedOriginalPos != null) { - deltaX = selectedWidget.getX() - selectedOriginalPos.x(); - deltaY = selectedWidget.getY() - selectedOriginalPos.y(); - } - int thisAnchorX = (int) (selectedWidget.getX() + rule.thisPoint().horizontalPoint().getPercentage() * selectedWidget.getWidth()); - int thisAnchorY = (int) (selectedWidget.getY() + rule.thisPoint().verticalPoint().getPercentage() * selectedWidget.getHeight()); - - int translatedX = Math.min(thisAnchorX - rule.relativeX() - deltaX, (int) scaledScreenWidth - 2); - int translatedY = Math.min(thisAnchorY - rule.relativeY() - deltaY, (int) scaledScreenHeight - 2); - - extractUnits(graphics, rule, deltaX, deltaY, thisAnchorX, thisAnchorY, translatedX, translatedY); - - graphics.horizontalLine(translatedX, thisAnchorX, thisAnchorY + 1, 0xAAAA0000); - graphics.verticalLine(translatedX + 1, translatedY, thisAnchorY, 0xAAAA0000); - - graphics.horizontalLine(translatedX, thisAnchorX, thisAnchorY, CommonColors.RED); - graphics.verticalLine(translatedX, translatedY, thisAnchorY, CommonColors.RED); - } - } - - matrices.popMatrix(); - matrices.pushMatrix(); - matrices.translate(getX(), getY()); - matrices.scale(ratio, ratio); - ((GuiInvoker) Minecraft.getInstance().gui).extractSidebar(graphics, tab.placeHolderObjective); - matrices.popMatrix(); - graphics.disableScissor(); - } - - private void extractUnits(GuiGraphicsExtractor graphics, PositionRule rule, int deltaX, int deltaY, int thisAnchorX, int thisAnchorY, int translatedX, int translatedY) { - boolean xUnitOnTop = rule.relativeY() > 0; - if (xUnitOnTop && thisAnchorY < 10) xUnitOnTop = false; - if (!xUnitOnTop && thisAnchorY > scaledScreenHeight - 10) xUnitOnTop = true; - - int xUnit = rule.relativeX() + deltaX; - int yUnit = rule.relativeY() + deltaY; - String xUnitText = String.valueOf(xUnit); - String yUnitText = String.valueOf(yUnit); - int yUnitTextWidth = tab.client.font.width(yUnitText); - boolean yUnitOnRight = rule.relativeX() > 0; - if (yUnitOnRight && translatedX + 2 + yUnitTextWidth >= scaledScreenWidth) yUnitOnRight = false; - if (!yUnitOnRight && translatedX - 2 - yUnitTextWidth <= 0) yUnitOnRight = true; - - if (Math.abs(xUnit) < 15 || Math.abs(yUnit) < 15) { - String text = "x: " + xUnitText + " y: " + yUnitText; - int textX = thisAnchorX < scaledScreenWidth / 2 ? (int) (scaledScreenWidth - tab.client.font.width(text) - 5) : 5; - graphics.text(tab.client.font, text, textX, 2, CommonColors.RED); - } - // X - graphics.text(tab.client.font, xUnitText, thisAnchorX - (xUnit) / 2, xUnitOnTop ? thisAnchorY - 9 : thisAnchorY + 2, CommonColors.SOFT_RED); - // Y - graphics.text(tab.client.font, yUnitText, yUnitOnRight ? translatedX + 2 : translatedX - 1 - yUnitTextWidth, thisAnchorY - (yUnit - 9) / 2, CommonColors.SOFT_RED, true); - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput builder) { - } - - private double bufferedDeltaX = 0; - private double bufferedDeltaY = 0; - - @Override - protected void onDrag(MouseButtonEvent click, double offsetX, double offsetY) { - double localDeltaX = offsetX / scaledRatio + bufferedDeltaX; - double localDeltaY = offsetY / scaledRatio + bufferedDeltaY; - - bufferedDeltaX = localDeltaX - (int) localDeltaX; - bufferedDeltaY = localDeltaY - (int) localDeltaY; - - if (selectedWidget != null && selectedOriginalPos != null) { - selectedWidget.setX(selectedWidget.getX() + (int) localDeltaX); - selectedWidget.setY(selectedWidget.getY() + (int) localDeltaY); - } - } - - @Override - public void onRelease(MouseButtonEvent click) { - if (pickParent) { - pickParent = false; - return; - } - if (!Objects.equals(hoveredWidget, selectedWidget)) { - tab.onHudWidgetSelected(hoveredWidget); - } - // TODO releasing a widget outside of the area causes weird behavior, might wanna look into that - // Update positioning real - if (selectedWidget != null && selectedOriginalPos != null) { - ScreenBuilder screenBuilder = WidgetManager.getScreenBuilder(tab.getCurrentLocation()); - PositionRule oldRule = screenBuilder.getPositionRule(selectedWidget.getInternalID()); - if (oldRule == null) oldRule = PositionRule.DEFAULT; - int relativeX = selectedWidget.getX() - selectedOriginalPos.x(); - int relativeY = selectedWidget.getY() - selectedOriginalPos.y(); - screenBuilder.setPositionRule(selectedWidget.getInternalID(), new PositionRule( - oldRule.parent(), - oldRule.parentPoint(), - oldRule.thisPoint(), - oldRule.relativeX() + relativeX, - oldRule.relativeY() + relativeY, - oldRule.screenLayer())); - tab.updateWidgets(); - } - - selectedWidget = hoveredWidget; - selectedOriginalPos = null; - super.onRelease(click); - } - - @Override - public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { - if (!(this.active && this.visible && isMouseOver(click.x(), click.y()))) return false; - double localMouseX = (click.x() - getX()) / scaledRatio; - double localMouseY = (click.y() - getY()) / scaledRatio; - if (click.button() == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { - List hoveredThingies = new ArrayList<>(); - for (HudWidget hudWidget : WidgetManager.getScreenBuilder(tab.getCurrentLocation()).getHudWidgets(tab.getCurrentScreenLayer())) { - if (hudWidget.isMouseOver(localMouseX, localMouseY)) hoveredThingies.add(hudWidget); - } - if (hoveredThingies.size() == 1) selectedWidget = hoveredThingies.getFirst(); - else if (!hoveredThingies.isEmpty()) { - for (int i = 0; i < hoveredThingies.size(); i++) { - if (hoveredThingies.get(i).equals(hoveredWidget)) { - selectedWidget = hoveredThingies.get((i + 1) % hoveredThingies.size()); - } - } - } - return true; - } - ScreenBuilder screenBuilder = WidgetManager.getScreenBuilder(tab.getCurrentLocation()); - if (pickParent && selectedWidget != null) { - if (selectedWidget.equals(hoveredWidget)) { - // Restore the button text, but don't do anything. - tab.onHudWidgetSelected(selectedWidget); - return true; - } - - PositionRule oldRule = screenBuilder.getPositionRule(selectedWidget.getInternalID()); - if (oldRule == null) oldRule = PositionRule.DEFAULT; - - int thisAnchorX = (int) (selectedWidget.getX() + oldRule.thisPoint().horizontalPoint().getPercentage() * selectedWidget.getWidth()); - int thisAnchorY = (int) (selectedWidget.getY() + oldRule.thisPoint().verticalPoint().getPercentage() * selectedWidget.getHeight()); - - int otherAnchorX = hoveredWidget == null ? 0 : hoveredWidget.getX(); - int otherAnchorY = hoveredWidget == null ? 0 : hoveredWidget.getY(); - - PositionRule newRule = new PositionRule( - hoveredWidget == null ? "screen" : hoveredWidget.getInternalID(), - PositionRule.Point.DEFAULT, - oldRule.thisPoint(), - thisAnchorX - otherAnchorX, - thisAnchorY - otherAnchorY, - oldRule.screenLayer() - ); - screenBuilder.setPositionRule(selectedWidget.getInternalID(), newRule); - tab.updateWidgets(); - tab.onHudWidgetSelected(selectedWidget); - return true; - } - - - if (selectedWidget != null && selectedWidget.isMouseOver(localMouseX, localMouseY) && - screenBuilder.getPositionRule(selectedWidget.getInternalID()) != null) { - selectedOriginalPos = new ScreenPosition(selectedWidget.getX(), selectedWidget.getY()); - } - return true; - } - - @Override - public boolean keyPressed(KeyEvent input) { - if (hoveredWidget != null && hoveredWidget.equals(selectedWidget)) { - int multiplier = (input.modifiers() & GLFW.GLFW_MOD_CONTROL) != 0 ? 5 : 1; - int x = 0, y = 0; - switch (input.key()) { - case GLFW.GLFW_KEY_UP -> y = -multiplier; - case GLFW.GLFW_KEY_DOWN -> y = multiplier; - case GLFW.GLFW_KEY_LEFT -> x = -multiplier; - case GLFW.GLFW_KEY_RIGHT -> x = multiplier; - } - ScreenBuilder screenBuilder = WidgetManager.getScreenBuilder(tab.getCurrentLocation()); - PositionRule oldRule = screenBuilder.getPositionRuleOrDefault(selectedWidget.getInternalID()); - - screenBuilder.setPositionRule(selectedWidget.getInternalID(), new PositionRule( - oldRule.parent(), - oldRule.parentPoint(), - oldRule.thisPoint(), - oldRule.relativeX() + x, - oldRule.relativeY() + y, - oldRule.screenLayer())); - tab.updateWidgets(); - return true; - } - return super.keyPressed(input); - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/package-info.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/package-info.java deleted file mode 100644 index 9b16ccfcbc6..00000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package de.hysky.skyblocker.skyblock.tabhud.config.preview; - -import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/CopyTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/CopyTracker.java new file mode 100644 index 00000000000..8485785d102 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/CopyTracker.java @@ -0,0 +1,85 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.utils.Location; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +/** + * Tracks where widgets were copied to. + * Used to have the option to remove the copies of a widget, and to pre-select other copies in the "Copy to" popup to easily "propagate" config changes + */ +public record CopyTracker(Layer hud, Layer tab, Layer secondaryTab) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Layer.CODEC.fieldOf("hud").forGetter(CopyTracker::hud), + Layer.CODEC.fieldOf("tab").forGetter(CopyTracker::tab), + Layer.CODEC.fieldOf("secondary_tab").forGetter(CopyTracker::secondaryTab) + ).apply(instance, CopyTracker::new) + ); + + public CopyTracker() { + this(new Layer(), new Layer(), new Layer()); + } + + public Layer get(WidgetManager.ScreenLayer layer) { + return switch (layer) { + case HUD -> hud; + case MAIN_TAB -> tab; + case SECONDARY_TAB -> secondaryTab; + }; + } + + public record Layer(Map map) { + public static final Codec CODEC = Codec.unboundedMap(Codec.STRING, LocationSets.CODEC) + .xmap(l -> (Map) new Object2ObjectOpenHashMap<>(l), Function.identity()) + .xmap(Layer::new, Layer::map); + + public Optional get(String widgetId) { + return Optional.ofNullable(map.get(widgetId)); + } + + public LocationSets getOrCreate(String widgetId) { + return map.computeIfAbsent(widgetId, _ -> new LocationSets(new ArrayList<>())); + } + + public Layer() { + this(new Object2ObjectOpenHashMap<>()); + } + } + + /** + * A list of sets that each represent locations where the widget was copied to. The sets should not overlap. + * @param sets the sets + */ + public record LocationSets(List> sets) { + // Codec that accepts either a list of locations, or a string (contents doesn't matter) to represent "all locations" + private static final Codec> LOCATION_SET_CODEC = Codec.either(Location.CODEC.listOf(), Codec.STRING) + .xmap( + e -> e.map(l -> (Set) EnumSet.copyOf(l), _ -> EnumSet.copyOf(WidgetManager.ALLOWED_LOCATIONS)), + s -> s.equals(WidgetManager.ALLOWED_LOCATIONS) ? Either.right("everywhere") : Either.left(List.copyOf(s))); + public static final Codec CODEC = LOCATION_SET_CODEC.listOf() + .xmap(l -> (List>) new ArrayList<>(l), Function.identity()) + .xmap(LocationSets::new, LocationSets::sets); + + public Optional> whereHas(Location location) { + return sets.stream().filter(locations -> locations.contains(location)).findFirst(); + } + + public void track(Set locations) { + for (Set set : sets) { + set.removeAll(locations); + } + sets.add(EnumSet.copyOf(locations)); + sets.removeIf(s -> s.size() <= 1); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/EditableScreenBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/EditableScreenBuilder.java new file mode 100644 index 00000000000..f85be1587f5 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/EditableScreenBuilder.java @@ -0,0 +1,25 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +/** + * A screen builder whose {@link LayerBuilder}s can be edited to remove and add widgets + */ +public class EditableScreenBuilder extends ScreenBuilder { + public EditableScreenBuilder() { + super(); + } + + public LayerBuilderEditor getEditor(WidgetManager.ScreenLayer layer) { + return new LayerBuilderEditor(get(layer)); + } + + public EditableLayer getLayer(WidgetManager.ScreenLayer layer) { + return new EditableLayer(get(layer), getEditor(layer)); + } + + public record EditableLayer(LayerBuilder builder, LayerBuilderEditor editor) { + public void update() { + builder.update(); + builder.getRendered().forEach(w -> w.widget.onConfigChanged()); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerBuilder.java new file mode 100644 index 00000000000..13f46ebdabf --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerBuilder.java @@ -0,0 +1,107 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +import com.mojang.logging.LogUtils; +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; +import de.hysky.skyblocker.utils.JsonValueInput; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.util.ProblemReporter; +import net.minecraft.util.profiling.Profiler; +import org.joml.Matrix3x2fStack; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class LayerBuilder { + private static final Logger LOGGER = LogUtils.getLogger(); + + protected LayerConfig config = LayerConfig.DUMMY; + protected final Set renderedWidgets = new ObjectOpenHashSet<>(); + protected final List widgets = new LinkedList<>(); + + private int positionsHash = 0; + + public void setConfig(LayerConfig config) { + this.config = config; + } + + public void update() { + positionsHash = 0; + widgets.clear(); + for (Map.Entry entry : config.widgets.entrySet()) { + if (entry.getValue().config().isEmpty()) continue; + HudWidget hudWidget = WidgetManager.getWidgetOrPlaceholder(entry.getKey()); + try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(LOGGER)) { + hudWidget.load(new JsonValueInput(reporter, entry.getValue().config().get())); + } + if (entry.getValue().position().isEmpty()) continue; + widgets.add(new PositionedWidget(hudWidget, entry.getValue().position().get())); + } + updateList(); + } + + protected void updateList() { + renderedWidgets.clear(); + getRendered().stream().map(w -> w.widget).forEach(renderedWidgets::add); + } + + public boolean contains(HudWidget widget) { + return renderedWidgets.contains(widget); + } + + + public void extractRenderStates(GuiGraphicsExtractor graphics, int screenWidth, int screenHeight, boolean config) { + Profiler.get().push("skyblocker:renderHud"); + int hash = Integer.hashCode(screenWidth); + hash = hash * 31 + Integer.hashCode(screenHeight); + for (PositionedWidget widget : getRendered()) { + boolean shouldRender = widget.widget.shouldRender() || config; + widget.visible = shouldRender; + hash = hash * 31 + Boolean.hashCode(shouldRender); + hash = hash * 31 + Integer.hashCode(widget.widget.getWidth()); + hash = hash * 31 + Integer.hashCode(widget.widget.getHeight()); + } + if (positionsHash != hash) { + positionsHash = hash; + updatePositions(screenWidth, screenHeight); + } + Matrix3x2fStack pose = graphics.pose(); + float delta = Minecraft.getInstance().getDeltaTracker().getGameTimeDeltaTicks(); + for (PositionedWidget widget : getRendered()) { + if (!widget.visible && !config) continue; + pose.pushMatrix(); + pose.translate(widget.widget.getX(), widget.widget.getY()); + if (config) widget.widget.extractRenderStateForConfig(graphics, delta); + else widget.widget.extractRenderState(graphics, delta); + pose.popMatrix(); + } + Profiler.get().pop(); + } + + public Collection getRendered() { + return widgets; + } + + public void updatePositions(int screenWidth, int screenHeight) { + updatePositions(getRendered().stream().filter(p -> !p.fromTab).toList(), screenWidth, screenHeight); + } + + public static void updatePositions(Collection widgets, int screenWidth, int screenHeight) { + Profiler.get().push("skyblocker:updatePositions"); + widgets.forEach(w -> w.positioned = false); + for (PositionedWidget widget : widgets) { + if (!widget.positioned) WidgetPositioner.applyRuleToWidget( + widget, + screenWidth, + screenHeight, + s -> widgets.stream().filter(w -> w.widget.getInternalID().equals(s)).findFirst().orElseThrow() + ); + } + Profiler.get().pop(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerBuilderEditor.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerBuilderEditor.java new file mode 100644 index 00000000000..2813b902715 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerBuilderEditor.java @@ -0,0 +1,62 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; + +import java.util.Optional; + +/** + * A class that allows adding and removing widgets from a {@link LayerBuilder} and serializing its config + * + */ +public class LayerBuilderEditor { + private final LayerBuilder layer; + // package private so you cannot go around and just edit the layers in WidgetManager + LayerBuilderEditor(LayerBuilder layer) { + this.layer = layer; + } + + public PositionedWidget add(HudWidget hudWidget, PositionRule rule) { + layer.config.widgets.put(hudWidget.getInternalID(), new WidgetConfig(new JsonObject(), rule)); + PositionedWidget positionedWidget = new PositionedWidget(hudWidget, rule); + layer.widgets.add(positionedWidget); + layer.updateList(); + return positionedWidget; + } + + public PositionedWidget add(HudWidget hudWidget) { + return add(hudWidget, PositionRule.DEFAULT); + } + + public void remove(HudWidget widget) { + layer.config.widgets.remove(widget.getInternalID()); + layer.widgets.removeIf(w -> w.widget.equals(widget)); + layer.updateList(); + } + + public void remove(PositionedWidget widget) { + remove(widget.widget); + } + + public void serializeConfig() { + layer.config.widgets.replaceAll((id, widgetConfig) -> { + PositionedWidget widget = layer.getRendered().stream().filter(w -> w.widget.getInternalID().equals(id)).findFirst().orElse(null); + if (widget == null) { + if (widgetConfig.config().isEmpty()) return widgetConfig; // normal, it was removed + // maybe not normal? not sure how to handle it // TODO + return widgetConfig; + } + JsonObject conf = new JsonObject(); + widget.widget.save(conf); + return new WidgetConfig(Optional.of(conf), widget.fromTab ? Optional.empty() : Optional.of(widget.rule)); + }); + for (PositionedWidget widget : layer.getRendered()) { + if (!widget.fromTab || layer.config.widgets.containsKey(widget.widget.getInternalID())) continue; + JsonObject conf = new JsonObject(); + widget.widget.save(conf); + layer.config.widgets.put(widget.widget.getInternalID(), new WidgetConfig(Optional.of(conf), Optional.empty())); + } + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerConfig.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerConfig.java new file mode 100644 index 00000000000..dadb9a0c79a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/LayerConfig.java @@ -0,0 +1,97 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.CenteredWidgetPositioner; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.TopAlignedWidgetPositioner; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArraySet; +import net.minecraft.client.resources.language.I18n; +import net.minecraft.util.StringRepresentable; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; + +public class LayerConfig { + public static final LayerConfig DUMMY = new LayerConfig(); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + FancyTab.CODEC.optionalFieldOf("fancyTab").forGetter(c -> Optional.ofNullable(c.fancyTab)), + Codec.unboundedMap(Codec.STRING, WidgetConfig.CODEC).fieldOf("widgets").forGetter(c -> c.widgets) + ).apply(instance, LayerConfig::new)); + + public @Nullable FancyTab fancyTab; + public final Map widgets; + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public LayerConfig(Optional fancyTab, Map widgetConfigs) { + this(fancyTab.orElse(null), widgetConfigs); + } + + public LayerConfig(@Nullable FancyTab fancyTab, Map widgets) { + this.fancyTab = fancyTab; + this.widgets = new Object2ObjectOpenHashMap<>(widgets); + } + + public LayerConfig() { + this(Optional.empty(), new Object2ObjectOpenHashMap<>()); + } + + public @Nullable FancyTab fancyTab() { + return fancyTab; + } + + public static class FancyTab { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.BOOL.fieldOf("enabled").forGetter(c -> c.enabled), + Positioner.CODEC.optionalFieldOf("positioner", Positioner.CENTERED).forGetter(c -> c.positioner), + Codec.STRING.listOf().optionalFieldOf("hidden_widgets", List.of()) + .xmap(ObjectArraySet::new, List::copyOf) + .forGetter(c -> c.hiddenWidgets) + ).apply(instance, FancyTab::new)); + public boolean enabled; + public Positioner positioner; + public ObjectArraySet hiddenWidgets; + + public FancyTab(boolean enabled, Positioner positioner, Set hiddenWidgets) { + this.enabled = enabled; + this.positioner = positioner; + this.hiddenWidgets = new ObjectArraySet<>(hiddenWidgets); + } + + public FancyTab() { + this(true, Positioner.CENTERED, Set.of()); + } + } + + public enum Positioner implements StringRepresentable { + TOP(TopAlignedWidgetPositioner::new), + CENTERED(CenteredWidgetPositioner::new); + + public static final Codec CODEC = StringRepresentable.fromEnum(Positioner::values); + + private final BiFunction function; + + Positioner(BiFunction widgetPositionerSupplier) { + function = widgetPositionerSupplier; + } + + public WidgetPositioner getNewPositioner(float maxHeight, int screenHeight) { + return function.apply(maxHeight, screenHeight); + } + + @Override + public String getSerializedName() { + return name().toLowerCase(Locale.ENGLISH); + } + + @Override + public String toString() { + return I18n.get("skyblocker.config.uiAndVisuals.tabHud.defaultPosition." + name()); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/PositionedWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/PositionedWidget.java new file mode 100644 index 00000000000..43ca824c41f --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/PositionedWidget.java @@ -0,0 +1,34 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; + +/** + * Mainly a pair of a {@link HudWidget} and a {@link PositionRule}.

+ * Includes a few more information for {@link LayerBuilder} and config screen + */ +public final class PositionedWidget { + public final HudWidget widget; + public PositionRule rule; + public boolean fromTab = false; + boolean positioned = false; + boolean visible = false; + + public PositionedWidget(HudWidget widget, PositionRule rule) { + this.widget = widget; + this.rule = rule; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + + PositionedWidget that = (PositionedWidget) o; + return widget.equals(that.widget); + } + + @Override + public int hashCode() { + return widget.hashCode(); + } +} 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 458caf4c0da..d928a11789b 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 @@ -1,233 +1,59 @@ package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.CenteredWidgetPositioner; -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.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; -import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; -import de.hysky.skyblocker.utils.Location; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; - -import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.resources.language.I18n; -import org.jspecify.annotations.Nullable; public class ScreenBuilder { - // TODO: eliminate this static field completely? - // we can get rid of this field by moving the widget dimensions check into `updateWidgetLists` - private static boolean positionsNeedsUpdating = true; - - private final Map positioning = new Object2ObjectOpenHashMap<>(); - private Map positioningBackup = null; - private final Location location; - - private List hudScreen = new ArrayList<>(); - private List mainTabScreen = new ArrayList<>(); - private List secondaryTabScreen = new ArrayList<>(); - - /** - * Create a ScreenBuilder from a json. - */ - public ScreenBuilder(Location location) { - this.location = location; - } - - public @Nullable PositionRule getPositionRule(String widgetInternalId) { - return positioning.get(widgetInternalId); - } - - public void forEachPositionRuleEntry(BiConsumer action) { - positioning.forEach(action); - } - - public PositionRule getPositionRuleOrDefault(String widgetInternalId) { - PositionRule positionRule = getPositionRule(widgetInternalId); - return positionRule == null ? PositionRule.DEFAULT : positionRule; - } - - public void setPositionRule(String widgetInternalId, @Nullable PositionRule newPositionRule) { - if (newPositionRule == null) positioning.remove(widgetInternalId); - else positioning.put(widgetInternalId, newPositionRule); - } + private final LayerBuilder hud; + private final TabLayerBuilder tab; + private final LayerBuilder secondaryTab; + private ScreenConfig config = ScreenConfig.DUMMY; - public void backupPositioning() { - positioningBackup = Map.copyOf(positioning); + protected ScreenBuilder(LayerBuilder hud, TabLayerBuilder tab, LayerBuilder secondaryTab) { + this.hud = hud; + this.tab = tab; + this.secondaryTab = secondaryTab; } - public void restorePositioningFromBackup() { - if (positioningBackup == null) return; - positioning.clear(); - positioning.putAll(positioningBackup); + public ScreenBuilder() { + this(new LayerBuilder(), new TabLayerBuilder(), new LayerBuilder()); } - public static void markDirty() { - positionsNeedsUpdating = true; + public LayerBuilder get(WidgetManager.ScreenLayer layer) { + return switch (layer) { + case HUD -> hud; + case MAIN_TAB -> tab; + case SECONDARY_TAB -> secondaryTab; + }; } - /** - * Updates the lists of widgets that should be rendered. This method runs every frame to check if any widgets have changed visibility (shouldRender). - * @param config whether this render in happening in the config screen - * @return true if the lists have changed and positioners should run, false if they are the same as before and repositioning is not needed - */ - public boolean updateWidgetLists(boolean config) { - // Save the hud widgets that should be rendered to new lists - final List hudNew = new ArrayList<>(); - final List mainTabNew = new ArrayList<>(); - final List secondaryTabNew = new ArrayList<>(); - - for (HudWidget widget : WidgetManager.widgetInstances.values()) { - widget.setVisible(false); - if (config ? widget.isEnabledIn(location) : widget.shouldRender(location)) { // TabHudWidget has this at false - // TODO maybe behavior to change? (having no position rule on a normal hud widget shouldn't quite be possible) - PositionRule rule = getPositionRule(widget.getInternalID()); - if (rule == null) { - hudNew.add(widget); - } else { - switch (rule.screenLayer()) { - case MAIN_TAB -> mainTabNew.add(widget); - case SECONDARY_TAB -> secondaryTabNew.add(widget); - case null, default -> hudNew.add(widget); - } - } - widget.setVisible(true); - widget.setPositioned(false); - } - } - - for (TabHudWidget widget : PlayerListManager.tabWidgetsToShow) { - if (!config && widget.isEmpty()) continue; - PositionRule rule = getPositionRule(widget.getInternalID()); - widget.setVisible(true); - if (rule == null) { - mainTabNew.add(widget); - } else { - widget.setPositioned(false); - switch (rule.screenLayer()) { - case HUD -> hudNew.add(widget); - case SECONDARY_TAB -> secondaryTabNew.add(widget); - case null, default -> mainTabNew.add(widget); - } - } - } - - // Compare the newly generated lists with the old ones - if (hudScreen.equals(hudNew) && mainTabScreen.equals(mainTabNew) && secondaryTabScreen.equals(secondaryTabNew)) { - return false; - } - hudScreen = hudNew; - mainTabScreen = mainTabNew; - secondaryTabScreen = secondaryTabNew; - - return true; + public LayerBuilder hud() { + return hud; } - /** - * Updates the widgets (if needed) after the new widget list has been generated and before positioners run. - */ - public void updateWidgets(WidgetManager.ScreenLayer screenLayer) { - for (HudWidget widget : getHudWidgets(screenLayer)) { - if (widget.shouldUpdateBeforeRendering()) widget.update(); - } + public LayerBuilder tab() { + return tab; } - public void positionWidgets(int screenW, int screenH) { - WidgetPositioner newPositioner = SkyblockerConfigManager.get().uiAndVisuals.tabHud.defaultPositioning.getNewPositioner(screenW, screenH); - - // Auto positioning - for (HudWidget widget : mainTabScreen) { - - if (getPositionRule(widget.getInternalID()) != null) { - widget.setPositioned(false); - } else { - newPositioner.positionWidget(widget); - widget.setPositioned(true); - } - } - newPositioner.finalizePositioning(); - // Custom positioning - for (HudWidget widget : mainTabScreen) { - if (!widget.isPositioned()) { - WidgetPositioner.applyRuleToWidget(widget, screenW, screenH, this::getPositionRule); - } - } - - for (HudWidget widget : hudScreen) { - if (!widget.isPositioned()) { - WidgetPositioner.applyRuleToWidget(widget, screenW, screenH, this::getPositionRule); - } - } - for (HudWidget widget : secondaryTabScreen) { - if (!widget.isPositioned()) { - WidgetPositioner.applyRuleToWidget(widget, screenW, screenH, this::getPositionRule); - } - } + public LayerBuilder secondaryTab() { + return secondaryTab; } - /** - * Renders the widgets present on the specified layer. Doesn't scale with the config option. - */ - public void extractWidgetsRenderState(GuiGraphicsExtractor graphics, WidgetManager.ScreenLayer screenLayer) { - List widgetsToRender = getHudWidgets(screenLayer); - - for (HudWidget widget : widgetsToRender) { - widget.extractRenderState(graphics); - } + public void updateFancyTab() { + tab.updateTab(config.hiddenTabWidgets); } - public List getHudWidgets(WidgetManager.ScreenLayer screenLayer) { - return switch (screenLayer) { - case MAIN_TAB -> mainTabScreen; - case SECONDARY_TAB -> secondaryTabScreen; - case HUD -> hudScreen; - case null, default -> List.of(); - }; + public void clearFancyTab() { + tab.clearTab(); } - /** - * Builds and renders the given {@link de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager.ScreenLayer WidgetManager.ScreenLayer}, which - * {@link #updateWidgetLists(boolean) updates the widget lists (for all screen layers)}, {@link #updateWidgets(WidgetManager.ScreenLayer) updates the widgets (for the current screen layer)}, - * {@link #positionWidgets(int, int) positions the widgets}, and {@link #extractWidgetsRenderState(GuiGraphics, WidgetManager.ScreenLayer) renders the widgets}. - */ - public void run(GuiGraphicsExtractor graphics, int screenW, int screenH, WidgetManager.ScreenLayer screenLayer) { - boolean widgetListsChanged = updateWidgetLists(false); - - updateWidgets(screenLayer); - - if (widgetListsChanged || positionsNeedsUpdating) { - positionsNeedsUpdating = false; - positionWidgets(screenW, screenH); - } - - extractWidgetsRenderState(graphics, screenLayer); + public void setConfig(ScreenConfig config) { + this.config = config; + hud.setConfig(config.hud()); + tab.setConfig(config.tab()); + secondaryTab.setConfig(config.secondaryTab()); } - - public enum DefaultPositioner { - TOP(TopAlignedWidgetPositioner::new), - CENTERED(CenteredWidgetPositioner::new); - - private final BiFunction function; - - DefaultPositioner(BiFunction widgetPositionerSupplier) { - function = widgetPositionerSupplier; - } - - public WidgetPositioner getNewPositioner(int screenWidth, int screenHeight) { - return function.apply(screenWidth, screenHeight); - } - - @Override - public String toString() { - return I18n.get("skyblocker.config.uiAndVisuals.tabHud.defaultPosition." + name()); - } + public boolean contains(HudWidget widget) { + return hud.contains(widget) || tab.contains(widget) || secondaryTab.contains(widget); } - } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenConfig.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenConfig.java new file mode 100644 index 00000000000..6cd37f77f08 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenConfig.java @@ -0,0 +1,56 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +import java.util.Set; +import java.util.stream.Stream; + +public class ScreenConfig { + public static final ScreenConfig DUMMY = new ScreenConfig(new LayerConfig(), new LayerConfig(), new LayerConfig()); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + LayerConfig.CODEC.fieldOf("hud").forGetter(ScreenConfig::hud), + LayerConfig.CODEC.fieldOf("tab").forGetter(ScreenConfig::tab), + LayerConfig.CODEC.fieldOf("secondary_tab").forGetter(ScreenConfig::secondaryTab) + ).apply(instance, ScreenConfig::new)); + + private final LayerConfig hud; + private final LayerConfig tab; + private final LayerConfig secondaryTab; + public final Set hiddenTabWidgets = new ObjectOpenHashSet<>(); + + public ScreenConfig(LayerConfig hud, LayerConfig tab, LayerConfig secondaryTab) { + this.hud = hud; + this.tab = tab; + this.secondaryTab = secondaryTab; + } + + public ScreenConfig() { + this(new LayerConfig(), new LayerConfig(), new LayerConfig()); + } + + public LayerConfig get(WidgetManager.ScreenLayer layer) { + return switch (layer) { + case HUD -> hud(); + case MAIN_TAB -> tab(); + case SECONDARY_TAB -> secondaryTab(); + }; + } + + public LayerConfig hud() { + return hud; + } + + public LayerConfig tab() { + return tab; + } + + public LayerConfig secondaryTab() { + return secondaryTab; + } + + public Stream allLayers() { + return Stream.of(hud, tab, secondaryTab); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/TabLayerBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/TabLayerBuilder.java new file mode 100644 index 00000000000..797f12caf89 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/TabLayerBuilder.java @@ -0,0 +1,85 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.util.profiling.Profiler; +import org.joml.Vector2i; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * A {@link LayerBuilder} specialized to display fancy tab + */ +public class TabLayerBuilder extends LayerBuilder { + private final Set merged = new ObjectOpenHashSet<>(); + private final List tabWidgets = new LinkedList<>(); + + public void clearTab() { + tabWidgets.clear(); + merge(); + } + + @Override + protected void updateList() { + merge(); + super.updateList(); + } + + protected void merge() { + merged.clear(); + merged.addAll(widgets); + merged.addAll(tabWidgets); + + renderedWidgets.clear(); + merged.stream().map(w -> w.widget).forEach(renderedWidgets::add); + } + + public void updateTab(Collection ignoredWidgets) { + Profiler.get().push("skyblocker:updateTabWidgetsList"); + Set currentWidgets = PlayerListManager.getCurrentWidgets(); + List newTabWidgets = new LinkedList<>(); + for (String s : currentWidgets) { + HudWidget widget = PlayerListManager.getTabWidget(s); + if (widget == null) continue; + if (ignoredWidgets.contains(widget.getInternalID())) continue; + PositionedWidget e = new PositionedWidget(widget, PositionRule.DEFAULT); + e.fromTab = true; + newTabWidgets.add(e); + } + if (newTabWidgets.equals(tabWidgets)) return; + tabWidgets.clear(); + tabWidgets.addAll(newTabWidgets); + /*for (HudWidget widget : tabWidgets) { + if (widget instanceof ComponentBasedWidget componentBasedWidget && !componentBasedWidget.shouldUpdateBeforeRendering()) componentBasedWidget.update(); + }*/ + merge(); + Profiler.get().pop(); + } + + @Override + public Set getRendered() { + return merged; + } + + @Override + public void updatePositions(int screenWidth, int screenHeight) { + super.updatePositions(screenWidth, screenHeight); + if (!tabWidgets.isEmpty()) { + WidgetPositioner positioner = SkyblockerConfigManager.get().uiAndVisuals.tabHud.defaultPositioning.getNewPositioner(0.9f, screenHeight); + List tabWidgets = getRendered().stream().filter(p -> p.fromTab).map(p -> p.widget).toList(); + tabWidgets.forEach(positioner::positionWidget); + Vector2i dimensions = positioner.finalizePositioning(); + int x = (screenWidth - dimensions.x) / 2; + int y = (screenHeight - dimensions.y) / 2; + tabWidgets.forEach(widget -> + widget.setPosition(widget.getX() + x, widget.getY() + y) + ); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetConfig.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetConfig.java new file mode 100644 index 00000000000..d22ff44e6c3 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetConfig.java @@ -0,0 +1,24 @@ +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; + +import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; +import de.hysky.skyblocker.utils.CodecUtils; + +import java.util.Optional; + +public record WidgetConfig(Optional config, Optional position) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + CodecUtils.JSON_OBJECT_CODEC.optionalFieldOf("config").forGetter(WidgetConfig::config), + PositionRule.CODEC.optionalFieldOf("position").forGetter(WidgetConfig::position) + ).apply(instance, WidgetConfig::new)); + + public WidgetConfig(JsonObject config, PositionRule position) { + this(Optional.of(config), Optional.of(position)); + } + + public static WidgetConfig disabled() { + return new WidgetConfig(Optional.empty(), Optional.empty()); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetManager.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetManager.java index 73eb33ff39f..d84427b6bc7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetManager.java @@ -2,31 +2,41 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.mojang.blaze3d.platform.Window; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; 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.galatea.SweepDetailsHudWidget; import de.hysky.skyblocker.skyblock.tabhud.TabHud; import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; 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; +import de.hysky.skyblocker.skyblock.tabhud.widget.PlaceholderWidget; +import de.hysky.skyblocker.utils.CodecUtils; +import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry; import net.fabricmc.fabric.api.client.rendering.v1.hud.VanillaHudElements; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.toasts.SystemToast; +import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.util.StringRepresentable; import org.joml.Matrix3x2fStack; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import java.io.BufferedReader; @@ -35,30 +45,67 @@ 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.Collections; +import java.util.EnumSet; import java.util.HashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; public class WidgetManager { + @SuppressWarnings("deprecation") + public static final Set ALLOWED_LOCATIONS = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(Location.UNKNOWN, Location.BLAZING_FORTRESS))); private static final Logger LOGGER = LogUtils.getLogger(); private static final Identifier FANCY_TAB_HUD = SkyblockerMod.id("fancy_tab_hud"); private static final Identifier FANCY_TAB = SkyblockerMod.id("fancy_tab"); - @SuppressWarnings("unused") private static final int VERSION = 2; + private static final String VERSION_KEY = "_version"; private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("hud_widgets.json"); - private static final Map BUILDER_MAP = new EnumMap<>(Arrays.stream(Location.values()).collect(Collectors.toMap(Function.identity(), ScreenBuilder::new))); + public static final ScreenBuilder SCREEN_BUILDER = new ScreenBuilder(); - public static final Map widgetInstances = new HashMap<>(); + private static Config CONFIG = new Config(); - public static ScreenBuilder getScreenBuilder(Location location) { - return BUILDER_MAP.get(location); + public static final Map WIDGET_INSTANCES = new HashMap<>(); + + private static boolean showOldVersionMessage = false; + private static boolean hasFancyTab = false; + + public static ScreenConfig getScreenConfig(Location screenId) { + return CONFIG.screenConfigs().computeIfAbsent(screenId, _ -> new ScreenConfig()); + } + + public static CopyTracker getCopyTracker() { + return CONFIG.copyTracker(); + } + + public static boolean hasFancyTab() { + return hasFancyTab; + } + + public static HudWidget getWidgetOrPlaceholder(String id) { + return WIDGET_INSTANCES.computeIfAbsent(id, PlaceholderWidget::new); + } + + public static boolean isWidgetInCurrentScreen(HudWidget widget) { + return SCREEN_BUILDER.contains(widget); + } + + public static boolean isWidgetInCurrentLayer(HudWidget widget) { + return currentLayer != null && SCREEN_BUILDER.get(currentLayer).contains(widget); + } + + public static List getWidgetsAvailableIn(Location location) { + return WIDGET_INSTANCES.values().stream().filter(w -> w.getInformation().available().test(location)).toList(); } + private static @Nullable Location currentLocation; + private static @Nullable ScreenLayer currentLayer; + // we probably want this to run pretty early? @Init(priority = -1) public static void init() { @@ -71,6 +118,19 @@ public static void init() { } loadConfig(); + PlayerListManager.registerTabListener(WidgetManager::onPlayerListUpdate); + }); + + SkyblockEvents.JOIN.register(() -> { + if (showOldVersionMessage) { + Scheduler.INSTANCE.schedule(() -> { + if (Minecraft.getInstance().player == null) return; + Minecraft.getInstance().player.sendSystemMessage(Constants.PREFIX.get().append( + "The HUD system has changed! Sadly your previous config couldn't be ported over due to complications... Check out /skyblocker hud!" + )); + showOldVersionMessage = false; + }, 10 * 20); // displays a bit too early and gets burried by tips and other stuff instantly, so we delay a bit + } }); ClientLifecycleEvents.CLIENT_STOPPING.register(_ -> saveConfig()); @@ -95,102 +155,178 @@ private static void extractRenderState(GuiGraphicsExtractor context, boolean hud matrices.popMatrix(); } + public static void onPlayerListUpdate() { + boolean fancyTab = SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled; + if (!fancyTab && hasFancyTab) { + SCREEN_BUILDER.clearFancyTab(); + } else if (fancyTab) { + SCREEN_BUILDER.updateFancyTab(); + hasFancyTab = true; + } + } + /** * Top level render method. - * Calls the appropriate ScreenBuilder with the screen's dimensions + * Calls the appropriate LayerBuilder with the screen's dimensions * * @param hud true to only render the hud (always on screen) widgets, false to only render the tab widgets. */ private static void extractRenderState(GuiGraphicsExtractor context, int w, int h, boolean hud) { Minecraft client = Minecraft.getInstance(); - ScreenBuilder screenBuilder = getScreenBuilder(Utils.getLocation()); + ScreenLayer layer; if (client.options.keyPlayerList.isDown()) { if (hud || TabHud.shouldRenderVanilla()) return; if (TabHud.toggleSecondary.isDown()) { - screenBuilder.run(context, w, h, ScreenLayer.SECONDARY_TAB); + layer = ScreenLayer.SECONDARY_TAB; } else { - screenBuilder.run(context, w, h, ScreenLayer.MAIN_TAB); + layer = ScreenLayer.MAIN_TAB; } } else if (hud) { - screenBuilder.run(context, w, h, ScreenLayer.HUD); + layer = ScreenLayer.HUD; + } else return; + Location location = Utils.getLocation(); + if (currentLocation != location) { + currentLocation = location; + SCREEN_BUILDER.setConfig(getScreenConfig(currentLocation)); + currentLayer = null; // force second condition to trigger } + if (currentLayer != layer) { + currentLayer = layer; + SCREEN_BUILDER.get(layer).update(); + } + SCREEN_BUILDER.get(currentLayer).extractRenderStates(context, w, h, false); } public static void loadConfig() { try (BufferedReader reader = Files.newBufferedReader(FILE)) { - JsonObject object = SkyblockerMod.GSON.fromJson(reader, JsonObject.class); - JsonObject positions = object.getAsJsonObject("positions"); - for (Map.Entry builderEntry : BUILDER_MAP.entrySet()) { - Location location = builderEntry.getKey(); - ScreenBuilder screenBuilder = builderEntry.getValue(); - if (positions.has(location.id())) { - JsonObject locationObject = positions.getAsJsonObject(location.id()); - for (Map.Entry entry : locationObject.entrySet()) { - PositionRule.CODEC.decode(JsonOps.INSTANCE, entry.getValue()) - .ifSuccess(pair -> screenBuilder.setPositionRule(entry.getKey(), pair.getFirst())) - .ifError(pairError -> LOGGER.error("[Skyblocker] Failed to parse position rule: {}", pairError.messageSupplier().get())); - } - } + AtomicReference<@Nullable String> error = new AtomicReference<>(); + JsonElement input = JsonParser.parseReader(reader); + if (!(input instanceof JsonObject object) || !object.has(VERSION_KEY)) { + // Don't bother TRYING to load if it's the old format. + showOldVersionMessage = true; + fillDefaultConfig(0); + LOGGER.info("[Skyblocker] Old HUD config detected. Ignoring :("); + return; + } + if (object.get(VERSION_KEY).isJsonPrimitive()) fillDefaultConfig(object.get(VERSION_KEY).getAsInt()); + CONFIG = Config.CODEC.decode(JsonOps.INSTANCE, input).resultOrPartial(error::set).orElseThrow().getFirst(); + if (error.get() != null) { // separate it to not run when the config fully cannot load + LOGGER.error("[Skyblocker] Failed to load part of the HUD config", new Exception(error.get())); + showErrorToast(); } } catch (NoSuchFileException _) { - LOGGER.warn("[Skyblocker] No hud widget config file found, using defaults"); - fillDefaultConfig(); + // Fill default config + fillDefaultConfig(0); } catch (Exception e) { - LOGGER.error("[Skyblocker] Failed to load hud widgets config", e); + LOGGER.error("[Skyblocker] Failed to HUD load config", e); + showErrorToast(); } } - public static void saveConfig() { - JsonObject output = new JsonObject(); - JsonObject positions = new JsonObject(); - for (Map.Entry builderEntry : BUILDER_MAP.entrySet()) { - Location location = builderEntry.getKey(); - ScreenBuilder screenBuilder = builderEntry.getValue(); - JsonObject locationObject = new JsonObject(); - screenBuilder.forEachPositionRuleEntry((s, positionRule) -> locationObject.add(s, PositionRule.CODEC.encodeStart(JsonOps.INSTANCE, positionRule).getOrThrow())); - if (locationObject.isEmpty()) continue; - positions.add(location.id(), locationObject); - } - output.add("positions", positions); - try (BufferedWriter writer = Files.newBufferedWriter(FILE)) { - SkyblockerMod.GSON.toJson(output, writer); - LOGGER.info("[Skyblocker] Saved hud widget config"); - } catch (IOException e) { - LOGGER.error("[Skyblocker] Failed to save hud widget config", e); - } + private static void showErrorToast() { + SystemToast.add(Minecraft.getInstance().getToastManager(), new SystemToast.SystemToastId(), Component.literal("Error reading Skyblocker HUD Config"), Component.literal("Check your logs!")); } - // All non-tab HUDs should have a position rule initialised here, because they don't have an auto positioning - private static void fillDefaultConfig() { - ScreenBuilder screenBuilder = getScreenBuilder(Location.THE_END); - screenBuilder.setPositionRule( - "hud_end", - new PositionRule("screen", PositionRule.Point.DEFAULT, PositionRule.Point.DEFAULT, SkyblockerConfigManager.get().otherLocations.end.x, SkyblockerConfigManager.get().otherLocations.end.y, WidgetManager.ScreenLayer.HUD) - ); + private static void fillDefaultConfig(int comingFromVersion) { + if (comingFromVersion <= 0) { + EditableScreenBuilder editableScreenBuilder = new EditableScreenBuilder(); + LayerBuilderEditor hud = editableScreenBuilder.getEditor(ScreenLayer.HUD); + // Mining related stuff + + HudWidget commissions = getWidgetOrPlaceholder("commissions"); + HudWidget powders = getWidgetOrPlaceholder("powders"); + EnumSet miningLocations = EnumSet.of(Location.CRYSTAL_HOLLOWS, Location.DWARVEN_MINES, Location.GLACITE_MINESHAFTS); + getCopyTracker().hud().getOrCreate(commissions.getInternalID()).track(miningLocations); + getCopyTracker().hud().getOrCreate(powders.getInternalID()).track(miningLocations); - for (Location loc : new Location[]{Location.CRYSTAL_HOLLOWS, Location.DWARVEN_MINES}) { - screenBuilder = getScreenBuilder(loc); - screenBuilder.setPositionRule( - CommsWidget.ID, - new PositionRule("screen", PositionRule.Point.DEFAULT, PositionRule.Point.DEFAULT, 5, 5, WidgetManager.ScreenLayer.HUD) + PositionRule commsRule = new PositionRule( + Optional.empty(), + new PositionRule.Point(PositionRule.VerticalPoint.BOTTOM, PositionRule.HorizontalPoint.LEFT), + new PositionRule.Point(PositionRule.VerticalPoint.TOP, PositionRule.HorizontalPoint.LEFT), + 5, + 5 ); - screenBuilder.setPositionRule( - "powders", - new PositionRule(CommsWidget.ID, new PositionRule.Point(PositionRule.VerticalPoint.BOTTOM, PositionRule.HorizontalPoint.LEFT), PositionRule.Point.DEFAULT, 0, 2, WidgetManager.ScreenLayer.HUD) + PositionRule powderRule = new PositionRule( + "commissions", + new PositionRule.Point(PositionRule.VerticalPoint.BOTTOM, PositionRule.HorizontalPoint.LEFT), + new PositionRule.Point(PositionRule.VerticalPoint.TOP, PositionRule.HorizontalPoint.LEFT), + 0, + 2 + ); + editableScreenBuilder.setConfig(getScreenConfig(Location.DWARVEN_MINES)); + hud.add(commissions, commsRule); + hud.add(powders, powderRule); + hud.serializeConfig(); + + + editableScreenBuilder.setConfig(getScreenConfig(Location.CRYSTAL_HOLLOWS)); + hud.add(commissions, commsRule); + hud.add(powders, powderRule); + hud.add(getWidgetOrPlaceholder("hud_crystals"), new PositionRule( + Optional.empty(), + new PositionRule.Point(PositionRule.VerticalPoint.TOP, PositionRule.HorizontalPoint.RIGHT), + new PositionRule.Point(PositionRule.VerticalPoint.TOP, PositionRule.HorizontalPoint.RIGHT), + -5, + -5 + )); + hud.serializeConfig(); + + editableScreenBuilder.setConfig(getScreenConfig(Location.GLACITE_MINESHAFTS)); + hud.add(commissions, commsRule); + hud.add(powders, powderRule); + hud.serializeConfig(); + + // Sweep details + HudWidget sweepDetails = getWidgetOrPlaceholder("sweep_details"); + for (Location location : SweepDetailsHudWidget.LOCATIONS) { + editableScreenBuilder.setConfig(getScreenConfig(location)); + hud.add(sweepDetails); + hud.serializeConfig(); + } + getCopyTracker().hud().getOrCreate(sweepDetails.getInternalID()).track(SweepDetailsHudWidget.LOCATIONS); + + // Galatea + editableScreenBuilder.setConfig(getScreenConfig(Location.GALATEA)); + hud.add(getWidgetOrPlaceholder("hud_treeprogress"), new PositionRule( + "sweep_details", + new PositionRule.Point(PositionRule.VerticalPoint.BOTTOM, PositionRule.HorizontalPoint.LEFT), + new PositionRule.Point(PositionRule.VerticalPoint.TOP, PositionRule.HorizontalPoint.LEFT), + 0, + 2 + )); + hud.serializeConfig(); + + // Garden + editableScreenBuilder.setConfig(getScreenConfig(Location.GARDEN)); + hud.add(getWidgetOrPlaceholder("hud_farming")); + hud.serializeConfig(); + + // The end + editableScreenBuilder.setConfig(getScreenConfig(Location.THE_END)); + hud.add(getWidgetOrPlaceholder("hud_end")); + hud.serializeConfig(); + + editableScreenBuilder.setConfig(getScreenConfig(Location.DUNGEON)); + hud.add(getWidgetOrPlaceholder("dungeon_splits"), new PositionRule( + Optional.empty(), + new PositionRule.Point(PositionRule.VerticalPoint.CENTER, PositionRule.HorizontalPoint.LEFT), + new PositionRule.Point(PositionRule.VerticalPoint.CENTER, PositionRule.HorizontalPoint.LEFT), + 5, + 0) ); + hud.serializeConfig(); } + } - screenBuilder = getScreenBuilder(Location.DUNGEON); - screenBuilder.setPositionRule( - "Dungeon Splits", - new PositionRule( - "screen", - new PositionRule.Point(PositionRule.VerticalPoint.CENTER, PositionRule.HorizontalPoint.LEFT), - new PositionRule.Point(PositionRule.VerticalPoint.CENTER, PositionRule.HorizontalPoint.LEFT), - 5, - 0, - WidgetManager.ScreenLayer.HUD) - ); + public static void saveConfig() { + try (BufferedWriter writer = Files.newBufferedWriter(FILE)) { + JsonElement element = Config.CODEC.encodeStart(JsonOps.INSTANCE, CONFIG).getOrThrow(); + element.getAsJsonObject().addProperty(VERSION_KEY, VERSION); + SkyblockerMod.GSON.toJson(element, writer); + LOGGER.info("[Skyblocker] Saved hud widget config"); + } catch (IOException e) { + LOGGER.error("[Skyblocker] Failed to save hud widget config", e); + } } @@ -205,11 +341,8 @@ private static void instantiateWidgets() {} * Do not change the signature unless you know what you're doing. */ public static void addWidgetInstance(HudWidget widget) { - HudWidget put = widgetInstances.put(widget.getInternalID(), widget); - if (widget instanceof TabHudWidget tabHudWidget) { - PlayerListManager.tabWidgetInstances.put(tabHudWidget.getHypixelWidgetName(), tabHudWidget); - } - if (put != null) LOGGER.warn("[Skyblocker] Duplicate hud widget found: {}", widget); + HudWidget put = WIDGET_INSTANCES.put(widget.getInternalID(), widget); + if (put != null && !(put instanceof PlaceholderWidget)) LOGGER.warn("[Skyblocker] Duplicate hud widget found: {}", widget); } /** @@ -218,11 +351,7 @@ public static void addWidgetInstance(HudWidget widget) { public enum ScreenLayer implements StringRepresentable { MAIN_TAB, SECONDARY_TAB, - HUD, - /** - * Default is only present for config and isn't used anywhere else - */ - DEFAULT; + HUD; public static final Codec CODEC = StringRepresentable.fromEnum(ScreenLayer::values); @@ -232,13 +361,23 @@ public String toString() { case MAIN_TAB -> "Main Tab"; case SECONDARY_TAB -> "Secondary Tab"; case HUD -> "HUD"; - case DEFAULT -> "Default"; }; } @Override public String getSerializedName() { - return name(); + return name().toLowerCase(Locale.ENGLISH); + } + } + + public record Config(Map screenConfigs, CopyTracker copyTracker) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + CodecUtils.mutableOptional(Codec.unboundedMap(Location.CODEC, ScreenConfig.CODEC).fieldOf("widgets"), Object2ObjectOpenHashMap::new).forGetter(Config::screenConfigs), + CopyTracker.CODEC.fieldOf("copies").forGetter(Config::copyTracker) + ).apply(instance, Config::new)); + + public Config() { + this(new Object2ObjectOpenHashMap<>(), new CopyTracker()); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/WidgetPositioner.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetPositioner.java similarity index 50% rename from src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/WidgetPositioner.java rename to src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetPositioner.java index 97ca410ff7d..2f3508ea304 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/WidgetPositioner.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/WidgetPositioner.java @@ -1,17 +1,18 @@ -package de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline; +package de.hysky.skyblocker.skyblock.tabhud.screenbuilder; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PositionRule; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import java.util.function.Function; import net.minecraft.client.gui.navigation.ScreenPosition; +import org.joml.Vector2i; import org.jspecify.annotations.Nullable; public abstract class WidgetPositioner { - protected final int screenWidth; + protected final float maxHeight; protected final int screenHeight; - public WidgetPositioner(int screenWidth, int screenHeight) { - this.screenWidth = screenWidth; + public WidgetPositioner(float maxHeight, int screenHeight) { + this.maxHeight = maxHeight; this.screenHeight = screenHeight; } @@ -21,42 +22,41 @@ public WidgetPositioner(int screenWidth, int screenHeight) { * Called whenever all the widgets that need to be positioned have been fed to the positioner. * Used for centering stuff and things */ - public abstract void finalizePositioning(); + public abstract Vector2i finalizePositioning(); - public static void applyRuleToWidget(HudWidget widget, int screenWidth, int screenHeight, Function ruleProvider) { - widget.setPositioned(true); - PositionRule rule = ruleProvider.apply(widget.getInternalID()); - if (rule == null) return; + public static void applyRuleToWidget(PositionedWidget widget, int screenWidth, int screenHeight, Function widgetProvider) { + widget.positioned = true; + PositionRule rule = widget.rule; int startX; int startY; - if (rule.parent().equals("screen")) { + if (rule.parent().isEmpty()) { startX = (int) (rule.parentPoint().horizontalPoint().getPercentage() * screenWidth); startY = (int) (rule.parentPoint().verticalPoint().getPercentage() * screenHeight); } else { - HudWidget parentWidget = WidgetManager.widgetInstances.get(rule.parent()); - if (parentWidget == null) return; - if (!parentWidget.isPositioned()) applyRuleToWidget(parentWidget, screenWidth, screenHeight, ruleProvider); + PositionedWidget parentWidget = widgetProvider.apply(rule.parent().get()); + if (parentWidget.fromTab) return; + if (!parentWidget.positioned) applyRuleToWidget(parentWidget, screenWidth, screenHeight, widgetProvider); // size 0 part 2 - if (parentWidget.isVisible()) { - startX = parentWidget.getX() + (int) (rule.parentPoint().horizontalPoint().getPercentage() * parentWidget.getWidth()); - startY = parentWidget.getY() + (int) (rule.parentPoint().verticalPoint().getPercentage() * parentWidget.getHeight()); + if (parentWidget.visible) { + startX = parentWidget.widget.getX() + (int) (rule.parentPoint().horizontalPoint().getPercentage() * parentWidget.widget.getWidth()); + startY = parentWidget.widget.getY() + (int) (rule.parentPoint().verticalPoint().getPercentage() * parentWidget.widget.getHeight()); } else { - startX = parentWidget.getX(); - startY = parentWidget.getY(); + startX = parentWidget.widget.getX(); + startY = parentWidget.widget.getY(); } } // Effectively make the widget size 0 - if (widget.isVisible()) { - widget.setX(startX + rule.relativeX() - (int) (rule.thisPoint().horizontalPoint().getPercentage() * widget.getWidth())); - widget.setY(startY + rule.relativeY() - (int) (rule.thisPoint().verticalPoint().getPercentage() * widget.getHeight())); + if (widget.visible) { + widget.widget.setX(startX + rule.relativeX() - (int) (rule.thisPoint().horizontalPoint().getPercentage() * widget.widget.getWidth())); + widget.widget.setY(startY + rule.relativeY() - (int) (rule.thisPoint().verticalPoint().getPercentage() * widget.widget.getHeight())); } else { - widget.setX(startX + rule.relativeX()); - widget.setY(startY + rule.relativeY()); + widget.widget.setX(startX + rule.relativeX()); + widget.widget.setY(startY + rule.relativeY()); } } @@ -70,16 +70,16 @@ public static void applyRuleToWidget(HudWidget widget, int screenWidth, int scre * @param parentPoint The point on the parent widget that the child widget should be positioned relative to * @return The start position of the child widget */ - public static @Nullable ScreenPosition getStartPosition(String parent, int screenWidth, int screenHeight, PositionRule.Point parentPoint) { - if (parent.equals("screen")) { + public static ScreenPosition getStartPosition(@Nullable String parent, int screenWidth, int screenHeight, PositionRule.Point parentPoint) { + if (parent == null) { return new ScreenPosition( (int) (parentPoint.horizontalPoint().getPercentage() * screenWidth), (int) (parentPoint.verticalPoint().getPercentage() * screenHeight) ); } else { - HudWidget parentWidget = WidgetManager.widgetInstances.get(parent); - if (parentWidget == null) return null; + HudWidget parentWidget = WidgetManager.WIDGET_INSTANCES.get(parent); + if (parentWidget == null) return new ScreenPosition(0, 0); return new ScreenPosition( parentWidget.getX() + (int) (parentPoint.horizontalPoint().getPercentage() * parentWidget.getWidth()), diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CenteredWidgetPositioner.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CenteredWidgetPositioner.java index 261c2e33737..d3b05e8c5fe 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CenteredWidgetPositioner.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CenteredWidgetPositioner.java @@ -1,9 +1,11 @@ package de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetPositioner; import de.hysky.skyblocker.skyblock.tabhud.util.ScreenConst; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import it.unimi.dsi.fastutil.objects.ObjectIntMutablePair; import it.unimi.dsi.fastutil.objects.ObjectIntPair; +import org.joml.Vector2i; import java.util.ArrayList; import java.util.List; @@ -11,29 +13,28 @@ public class CenteredWidgetPositioner extends WidgetPositioner { int totalWidth = 0; + int totalHeight = 0; final int maxY; // each column is a pair containing a list of widgets for the rows and an int for the width of the column List>> columns = new ArrayList<>(); - private final List widgets = new ArrayList<>(); - int currentY = 0; int currentWidth = 0; - public CenteredWidgetPositioner(int screenWidth, int screenHeight) { - super(screenWidth, screenHeight); + public CenteredWidgetPositioner(float maxHeight, int screenHeight) { + super(maxHeight, screenHeight); columns.add(new ObjectIntMutablePair<>(new ArrayList<>(), 0)); - maxY = Math.min(400, (int) (screenHeight * 0.9f)); + maxY = Math.min(400, (int) (screenHeight * maxHeight)); } @Override public void positionWidget(HudWidget hudWidget) { - widgets.add(hudWidget); if (currentY + hudWidget.getHeight() > maxY) { + totalHeight = Math.max(totalHeight, currentY); currentY = 0; currentWidth = 0; columns.add(new ObjectIntMutablePair<>(new ArrayList<>(), 0)); @@ -46,7 +47,7 @@ public void positionWidget(HudWidget hudWidget) { } @Override - public void finalizePositioning() { + public Vector2i finalizePositioning() { for (int i = 0; i < columns.size(); i++) { ObjectIntPair> listObjectIntPair = columns.get(i); int columnWidth = listObjectIntPair.rightInt(); @@ -58,7 +59,7 @@ public void finalizePositioning() { height += tabHudWidget.getHeight(); } // set x and y of the widgets! - int offset = (screenHeight - height) / 2; + int offset = (totalHeight - height) / 2; for (HudWidget tabHudWidget : column) { tabHudWidget.setY(tabHudWidget.getY() + offset); if (i < columns.size() / 2) { @@ -69,11 +70,6 @@ public void finalizePositioning() { } totalWidth += columnWidth + ScreenConst.WIDGET_PAD; } - - // Center everything - int off = (screenWidth - totalWidth) / 2; - for (HudWidget tabHudWidget : widgets) { - tabHudWidget.setX(tabHudWidget.getX() + off); - } + return new Vector2i(totalWidth, totalHeight); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PositionRule.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PositionRule.java index 76fbbf91012..73afc141399 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PositionRule.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PositionRule.java @@ -2,20 +2,23 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; -public record PositionRule(String parent, Point parentPoint, Point thisPoint, int relativeX, int relativeY, - WidgetManager.ScreenLayer screenLayer) { - public static final PositionRule DEFAULT = new PositionRule("screen", Point.DEFAULT, Point.DEFAULT, 5, 5, WidgetManager.ScreenLayer.DEFAULT); +import java.util.Optional; + +public record PositionRule(Optional parent, Point parentPoint, Point thisPoint, int relativeX, int relativeY) { + public static final PositionRule DEFAULT = new PositionRule(Optional.empty(), Point.DEFAULT, Point.DEFAULT, 5, 5); public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - Codec.STRING.fieldOf("parent").forGetter(PositionRule::parent), + Codec.STRING.optionalFieldOf("parent").forGetter(PositionRule::parent), Point.CODEC.fieldOf("parent_anchor").forGetter(PositionRule::parentPoint), Point.CODEC.fieldOf("this_anchor").forGetter(PositionRule::thisPoint), Codec.INT.fieldOf("relative_x").forGetter(PositionRule::relativeX), - Codec.INT.fieldOf("relative_y").forGetter(PositionRule::relativeY), - WidgetManager.ScreenLayer.CODEC.fieldOf("layer").forGetter(PositionRule::screenLayer) + Codec.INT.fieldOf("relative_y").forGetter(PositionRule::relativeY) ).apply(instance, PositionRule::new)); + public PositionRule(String parent, Point parentPoint, Point thisPoint, int relativeX, int relativeY) { + this(parent.equals("screen") ? Optional.empty() : Optional.of(parent), parentPoint, thisPoint, relativeX, relativeY); + } + public enum HorizontalPoint { LEFT, diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/TopAlignedWidgetPositioner.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/TopAlignedWidgetPositioner.java index 9aeeee68f71..44dc27fa7fa 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/TopAlignedWidgetPositioner.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/TopAlignedWidgetPositioner.java @@ -1,31 +1,28 @@ package de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetPositioner; import de.hysky.skyblocker.skyblock.tabhud.util.ScreenConst; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; - -import java.util.ArrayList; -import java.util.List; +import org.joml.Vector2i; public class TopAlignedWidgetPositioner extends WidgetPositioner { - private static final int START_Y = 20; + private static final int START_Y = 0; private int totalWidth = 0; + private int totalHeight = 0; private int currentWidth = 0; private int currentY = START_Y; - private final List widgets = new ArrayList<>(); - - public TopAlignedWidgetPositioner(int screenWidth, int screenHeight) { - super(screenWidth, screenHeight); + public TopAlignedWidgetPositioner(float maxHeight, int screenHeight) { + super(maxHeight, screenHeight); } @Override public void positionWidget(HudWidget hudWidget) { - widgets.add(hudWidget); - - if (currentY + hudWidget.getHeight() > screenHeight * 0.75f) { + if (currentY + hudWidget.getHeight() > screenHeight * maxHeight) { + totalHeight = Math.max(totalHeight, currentY); totalWidth += currentWidth + ScreenConst.WIDGET_PAD; currentY = START_Y; currentWidth = 0; @@ -37,10 +34,9 @@ public void positionWidget(HudWidget hudWidget) { } @Override - public void finalizePositioning() { - int off = (screenWidth - totalWidth - currentWidth) / 2; - for (HudWidget widget : widgets) { - widget.setX(widget.getX() + off); - } + public Vector2i finalizePositioning() { + totalHeight = Math.max(totalHeight, currentY); + totalWidth += currentWidth + ScreenConst.WIDGET_PAD; + return new Vector2i(totalWidth, totalHeight); } } 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 index 736c7d6418e..5c8298096bb 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListManager.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/PlayerListManager.java @@ -1,18 +1,26 @@ package de.hysky.skyblocker.skyblock.tabhud.util; -import com.mojang.authlib.GameProfile; -import de.hysky.skyblocker.config.SkyblockerConfigManager; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; import de.hysky.skyblocker.mixins.accessors.PlayerTabOverlayAccessor; -import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; +import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; 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.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.client.multiplayer.PlayerInfo; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,19 +31,14 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.UUID; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientPacketListener; -import net.minecraft.client.multiplayer.PlayerInfo; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.Style; +import java.util.stream.Collectors; /** * This class may be used to get data from the player list. It doesn't get its @@ -43,6 +46,8 @@ * is holding periodically. The list is sorted like in the vanilla game. */ public class PlayerListManager { + public static boolean shouldUpdateNextTick = false; + 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*"); @@ -56,101 +61,83 @@ public class PlayerListManager { /** * The player list in tab, but a list of strings instead of {@link PlayerInfo}s. * - * @implNote All leading and trailing whitespace is removed from the strings. + * @implNote All leading and trailing whitespace are removed from the strings. */ private static List playerStringList = new ArrayList<>(); private static @Nullable String footer; - public static final Map tabWidgetInstances = new Object2ObjectOpenHashMap<>(); - public static final List tabWidgetsToShow = new ObjectArrayList<>(5); + static final Map WIDGET_MAP = new Object2ObjectLinkedOpenHashMap<>(); // linked so iterating gives the same order as the vanilla tab - private static void reset() { - if (!tabWidgetsToShow.isEmpty()) { - tabWidgetsToShow.clear(); - } + private static final Set TAB_LISTENERS = new ObjectOpenHashSet<>(); // this might not actually be a set due to how lambdas work + private static final Multimap> TAB_WIDGET_LISTENERS = MultimapBuilder.hashKeys().arrayListValues().build(); + private static final Set FOOTER_LISTENERS = new ObjectOpenHashSet<>(); + static final Map HANDLED_TAB_WIDGETS = new Object2ObjectOpenHashMap<>(); + + public static PlayerListManager.@Nullable Widget getListWidget(String name) { + return WIDGET_MAP.get(name); } - // TODO: check for changes instead of updating every second - public static void updateList() { - if (!Utils.isOnSkyblock()) { - reset(); - return; - } + public static @Nullable HudWidget getTabWidget(String name) { + return HANDLED_TAB_WIDGETS.get(name); + } - ClientPacketListener networkHandler = Minecraft.getInstance().getConnection(); + public static Set getCurrentWidgets() { + return WIDGET_MAP.keySet(); + } - // check is needed, else game crashes on server leave - if (networkHandler != null) { - playerList = networkHandler.getOnlinePlayers() - .stream() - .sorted(PlayerTabOverlayAccessor.getOrdering()) - .toList(); - playerStringList = playerList.stream() - .map(PlayerInfo::getTabListDisplayName) - .filter(Objects::nonNull) - .map(Component::getString) - .map(String::strip) - .toList(); - } + public static void addHandledTabWidget(String name, HudWidget widget) { + HANDLED_TAB_WIDGETS.put(name, widget); + } - if (!SkyblockerConfigManager.get().uiAndVisuals.tabHud.tabHudEnabled) { - reset(); - return; - } + public static void registerTabListener(Runnable listener) { + TAB_LISTENERS.add(listener); + } - if (Minecraft.getInstance().screen instanceof WidgetsConfigurationScreen widgetsConfigurationScreen && widgetsConfigurationScreen.isPreviewVisible()) return; + public static void registerFooterListener(Runnable listener) { + FOOTER_LISTENERS.add(listener); + } - if (Utils.isInDungeons()) updateDungeons(null); - else updateWidgetsFrom(playerList); + public static void registerTabWidgetListener(String widgetName, Consumer listener) { + TAB_WIDGET_LISTENERS.put(widgetName, listener); } - /** - * 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(@Nullable List lines) { - if (lines != null) { - // This is so wack I hate this - // I hate this too - playerList = new ArrayList<>(); - for (int i = 0; i < lines.size(); i++) { - playerList.add(new PlayerInfo(new GameProfile(UUID.randomUUID(), String.valueOf(i)), false)); - playerList.getLast().setTabListDisplayName(lines.get(i)); - } + public static void tryUpdateList() { + if (shouldUpdateNextTick) { + updateList(); + shouldUpdateNextTick = false; } + } - 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())); - } + public static void updateList() { + if (!Utils.isOnSkyblock()) return; + ClientPacketListener networkHandler = Minecraft.getInstance().getConnection(); - } + // check is needed, else game crashes on server leave + if (networkHandler != null) { + playerList = networkHandler.getOnlinePlayers() + .stream() + .sorted(PlayerTabOverlayAccessor.getOrdering()) + .toList(); + playerStringList = playerList.stream() + .map(PlayerInfo::getTabListDisplayName) + .filter(Objects::nonNull) + .map(Component::getString) + .map(String::strip) + .toList(); + } - /** - * 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(); + Component sideThing = Component.empty(); + List contents = new ArrayList<>(); + List playerListEntries = new ArrayList<>(); + 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 (PlayerInfo playerListEntry : lines) { + WIDGET_MAP.clear(); + for (PlayerInfo playerListEntry : playerList) { Component displayName = playerListEntry.getTabListDisplayName(); if (displayName == null) continue; String string = displayName.getString(); @@ -169,7 +156,7 @@ public static void updateWidgetsFrom(List lines) { // Check if info, if it is, dip out if (infoColumnPredicate.test(string)) { playersDone = true; - if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); + WIDGET_MAP.put(hypixelWidgetName.right(), new Widget(Component.empty(), contents, playerListEntries)); contents.clear(); playerListEntries.clear(); continue; @@ -180,13 +167,19 @@ public static void updateWidgetsFrom(List lines) { // 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(Locale.ENGLISH).startsWith("ends in"))) { - if (!contents.isEmpty()) tabWidgetsToShow.add(getTabHudWidget(hypixelWidgetName, contents, playerListEntries)); + if (!contents.isEmpty()) WIDGET_MAP.put(hypixelWidgetName.right(), new Widget(sideThing, contents, playerListEntries)); + + sideThing = Component.empty(); contents.clear(); playerListEntries.clear(); + Pair, ? extends Component> nameAndInfo = getNameAndInfo(displayName); hypixelWidgetName = nameAndInfo.left(); + if (!HANDLED_TAB_WIDGETS.containsKey(hypixelWidgetName.right())) { + WidgetManager.addWidgetInstance(new DefaultTabHudWidget(hypixelWidgetName.right(), Component.literal(hypixelWidgetName.right()).withStyle(ChatFormatting.BOLD), hypixelWidgetName.firstInt())); + } if (!nameAndInfo.right().getString().isBlank()) { - contents.add(trim(nameAndInfo.right())); + sideThing = trim(nameAndInfo.right()); playerListEntries.add(playerListEntry); } continue; @@ -196,10 +189,11 @@ public static void updateWidgetsFrom(List lines) { 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())); - } + + if (!contents.isEmpty()) WIDGET_MAP.put(hypixelWidgetName.right(), new Widget(sideThing, contents, playerListEntries)); + + TAB_LISTENERS.forEach(Runnable::run); + WIDGET_MAP.forEach((key, value) -> TAB_WIDGET_LISTENERS.get(key).forEach(c -> c.accept(value))); } private static Component trim(Component text) { @@ -234,23 +228,6 @@ private static Component trim(Component text) { 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(), Component.literal(hypixelWidgetName.right()).withStyle(ChatFormatting.BOLD), hypixelWidgetName.firstInt()); - WidgetManager.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: @@ -305,6 +282,7 @@ public static void updateFooter(@Nullable Component f) { if (footer.isEmpty()) { footer = null; } + FOOTER_LISTENERS.forEach(Runnable::run); } } @@ -427,14 +405,30 @@ public static int getSize() { } private static final class DefaultTabHudWidget extends TabHudWidget { + private DefaultTabHudWidget(String hypixelWidgetName, MutableComponent title, int color) { - super(hypixelWidgetName, title, color); + super(hypixelWidgetName, title, color, new Information(nameToId(hypixelWidgetName), title.plainCopy().append(Component.literal(" (auto)").withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC)))); } @Override - protected void updateContent(List lines) { - lines.forEach(text -> addComponent(new PlainTextElement(text))); + protected void updateContent(Widget widget) { + for (Component line : widget.lines()) { + addElement(new PlainTextElement(line)); + } } } + /** + * @param detail The text after the : on the widget's name. {@link Component#empty()} if there is none. + * @param lines The different lines, trimmed. + * @param playerListEntries The player list entries, unprocessed. If detail is present, the whole line is included as the first line in the list. + */ + public record Widget(Component detail, List lines, List playerListEntries) { + public static final Widget EMPTY = new Widget(Component.empty(), List.of(), List.of()); + public Widget(Component detail, List lines, List playerListEntries) { + this.detail = detail.copy(); + this.lines = lines.stream().map(Component::copy).collect(Collectors.toList()); + this.playerListEntries = List.copyOf(playerListEntries); + } + } } 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 index fed9a43de9a..2f4e0eba3a9 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CommsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/CommsWidget.java @@ -1,19 +1,19 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; +import com.mojang.logging.LogUtils; import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Element; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import org.slf4j.Logger; -import com.mojang.logging.LogUtils; +import java.util.regex.Matcher; +import java.util.regex.Pattern; // this widget shows the status of the king's commissions. // (dwarven mines and crystal hollows) @@ -31,16 +31,16 @@ public class CommsWidget extends TabHudWidget { public static final Pattern COMM_PATTERN = Pattern.compile("(?.*): (?.*)%?"); public CommsWidget() { - super("Commissions", TITLE, ChatFormatting.DARK_AQUA.getColor()); + super("Commissions", TITLE, ChatFormatting.DARK_AQUA.getColor(), new Information("commissions", Component.literal("Commissions"), Location.CRYSTAL_HOLLOWS, Location.DWARVEN_MINES, Location.GLACITE_MINESHAFTS)); } @Override - public void updateContent(List lines) { - if (lines.isEmpty()) { - this.addComponent(Elements.iconTextComponent()); + public void updateContent(PlayerListManager.Widget widget) { + if (widget.lines().isEmpty()) { + this.addElement(Elements.iconTextComponent()); return; } - for (Component line : lines) { + for (Component line : widget.lines()) { Matcher m = COMM_PATTERN.matcher(line.getString()); if (m.matches()) { Element element; @@ -60,7 +60,7 @@ public void updateContent(List lines) { } element = Elements.progressComponent(Ico.BOOK, Component.nullToEmpty(name), percent); } - this.addComponent(element); + this.addElement(element); } } } 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 index f66b99162c5..94968a3b796 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ComposterWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ComposterWidget.java @@ -3,15 +3,16 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.List; -import java.util.Locale; - +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.Locale; + // this widget shows info about the garden's composter @RegisterWidget public class ComposterWidget extends TabHudWidget { @@ -20,19 +21,19 @@ public class ComposterWidget extends TabHudWidget { ChatFormatting.BOLD); public ComposterWidget() { - super("Composter", TITLE, ChatFormatting.GREEN.getColor()); + super("Composter", TITLE, ChatFormatting.GREEN.getColor(), new Information("composter", Component.literal("Composter"), Location.GARDEN)); } @Override - public void updateContent(List lines) { + public void updateContent(PlayerListManager.Widget widget) { - for (Component line : lines) { + for (Component line : widget.lines()) { switch (line.getString().toLowerCase(Locale.ENGLISH)) { - case String s when s.contains("organic") -> this.addComponent(Elements.iconTextComponent(Ico.SAPLING, line)); - case String s when s.contains("fuel") -> this.addComponent(Elements.iconTextComponent(Ico.FURNACE, line)); - case String s when s.contains("time") -> this.addComponent(Elements.iconTextComponent(Ico.CLOCK, line)); - case String s when s.contains("stored") -> this.addComponent(Elements.iconTextComponent(Ico.COMPOSTER, line)); - default -> this.addComponent(new PlainTextElement(line)); + case String s when s.contains("organic") -> this.addElement(Elements.iconTextComponent(Ico.SAPLING, line)); + case String s when s.contains("fuel") -> this.addElement(Elements.iconTextComponent(Ico.FURNACE, line)); + case String s when s.contains("time") -> this.addElement(Elements.iconTextComponent(Ico.CLOCK, line)); + case String s when s.contains("stored") -> this.addElement(Elements.iconTextComponent(Ico.COMPOSTER, line)); + default -> this.addElement(new PlainTextElement(line)); } } } 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 index 2d3e64adb82..c3fa2cd0819 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonBuffWidget.java @@ -3,14 +3,14 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.Arrays; +import java.util.Comparator; + // this widget shows a list of obtained dungeon buffs @RegisterWidget public class DungeonBuffWidget extends TabHudWidget { @@ -19,16 +19,16 @@ public class DungeonBuffWidget extends TabHudWidget { ChatFormatting.BOLD); public DungeonBuffWidget() { - super("Dungeon Buffs", TITLE, ChatFormatting.DARK_PURPLE.getColor()); + super("Dungeon Buffs", TITLE, ChatFormatting.DARK_PURPLE.getColor(), new Information("dungeon_buffs", Component.literal("Dungeon Buffs"), Location.DUNGEON)); } @Override - public void updateContent(List ignored) { + public void updateContent(PlayerListManager.Widget ignored) { String footertext = PlayerListManager.getFooter(); if (footertext == null || !footertext.contains("Dungeon Buffs")) { - this.addComponent(new PlainTextElement(Component.literal("No data").withStyle(ChatFormatting.GRAY))); + this.addElement(new PlainTextElement(Component.literal("No data").withStyle(ChatFormatting.GRAY))); return; } @@ -36,7 +36,7 @@ public void updateContent(List ignored) { String[] lines = interesting.split("\n"); if (!lines[1].startsWith("Blessing")) { - this.addComponent(new PlainTextElement(Component.literal("No buffs found!").withStyle(ChatFormatting.GRAY))); + this.addElement(new PlainTextElement(Component.literal("No buffs found!").withStyle(ChatFormatting.GRAY))); return; } @@ -51,7 +51,7 @@ public void updateContent(List ignored) { break; } int color = getBlessingColor(line); - this.addComponent(new PlainTextElement(Component.literal(line).withStyle(style -> style.withColor(color)))); + this.addElement(new PlainTextElement(Component.literal(line).withStyle(style -> style.withColor(color)))); } } 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 index f34a3de6336..e745e4da57b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDeathWidget.java @@ -3,15 +3,16 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; +import de.hysky.skyblocker.skyblock.tabhud.widget.element.ElementCollector; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + // this widget shows various dungeon info // deaths, healing, dmg taken, milestones @RegisterWidget @@ -25,18 +26,18 @@ public class DungeonDeathWidget extends TabHudWidget { private static final Pattern DEATH_PATTERN = Pattern.compile("Team Deaths: (?\\d+).*"); public DungeonDeathWidget() { - super("Dungeon Deaths", TITLE, ChatFormatting.DARK_PURPLE.getColor()); + super("Dungeon Deaths", TITLE, ChatFormatting.DARK_PURPLE.getColor(), Location.DUNGEON); } @Override - public void updateContent(List ignored) { + public void updateContent(PlayerListManager.Widget ignored) { Matcher m = PlayerListManager.regexAt(25, DEATH_PATTERN); if (m == null) { - this.addComponent(Elements.iconTextComponent()); + this.addElement(Elements.iconTextComponent()); } else { ChatFormatting f = (m.group("deathnum").equals("0")) ? ChatFormatting.GREEN : ChatFormatting.RED; - Component d = simpleEntryText(m.group("deathnum"), "Deaths: ", f); - this.addComponent(Elements.iconTextComponent(Ico.SKULL, d)); + Component d = ElementCollector.simpleEntryText(m.group("deathnum"), "Deaths: ", f); + this.addElement(Elements.iconTextComponent(Ico.SKULL, d)); } this.addSimpleIcoText(Ico.IRON_SWORD, "Damage Dealt:", ChatFormatting.RED, 26); 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 index a9407b29652..1c4ae787fc8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDownedWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonDownedWidget.java @@ -3,9 +3,9 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; +import de.hysky.skyblocker.skyblock.tabhud.widget.element.ElementCollector; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; -import java.util.List; - +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; @@ -19,14 +19,14 @@ public class DungeonDownedWidget extends TabHudWidget { ChatFormatting.BOLD); public DungeonDownedWidget() { - super("Dungeon Downed", TITLE, ChatFormatting.DARK_PURPLE.getColor()); + super("Dungeon Downed", TITLE, ChatFormatting.DARK_PURPLE.getColor(), Location.DUNGEON); } @Override - public void updateContent(List ignored) { + public void updateContent(PlayerListManager.Widget ignored) { String down = PlayerListManager.strAt(21); if (down == null) { - this.addComponent(Elements.iconTextComponent()); + this.addElement(Elements.iconTextComponent()); } else { ChatFormatting format = ChatFormatting.RED; @@ -35,8 +35,8 @@ public void updateContent(List ignored) { } int idx = down.indexOf(": "); Component downed = (idx == -1) ? null - : simpleEntryText(down.substring(idx + 2), "Downed: ", format); - this.addComponent(Elements.iconTextComponent(Ico.SKULL, downed)); + : ElementCollector.simpleEntryText(down.substring(idx + 2), "Downed: ", format); + this.addElement(Elements.iconTextComponent(Ico.SKULL, downed)); } this.addSimpleIcoText(Ico.CLOCK, "Time:", ChatFormatting.GRAY, 22); 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 index 34fae05b464..7290dc32757 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java @@ -8,13 +8,14 @@ import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlayerElement; import de.hysky.skyblocker.utils.FlexibleItemStack; - -import java.util.List; -import java.util.regex.Matcher; +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.List; +import java.util.regex.Matcher; + // this widget shows info about a player in the current dungeon group public class DungeonPlayerWidget extends TabHudWidget { private static final MutableComponent TITLE = Component.literal("Player").withStyle(ChatFormatting.DARK_PURPLE, ChatFormatting.BOLD); @@ -24,27 +25,27 @@ public class DungeonPlayerWidget extends TabHudWidget { // title needs to be changeable here public DungeonPlayerWidget(int player) { - super("Dungeon Player " + player, TITLE, ChatFormatting.DARK_PURPLE.getColor()); + super("Dungeon Player " + player, TITLE, ChatFormatting.DARK_PURPLE.getColor(), Location.DUNGEON); this.player = player; } @Override - public void updateContent(List ignored) { + public void updateContent(PlayerListManager.Widget ignored) { int start = 1 + (player - 1) * 4; if (PlayerListManager.strAt(start) == null) { int idx = player - 1; - this.addComponent(Elements.iconTextComponent(Ico.SIGN, Component.literal(MSGS.get(idx)).withStyle(ChatFormatting.GRAY))); + this.addElement(Elements.iconTextComponent(Ico.SIGN, Component.literal(MSGS.get(idx)).withStyle(ChatFormatting.GRAY))); return; } Matcher m = PlayerListManager.regexAt(start, DungeonPlayerManager.PLAYER_TAB_PATTERN); if (m == null) { - this.addComponent(Elements.iconTextComponent()); - this.addComponent(Elements.iconTextComponent()); + this.addElement(Elements.iconTextComponent()); + this.addElement(Elements.iconTextComponent()); } else { Component name = Component.literal("Name: ").append(Component.literal(m.group("name")).withStyle(ChatFormatting.YELLOW)); - this.addComponent(new PlayerElement(PlayerListManager.getRaw(start), name)); + this.addElement(new PlayerElement(PlayerListManager.getRaw(start), name)); String cl = m.group("class"); String level = m.group("level"); @@ -52,7 +53,7 @@ public void updateContent(List ignored) { if (level == null) { PlainTextElement ptc = new PlainTextElement( Component.literal("Player is dead").withStyle(ChatFormatting.RED)); - this.addComponent(ptc); + this.addElement(ptc); } else { DungeonClass dungeonClass = DungeonClass.from(cl); @@ -64,7 +65,7 @@ public void updateContent(List ignored) { } Component clazz = Component.literal("Class: ").append(Component.literal(cl).withStyle(clf)); - this.addComponent(Elements.iconTextComponent(cli, clazz)); + this.addElement(Elements.iconTextComponent(cli, clazz)); } } 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 index d43fbdc4008..06c89ec536d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPuzzleWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPuzzleWidget.java @@ -4,14 +4,14 @@ import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + // this widget shows info about all puzzeles in the dungeon (name and status) @RegisterWidget public class DungeonPuzzleWidget extends TabHudWidget { @@ -27,11 +27,11 @@ public class DungeonPuzzleWidget extends TabHudWidget { private static final Pattern PUZZLE_PATTERN = Pattern.compile("(?.*): \\[(?.*)\\] ?.*"); public DungeonPuzzleWidget() { - super("Dungeon Puzzles", TITLE, ChatFormatting.DARK_PURPLE.getColor()); + super("Dungeon Puzzles", TITLE, ChatFormatting.DARK_PURPLE.getColor(), Location.DUNGEON); } @Override - public void updateContent(List ignored) { + public void updateContent(PlayerListManager.Widget ignored) { int pos = 48; while (pos < 60) { @@ -51,11 +51,11 @@ public void updateContent(List ignored) { .append(Component.literal("[").withStyle(ChatFormatting.GRAY)) .append(Component.literal(m.group("status")).withStyle(statcol, ChatFormatting.BOLD)) .append(Component.literal("]").withStyle(ChatFormatting.GRAY)); - this.addComponent(Elements.iconTextComponent(Ico.SIGN, t)); + this.addElement(Elements.iconTextComponent(Ico.SIGN, t)); pos++; } if (pos == 48) { - this.addComponent(Elements.iconTextComponent(Ico.BARRIER, Component.literal("No puzzles!").withStyle(ChatFormatting.GRAY))); + this.addElement(Elements.iconTextComponent(Ico.BARRIER, Component.literal("No puzzles!").withStyle(ChatFormatting.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 index 602444a9007..c2049edc835 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonSecretWidget.java @@ -4,12 +4,13 @@ import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; -import java.util.List; -import java.util.regex.Pattern; +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.regex.Pattern; + // this widget shows info about the secrets of the dungeon @RegisterWidget public class DungeonSecretWidget extends TabHudWidget { @@ -18,11 +19,11 @@ public class DungeonSecretWidget extends TabHudWidget { private static final Pattern DISCOVERIES = Pattern.compile("Discoveries: (\\d+)"); public DungeonSecretWidget() { - super("Dungeon Discoveries", TITLE, ChatFormatting.DARK_PURPLE.getColor()); + super("Dungeon Discoveries", TITLE, ChatFormatting.DARK_PURPLE.getColor(), Location.DUNGEON); } @Override - public void updateContent(List ignored) { + public void updateContent(PlayerListManager.Widget ignored) { if (!DungeonScore.isDungeonStarted()) { this.addSimpleIcoText(Ico.CHEST, "Secrets:", ChatFormatting.YELLOW, 30); this.addSimpleIcoText(Ico.SKULL, "Crypts:", ChatFormatting.YELLOW, 31); 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 index 13940fde153..cf1bd903377 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonServerWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonServerWidget.java @@ -4,14 +4,14 @@ import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + // this widget shows broad info about the current dungeon // opened/completed rooms, % of secrets found and time taken @RegisterWidget @@ -25,20 +25,20 @@ public class DungeonServerWidget extends TabHudWidget { private static final Pattern SECRET_PATTERN = Pattern.compile("Secrets Found: (?.*)%"); public DungeonServerWidget() { - super("Dungeon Info", TITLE, ChatFormatting.DARK_PURPLE.getColor()); + super("Dungeon Info", TITLE, ChatFormatting.DARK_PURPLE.getColor(), Location.DUNGEON); } @Override - public void updateContent(List ignored) { + public void updateContent(PlayerListManager.Widget ignored) { this.addSimpleIcoText(Ico.NTAG, "Name:", ChatFormatting.AQUA, 41); this.addSimpleIcoText(Ico.SIGN, "Rooms Visited:", ChatFormatting.DARK_PURPLE, 42); this.addSimpleIcoText(Ico.SIGN, "Rooms Completed:", ChatFormatting.LIGHT_PURPLE, 43); Matcher m = PlayerListManager.regexAt(44, SECRET_PATTERN); if (m == null) { - this.addComponent(Elements.progressComponent()); + this.addElement(Elements.progressComponent()); } else { - this.addComponent(Elements.progressComponent(Ico.CHEST, Component.nullToEmpty("Secrets found:"), + this.addElement(Elements.progressComponent(Ico.CHEST, Component.nullToEmpty("Secrets found:"), Float.parseFloat(m.group("secnum")), ChatFormatting.DARK_PURPLE.getColor())); } 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 index 24cc5332309..602a686140f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EffectWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EffectWidget.java @@ -1,19 +1,20 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; import de.hysky.skyblocker.annotations.RegisterWidget; +import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + // 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 @@ -26,30 +27,30 @@ public class EffectWidget extends TabHudWidget { public EffectWidget() { super("Active Effects", TITLE, ChatFormatting.DARK_PURPLE.getColor()); + PlayerListManager.registerFooterListener(() -> { + if (SkyblockerConfigManager.get().uiAndVisuals.tabHud.effectsFromFooter && WidgetManager.isWidgetInCurrentScreen(this)) update(); + }); } @Override - public void updateContent(List lines) { - - if (lines.isEmpty()) - fetchFromFooter(); - else - fetchFromWidget(lines); + public void updateContent(PlayerListManager.Widget widget) { + String string = widget.detail().getString().replaceAll("[()]", ""); + addElement(new PlainTextElement(Component.literal(string).withStyle(ChatFormatting.YELLOW, ChatFormatting.BOLD).append(Component.literal(" effect(s) active").withStyle(ChatFormatting.WHITE)))); + for (Component line : widget.lines()) { + addElement(new PlainTextElement(line)); + } } - private void fetchFromWidget(List lines) { - String string = lines.getFirst().getString().replaceAll("[()]", ""); - addComponent(new PlainTextElement(Component.literal(string).withStyle(ChatFormatting.YELLOW, ChatFormatting.BOLD).append(Component.literal(" effect(s) active").withStyle(ChatFormatting.WHITE)))); - for (int i = 1; i < lines.size(); i++) { - addComponent(new PlainTextElement(lines.get(i))); - } + @Override + protected void updateContentMissing() { + if (SkyblockerConfigManager.get().uiAndVisuals.tabHud.effectsFromFooter) fetchFromFooter(); } private void fetchFromFooter() { String footertext = PlayerListManager.getFooter(); if (footertext == null || !footertext.contains("Active Effects")) { - this.addComponent(Elements.iconTextComponent()); + this.addElement(Elements.iconTextComponent()); return; } @@ -58,39 +59,39 @@ private void fetchFromFooter() { if (m.find() && m.group("buff") != null) { String buff = m.group("buff"); if (buff.startsWith("Not")) { - this.addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("BOOSTER_COOKIE", Ico.COOKIE), Component.nullToEmpty("Cookie: not active"))); + this.addElement(Elements.iconTextComponent(ItemRepository.getItemStack("BOOSTER_COOKIE", Ico.COOKIE), Component.nullToEmpty("Cookie: not active"))); } else { Component cookie = Component.literal("Cookie: ").append(buff); - this.addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("BOOSTER_COOKIE", Ico.COOKIE), cookie)); + this.addElement(Elements.iconTextComponent(ItemRepository.getItemStack("BOOSTER_COOKIE", Ico.COOKIE), cookie)); } } String[] lines = footertext.split("Active Effects")[1].split("\n"); if (lines.length < 2) { - this.addComponent(Elements.iconTextComponent()); + this.addElement(Elements.iconTextComponent()); return; } if (lines[1].startsWith("No")) { Component txt = Component.literal("No effects active").withStyle(ChatFormatting.GRAY); - this.addComponent(Elements.iconTextComponent(Ico.POTION, txt)); + this.addElement(Elements.iconTextComponent(Ico.POTION, txt)); } else if (lines[1].contains("God")) { String timeleft = lines[1].split("! ")[1]; Component godpot = Component.literal("God potion!").withStyle(ChatFormatting.RED); Component txttleft = Component.literal(timeleft).withStyle(ChatFormatting.LIGHT_PURPLE); - this.addComponent(Elements.iconFatTextComponent(ItemRepository.getItemStack("GOD_POTION_2", Ico.GOD_POTION), godpot, txttleft)); + this.addElement(Elements.iconFatTextComponent(ItemRepository.getItemStack("GOD_POTION_2", Ico.GOD_POTION), godpot, txttleft)); } else { String number = lines[1].substring("You have ".length()); int idx = number.indexOf(' '); if (idx == -1 || lines.length < 4) { - this.addComponent(Elements.iconFatTextComponent()); + this.addElement(Elements.iconFatTextComponent()); return; } number = number.substring(0, idx); Component active = Component.literal("Active Effects: ") .append(Component.literal(number).withStyle(ChatFormatting.YELLOW)); - this.addComponent(Elements.iconFatTextComponent(Ico.POTION, active, + this.addElement(Elements.iconFatTextComponent(Ico.POTION, active, Component.literal(lines[2]).withStyle(ChatFormatting.AQUA))); } } 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 index 8494b3c2d27..a3ab4f5fb45 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElectionWidget.java @@ -2,17 +2,18 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; import de.hysky.skyblocker.utils.FlexibleItemStack; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; // this widget shows the status or results of the current election @RegisterWidget @@ -46,31 +47,32 @@ public ElectionWidget() { } @Override - public void updateContent(List lines) { - String status = lines.getFirst().getString(); + public void updateContent(PlayerListManager.Widget widget) { + List lines = widget.lines(); + String status = widget.detail().getString(); if (status.contains("Over!")) { // election is over - this.addComponent(Elements.iconTextComponent(Ico.BARRIER, EL_OVER)); + this.addElement(Elements.iconTextComponent(Ico.BARRIER, EL_OVER)); - for (int i = 1; i < lines.size(); i++) { - this.addComponent(new PlainTextElement(lines.get(i))); + for (Component line : lines) { + this.addElement(new PlainTextElement(line)); } } else { // election is going on - this.addSimpleIcoText(Ico.CLOCK, "Ends in: ", ChatFormatting.GOLD, lines.getFirst().getString().trim()); + this.addSimpleIcoText(Ico.CLOCK, "Ends in: ", ChatFormatting.GOLD, status.trim()); - for (int i = 1; i < lines.size(); i++) { + for (int i = 0; i < lines.size(); i++) { String string = lines.get(i).getString(); Matcher m = VOTE_PATTERN.matcher(string); if (m.matches()) { String mayorname = m.group("mayor"); String pcntstr = m.group("pcnt"); float pcnt = Float.parseFloat(pcntstr); - Component candidate = Component.literal(mayorname).withStyle(COLS[i - 1]); - this.addComponent(Elements.progressComponent(MAYOR_DATA.get(mayorname), candidate, pcnt, COLS[i - 1].getColor())); - } else this.addComponent(new PlainTextElement(lines.get(i))); + Component candidate = Component.literal(mayorname).withStyle(COLS[i]); + this.addElement(Elements.progressComponent(MAYOR_DATA.get(mayorname), candidate, pcnt, COLS[i].getColor())); + } else this.addElement(new PlainTextElement(lines.get(i))); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElementBasedWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElementBasedWidget.java index 00bdb1100be..9797b068d89 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElementBasedWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ElementBasedWidget.java @@ -1,25 +1,21 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; -import com.demonwav.mcdev.annotations.Translatable; import com.mojang.logging.LogUtils; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; -import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Element; -import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; +import de.hysky.skyblocker.skyblock.tabhud.widget.element.ElementCollector; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import de.hysky.skyblocker.utils.FlexibleItemStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.network.chat.Component; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.Options; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphicsExtractor; /** * Abstract base class for a element based Widget. @@ -27,7 +23,7 @@ * Their size is dependent on the elements inside, * the position may be changed after construction. */ -public abstract class ElementBasedWidget extends HudWidget { +public abstract class ElementBasedWidget extends HudWidget implements ElementCollector { public static final Logger LOGGER = LogUtils.getLogger(); private static final Font txtRend = Minecraft.getInstance().font; @@ -36,8 +32,7 @@ public abstract class ElementBasedWidget extends HudWidget { private static final List ERROR_ELEMENTS = List.of(new PlainTextElement(Component.literal("An error occurred! Please check logs.").withColor(0xFFFF0000))); private final ArrayList elements = new ArrayList<>(); - - private int prevW = 0, prevH = 0; + private List configElements; public static final int BORDER_SZE_N = txtRend.lineHeight + 2; public static final int BORDER_SZE_S = 4; @@ -50,22 +45,26 @@ public abstract class ElementBasedWidget extends HudWidget { private final int color; private final Component title; + private boolean lastRenderedConfig = false; + /** * Most often than not this should be instantiated only once. * * @param title title * @param colorValue the colour - * @param internalID the internal ID, for config, positioning depending on other widgets, all that good stuff */ - public ElementBasedWidget(Component title, @Nullable Integer colorValue, String internalID) { - super(internalID); + public ElementBasedWidget(Component title, @Nullable Integer colorValue, Information information) { + super(information); this.title = title; this.color = 0xFF000000 | (colorValue == null ? 0 : colorValue); + configElements = List.of(new PlainTextElement(title.plainCopy())); + pack(elements); // initial pack to limit weird rendering artifacts } - public void addComponent(Element c) { + public T addElement(T c) { c.setParent(this); this.elements.add(c); + return c; } public final boolean isEmpty() { @@ -84,66 +83,83 @@ public final void update() { this.elements.clear(); this.elements.addAll(ERROR_ELEMENTS); } - this.pack(); + if (!lastRenderedConfig) this.pack(elements); } - public abstract void updateContent(); + protected final void updateConfig() { + ElementCollection collector = new ElementCollection(); + updateConfigContent(collector); + if (!collector.getElements().isEmpty()) { + configElements = collector.getElements(); + } + this.pack(configElements); + } - /** - * Shorthand function for simple elements. - * 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(@Nullable FlexibleItemStack ico, String string, ChatFormatting fmt, int idx) { - Component txt = simpleEntryText(idx, string, fmt); - this.addComponent(Elements.iconTextComponent(ico, txt)); + @Override + public void onConfigChanged() { + super.onConfigChanged(); + updateConfig(); } - public final void addSimpleIcoText(@Nullable FlexibleItemStack ico, String string, ChatFormatting fmt, String content) { - Component txt = simpleEntryText(content, string, fmt); - this.addComponent(Elements.iconTextComponent(ico, txt)); + public abstract void updateContent(); + + protected void updateConfigContent(ElementCollector collector) { + // very basic default impl + update(); + elements.forEach(collector::addElement); } - public final void addSimpleIconTranslatableText(@Nullable FlexibleItemStack icon, @Translatable String translationKey, ChatFormatting formatting, String content) { - Component text = simpleEntryTranslatableText(translationKey, content, formatting); - this.addComponent(Elements.iconTextComponent(icon, text)); + public boolean shouldUpdateBeforeRendering() { + return false; } - public final void addSimpleIconTranslatableText(FlexibleItemStack icon, @Translatable String translationKey, ChatFormatting formatting, Component content) { - Component text = simpleEntryTranslatableText(translationKey, content, formatting); - this.addComponent(Elements.iconTextComponent(icon, text)); + @Override + protected final void extractWidgetRenderState(GuiGraphicsExtractor context, float delta) { + if (shouldUpdateBeforeRendering()) update(); + if (lastRenderedConfig) { + lastRenderedConfig = false; + pack(elements); + } + extractInternal(context, elements); } @Override - public final void extractWidgetRenderState(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) { + protected void extractWidgetRenderStateForConfig(GuiGraphicsExtractor graphics, float delta) { + if (!lastRenderedConfig) { + lastRenderedConfig = true; + pack(configElements); + } + extractInternal(graphics, configElements); + } + + private void extractInternal(GuiGraphicsExtractor context, Collection elements) { if (SkyblockerConfigManager.get().uiAndVisuals.tabHud.enableHudBackground) { Options options = Minecraft.getInstance().options; int textBackgroundColor = options.getBackgroundColor(SkyblockerConfigManager.get().uiAndVisuals.tabHud.style.isMinimal() ? MINIMAL_COL_BG_BOX : DEFAULT_COL_BG_BOX); - context.fill(x + 1, y, x + w - 1, y + h, textBackgroundColor); - context.fill(x, y + 1, x + 1, y + h - 1, textBackgroundColor); - context.fill(x + w - 1, y + 1, x + w, y + h - 1, textBackgroundColor); + context.fill(1, 0, w - 1, h, textBackgroundColor); + context.fill(0, 1, 1, h - 1, textBackgroundColor); + context.fill(w - 1, 1, w, h - 1, textBackgroundColor); } int strHeightHalf = txtRend.lineHeight / 2; int strAreaWidth = txtRend.width(title) + 4; - context.text(txtRend, title, x + 8, y + 2, this.color, false); + context.text(txtRend, title, 8, 2, this.color, false); // Only draw borders if not in minimal mode if (!SkyblockerConfigManager.get().uiAndVisuals.tabHud.style.isMinimal()) { - this.extractHorizontalLine(context, x + 2, y + 1 + strHeightHalf, 4); - this.extractHorizontalLine(context, x + 2 + strAreaWidth + 4, y + 1 + strHeightHalf, w - 4 - 4 - strAreaWidth); - this.extractHorizontalLine(context, x + 2, y + h - 2, w - 4); + this.extractHorizontalLine(context, 2, 1 + strHeightHalf, 4); + this.extractHorizontalLine(context, 2 + strAreaWidth + 4, 1 + strHeightHalf, w - 4 - 4 - strAreaWidth); + this.extractHorizontalLine(context, 2, h - 2, w - 4); - this.extractVerticalLine(context, x + 1, y + 2 + strHeightHalf, h - 4 - strHeightHalf); - this.extractVerticalLine(context, x + w - 2, y + 2 + strHeightHalf, h - 4 - strHeightHalf); + this.extractVerticalLine(context, 1, 2 + strHeightHalf, h - 4 - strHeightHalf); + this.extractVerticalLine(context, w - 2, 2 + strHeightHalf, h - 4 - strHeightHalf); } - int yOffs = y + BORDER_SZE_N; + int yOffs = BORDER_SZE_N; for (Element c : elements) { - c.extractRenderState(context, x + BORDER_SZE_W, yOffs); + c.extractRenderState(context, BORDER_SZE_W, yOffs); yOffs += c.getHeight() + Element.PAD_L; } } @@ -153,7 +169,7 @@ public final void extractWidgetRenderState(GuiGraphicsExtractor context, int mou * Must be called before returning from the widget constructor and after all * elements are added! */ - private void pack() { + private void pack(Collection elements) { h = 0; w = 0; for (Element c : elements) { @@ -167,10 +183,6 @@ private void pack() { // min width is dependent on title w = Math.max(w, BORDER_SZE_W + BORDER_SZE_E + txtRend.width(title) + 4 + 4 + 1); - // update the positions so it doesn't wait for the next tick or something - if (h != prevH || w != prevW) ScreenBuilder.markDirty(); - prevW = w; - prevH = h; } private void extractHorizontalLine(GuiGraphicsExtractor graphics, int xpos, int ypos, int width) { @@ -181,40 +193,4 @@ private void extractVerticalLine(GuiGraphicsExtractor graphics, int xpos, int yp graphics.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 @Nullable Component simpleEntryText(int idx, String entryName, ChatFormatting contentFmt) { - - String src = PlayerListManager.strAt(idx); - - if (src == null) { - return null; - } - - int cidx = src.indexOf(':'); - if (cidx == -1) { - return null; - } - - src = src.substring(src.indexOf(':') + 1); - return simpleEntryText(src, entryName, contentFmt); - } - - /** - * @return [entryName] [entryContent.formatted(contentFmt)] - */ - public static Component simpleEntryText(String entryContent, String entryName, ChatFormatting contentFmt) { - return Component.literal(entryName).append(Component.literal(entryContent).withStyle(contentFmt)); - } - - public static Component simpleEntryTranslatableText(String translationKey, String content, ChatFormatting contentFormatting) { - return Component.translatable(translationKey, Component.literal(content).withStyle(contentFormatting)); - } - - public static Component simpleEntryTranslatableText(String translationKey, Component content, ChatFormatting contentFormatting) { - return Component.translatable(translationKey, content.copy().withStyle(contentFormatting)); - } } 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 index a09b7eea1a9..d69dbec3e7e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EssenceWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EssenceWidget.java @@ -2,15 +2,16 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.List; -import java.util.Locale; - +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.Locale; + // this widget shows your dungeon essences (dungeon hub only) @RegisterWidget public class EssenceWidget extends TabHudWidget { @@ -19,24 +20,24 @@ public class EssenceWidget extends TabHudWidget { ChatFormatting.BOLD); public EssenceWidget() { - super("Essence", TITLE, ChatFormatting.DARK_AQUA.getColor()); + super("Essence", TITLE, ChatFormatting.DARK_AQUA.getColor(), Location.DUNGEON_HUB); } @Override - public void updateContent(List lines) { - for (Component line : lines) { + public void updateContent(PlayerListManager.Widget widget) { + for (Component line : widget.lines()) { switch (line.getString().toLowerCase(Locale.ENGLISH)) { - case String s when s.contains("wither") -> this.addComponent(Elements.iconTextComponent(Ico.WITHER, line)); - case String s when s.contains("spider") -> this.addComponent(Elements.iconTextComponent(Ico.STRING, line)); - case String s when s.contains("undead") -> this.addComponent(Elements.iconTextComponent(Ico.FLESH, line)); - case String s when s.contains("dragon") -> this.addComponent(Elements.iconTextComponent(Ico.DRAGON, line)); - case String s when s.contains("gold") -> this.addComponent(Elements.iconTextComponent(Ico.GOLD, line)); - case String s when s.contains("diamond") -> this.addComponent(Elements.iconTextComponent(Ico.DIAMOND, line)); - case String s when s.contains("ice") -> this.addComponent(Elements.iconTextComponent(Ico.ICE, line)); - case String s when s.contains("crimson") -> this.addComponent(Elements.iconTextComponent(Ico.REDSTONE, line)); - case String s when s.contains("forest") -> this.addComponent(Elements.iconTextComponent(Ico.FLOWERING_AZALEA_LEAVES, line)); - case String s when s.contains("fossil") -> this.addComponent(Elements.iconTextComponent(Ico.NAUTILUS_SHELL, line)); - default -> this.addComponent(new PlainTextElement(line)); + case String s when s.contains("wither") -> this.addElement(Elements.iconTextComponent(Ico.WITHER, line)); + case String s when s.contains("spider") -> this.addElement(Elements.iconTextComponent(Ico.STRING, line)); + case String s when s.contains("undead") -> this.addElement(Elements.iconTextComponent(Ico.FLESH, line)); + case String s when s.contains("dragon") -> this.addElement(Elements.iconTextComponent(Ico.DRAGON, line)); + case String s when s.contains("gold") -> this.addElement(Elements.iconTextComponent(Ico.GOLD, line)); + case String s when s.contains("diamond") -> this.addElement(Elements.iconTextComponent(Ico.DIAMOND, line)); + case String s when s.contains("ice") -> this.addElement(Elements.iconTextComponent(Ico.ICE, line)); + case String s when s.contains("crimson") -> this.addElement(Elements.iconTextComponent(Ico.REDSTONE, line)); + case String s when s.contains("forest") -> this.addElement(Elements.iconTextComponent(Ico.FLOWERING_AZALEA_LEAVES, line)); + case String s when s.contains("fossil") -> this.addElement(Elements.iconTextComponent(Ico.NAUTILUS_SHELL, line)); + default -> this.addElement(new PlainTextElement(line)); } } } 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 index 4ed794a8b90..34a3301684e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EventWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/EventWidget.java @@ -2,13 +2,14 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; -import java.util.List; - import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.List; + // this widget shows info about ongoing events (e.g. election) @RegisterWidget public class EventWidget extends TabHudWidget { @@ -20,8 +21,9 @@ public EventWidget() { } @Override - public void updateContent(List lines) { - if (!lines.isEmpty()) this.addComponent(Elements.iconTextComponent(Ico.NTAG, lines.getFirst())); - if (lines.size() > 1) this.addComponent(Elements.iconTextComponent(Ico.CLOCK, lines.get(1))); + public void updateContent(PlayerListManager.Widget widget) { + List lines = widget.lines(); + if (!lines.isEmpty()) this.addElement(Elements.iconTextComponent(Ico.NTAG, lines.getFirst())); + if (lines.size() > 1) this.addElement(Elements.iconTextComponent(Ico.CLOCK, lines.get(1))); } } 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 index 9e2e40f8eae..2c8aa269f0a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java @@ -2,17 +2,17 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + // this widget shows info about fire sales when in the hub. // or not, if there isn't one going on @RegisterWidget @@ -32,9 +32,8 @@ public FireSaleWidget() { } @Override - public void updateContent(List lines) { - for (int i = 1; i < lines.size(); i++) { - Component text = lines.get(i); + public void updateContent(PlayerListManager.Widget widget) { + for (Component text : widget.lines()) { Matcher m = FIRE_PATTERN.matcher(text.getString()); if (m.matches()) { String avail = m.group("avail"); @@ -42,11 +41,11 @@ public void updateContent(List lines) { float total = Float.parseFloat(m.group("total")) * 1000; Component prgressTxt = Component.literal(String.format("%s/%.0f", avail, total)); float pcnt = (Float.parseFloat(avail) / (total)) * 100f; - this.addComponent(Elements.progressComponent(Ico.GOLD, itemTxt, prgressTxt, pcnt)); + this.addElement(Elements.progressComponent(Ico.GOLD, itemTxt, prgressTxt, pcnt)); } else if (text.getString().toLowerCase(Locale.ENGLISH) instanceof String s && (s.contains("starts") || s.contains("starting"))) { - this.addComponent(Elements.iconTextComponent(Ico.CLOCK, text)); + this.addElement(Elements.iconTextComponent(Ico.CLOCK, text)); } else { - this.addComponent(new PlainTextElement(text)); + this.addElement(new PlainTextElement(text)); } } } 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 index 01b7c56514e..92d4c52291c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ForgeWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ForgeWidget.java @@ -2,14 +2,15 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Element; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; -import java.util.List; - import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.List; + // this widget shows what you're forging right now. // for locked slots, the unlock requirement is shown @RegisterWidget @@ -23,9 +24,9 @@ public ForgeWidget() { } @Override - public void updateContent(List lines) { - boolean b = lines.getFirst().getString().trim().startsWith("("); - for (int i = b ? 1 : 0, slot = 1; i < lines.size(); i++, slot++) { + public void updateContent(PlayerListManager.Widget widget) { + List lines = widget.lines(); + for (int i = 0, slot = 1; i < lines.size(); i++, slot++) { String trim = lines.get(i).getString().trim(); Element c; @@ -63,7 +64,7 @@ public void updateContent(List lines) { } } } - this.addComponent(c); + this.addElement(c); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/HudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/HudWidget.java index 2d921a6e6bc..ab7f920cc0e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/HudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/HudWidget.java @@ -1,94 +1,88 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.tabhud.config.OptionWidgetCollector; +import de.hysky.skyblocker.utils.Formatters; +import de.hysky.skyblocker.utils.JsonValueInput; import de.hysky.skyblocker.utils.Location; -import de.hysky.skyblocker.utils.render.gui.AbstractWidget; -import java.util.Objects; -import java.util.Set; -import net.minecraft.client.Minecraft; +import de.hysky.skyblocker.utils.render.gui.RangedSliderWidget; import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.layouts.LayoutElement; import net.minecraft.network.chat.Component; -public abstract class HudWidget extends AbstractWidget { - /** - * Single constant set for representing all possible locations for a {@code HudWidget} to prevent unnecessarily - * recreating this set many times over (not the best for efficiency). - */ - protected static final Set ALL_LOCATIONS = Set.of(Location.values()); - private final String internalID; +import java.util.EnumSet; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public abstract class HudWidget implements LayoutElement { + protected int w = 0, h = 0; + protected int x = 0, y = 0; + protected float scale = 1; + private final Information information; /** * Most often than not this should be instantiated only once. * - * @param internalID the internal ID, for config, positioning depending on other widgets, all that good stuff + * @param information the internal ID, for config, positioning depending on other widgets, all that good stuff */ - public HudWidget(String internalID) { - this.internalID = internalID; + public HudWidget(Information information) { + this.information = information; } - /** - * Whether the widget should render in this location. Using this instead of rendering nothing will - * allow "child" widgets to take this one's spot when not rendered.

- * {@link de.hysky.skyblocker.utils.Utils#getLocation()} should not be used unless you know what you're doing. - * (might not be true anymore, need testing still c: ) - * - * @param location the location - * @return true if the widget should render in the specified location - */ - public boolean shouldRender(Location location) { - return isEnabledIn(location); - } - - /** - * @return the locations where this widget can be enabled/disabled in the widgets configuration screen - */ - public abstract Set availableLocations(); + protected abstract void extractWidgetRenderState(GuiGraphicsExtractor graphics, float delta); - public abstract void setEnabledIn(Location location, boolean enabled); + protected abstract void extractWidgetRenderStateForConfig(GuiGraphicsExtractor graphics, float delta); - /** - * @param location the location - * @return if the widget is enabled in this location in general. If this is true, this widget will be shown - * as enabled in the WidgetsConfigScreen and will render in the preview tab regardless if {@link #shouldRender(Location)} - * is true or not. - */ - public abstract boolean isEnabledIn(Location location); + public final void extractRenderState(GuiGraphicsExtractor graphics, float delta) { + graphics.pose().pushMatrix(); + graphics.pose().scale(scale); + extractWidgetRenderState(graphics, delta); + graphics.pose().popMatrix(); + } - /** - * Perform all your logic here. Or in the {@link #renderWidget(GuiGraphics, int, int, float)} method if you feel like it. - * But this will be called much less often. See usages of it. - * - * @see #shouldUpdateBeforeRendering() - */ - public abstract void update(); + public final void extractRenderStateForConfig(GuiGraphicsExtractor graphics, float delta) { + graphics.pose().pushMatrix(); + graphics.pose().scale(scale); + extractWidgetRenderStateForConfig(graphics, delta); + graphics.pose().popMatrix(); + } - /** - * Returns true if the update method should be called right before rendering. - * - * @return true if it should update - */ - public boolean shouldUpdateBeforeRendering() { - return false; + public boolean shouldRender() { + return true; } - protected abstract void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta); + public void load(JsonValueInput input) { + scale = input.readFloatOr("scale", 1); + } - public final void extractRenderState(GuiGraphicsExtractor graphics) { - extractRenderState(graphics, -1, -1, Minecraft.getInstance().getDeltaTracker().getGameTimeDeltaTicks()); + public void save(JsonObject output) { + output.addProperty("scale", scale); } - @Override - public final void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { - extractWidgetRenderState(graphics, mouseX, mouseY, delta); + public void getOptionWidgets(OptionWidgetCollector collector) { + collector.addWidget(RangedSliderWidget.builder() + .defaultValue(scale) + .optionFormatter(Component.literal("Scale"), Formatters.FLOAT_NUMBERS) + .minMax(0.1, 5) + .step(0.1) + .callback(d -> scale = (float) d) + .build()); } + public void onConfigChanged() {} + /** * @param object the other HudWidget * @return true if they are the same instance or the internal id is the same. */ @Override - public boolean equals(Object object) { + public final boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; @@ -97,37 +91,80 @@ public boolean equals(Object object) { } @Override - public int hashCode() { - return Objects.hash(internalID); + public final int hashCode() { + return getInternalID().hashCode(); } - public String getInternalID() { - return internalID; + public final String getInternalID() { + return information.id(); } - public Component getDisplayName() { - return Component.nullToEmpty(getInternalID()); + public final Information getInformation() { + return information; } - // Positioner shenanigans + public final int getX() { + return this.x; + } - private boolean positioned = false; - private boolean visible = false; + public final void setX(int x) { + this.x = x; + } + public final int getY() { + return this.y; + } - public final boolean isPositioned() { - return positioned; + public final void setY(int y) { + this.y = y; } - public final void setPositioned(boolean positioned) { - this.positioned = positioned; + public final int getWidth() { + return Math.round(this.w * scale); } - public final boolean isVisible() { - return visible; + public final int getHeight() { + return Math.round(this.h * scale); + } + + public final boolean isMouseOver(double mouseX, double mouseY) { + // FIXME scaled + return mouseX >= getX() && mouseX <= getX() + getWidth() && mouseY >= getY() && mouseY < getY() + getHeight(); + } + + @Override + public final void visitWidgets(Consumer widgetVisitor) {} + + /** + * @param id the id for the config file + * @param displayName the name that will be shown in the config screen + * @param available in which locations the widget can be added. If not available everywhere, {@link java.util.EnumSet} and {@code contains} are recommended + */ + public record Information(String id, Component displayName, Predicate available) { + /** + * Shorter constructor that makes the widget available everywhere + * + * @see Information#Information(String, Component, Predicate) + */ + public Information(String id, Component displayName) { + this(id, displayName, _ -> true); + } + + public Information(String id, Component displayName, Set allowedLocations) { + this(id, displayName, allowedLocations::contains); + } + + public Information(String id, Component displayName, Location allowedLocation) { + this(id, displayName, EnumSet.of(allowedLocation)); + } + + public Information(String id, Component displayName, Location allowedLocation, Location... allowedLocations) { + this(id, displayName, EnumSet.of(allowedLocation, allowedLocations)); + } + } - public final void setVisible(boolean visible) { - this.visible = visible; + public static String nameToId(String name) { + return name.toLowerCase(Locale.ENGLISH).replace(' ', '_').replace("'", ""); } } 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 index ad90a29d8ac..e0f78568bb1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/JacobsContestWidget.java @@ -3,19 +3,19 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; import de.hysky.skyblocker.utils.FlexibleItemStack; +import de.hysky.skyblocker.utils.Location; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; -import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; - import static java.util.Map.entry; // this widget shows info about the current jacob's contest (garden only) @@ -45,14 +45,15 @@ public class JacobsContestWidget extends TabHudWidget { ); public JacobsContestWidget() { - super("Jacob's Contest", TITLE, ChatFormatting.YELLOW.getColor()); + super("Jacob's Contest", TITLE, ChatFormatting.YELLOW.getColor(), Location.HUB, Location.THE_FARMING_ISLAND, Location.GARDEN); } @Override - public void updateContent(List lines) { - for (Component line : lines) { + public void updateContent(PlayerListManager.Widget widget) { + if (widget.detail().getString().contains("left")) this.addElement(Elements.iconTextComponent(Ico.CLOCK, widget.detail())); + for (Component line : widget.lines()) { String string = line.getString(); - if (string.endsWith("left") || string.contains("Starts")) this.addComponent(Elements.iconTextComponent(Ico.CLOCK, line)); + if (string.contains("Starts")) this.addElement(Elements.iconTextComponent(Ico.CLOCK, line)); else { Matcher matcher = CROP_PATTERN.matcher(string); if (matcher.matches()) { @@ -61,9 +62,9 @@ public void updateContent(List lines) { MutableComponent cropText = Component.empty().append(crop); if (matcher.group("fortune").equals("☘")) cropText.append(Component.literal(" ☘").withStyle(ChatFormatting.GOLD)); - this.addComponent(Elements.iconTextComponent(FARM_DATA.get(crop), cropText)); - if (percentage != null) this.addComponent(new PlainTextElement(Component.literal(percentage))); - } else this.addComponent(new PlainTextElement(line)); + this.addElement(Elements.iconTextComponent(FARM_DATA.get(crop), cropText)); + if (percentage != null) this.addElement(new PlainTextElement(Component.literal(percentage))); + } else this.addElement(new PlainTextElement(line)); } } } 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 index 82bf9558d2e..aa5260ef633 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/MinionWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/MinionWidget.java @@ -1,21 +1,21 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; import de.hysky.skyblocker.annotations.RegisterWidget; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; import de.hysky.skyblocker.utils.FlexibleItemStack; +import de.hysky.skyblocker.utils.Location; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.item.Items; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.world.item.Items; - // this widget shows info about minions placed on the home island @RegisterWidget public class MinionWidget extends TabHudWidget { @@ -95,15 +95,15 @@ public class MinionWidget extends TabHudWidget { public static final Pattern MINION_PATTERN = Pattern.compile("^(?\\d+)x (?.*) (?[XVI]*) \\[(?.*)]"); public MinionWidget() { - super("Minions", TITLE, ChatFormatting.DARK_AQUA.getColor()); + super("Minions", TITLE, ChatFormatting.DARK_AQUA.getColor(), Location.PRIVATE_ISLAND); } @Override - public void updateContent(List lines) { - addComponent(new PlainTextElement(lines.getFirst().copy().append(Component.literal(" minions")))); - for (int i = 1; i < lines.size(); i++) { - String string = lines.get(i).getString(); - if (string.toLowerCase(Locale.ENGLISH).startsWith("...")) this.addComponent(new PlainTextElement(lines.get(i).copy().withStyle(ChatFormatting.GRAY))); + public void updateContent(PlayerListManager.Widget widget) { + addElement(new PlainTextElement(widget.detail().copy().append(Component.literal(" minions")))); + for (Component line : widget.lines()) { + String string = line.getString(); + if (string.toLowerCase(Locale.ENGLISH).startsWith("...")) this.addElement(new PlainTextElement(line.copy().withStyle(ChatFormatting.GRAY))); else addMinionComponent(string); } } @@ -128,7 +128,7 @@ public void addMinionComponent(String line) { // makes "BLOCKED" also red. in reality, it's some kind of crimson mt.append(Component.literal(stat).withStyle(format)); - this.addComponent(Elements.iconTextComponent(MIN_ICOS.get(min), mt)); + this.addElement(Elements.iconTextComponent(MIN_ICOS.get(min), mt)); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PetWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PetWidget.java index 236312124c3..1ce8e5207b3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PetWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PetWidget.java @@ -1,24 +1,22 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; import de.hysky.skyblocker.utils.FlexibleItemStack; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; - import net.minecraft.ChatFormatting; import net.minecraft.core.component.DataComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.concurrent.TimeUnit; + @RegisterWidget public class PetWidget extends TabHudWidget { private static final MutableComponent TITLE = Component.literal("Pet").withStyle(ChatFormatting.YELLOW, @@ -44,13 +42,13 @@ public PetWidget() { } @Override - protected void updateContent(List lines) { - for (Component line : lines) { + protected void updateContent(PlayerListManager.Widget widget) { + for (Component line : widget.lines()) { String string = line.getString(); if (string.contains("[") && string.contains("]")) { String[] split = string.split("]", 2); if (split.length < 2) { - addComponent(new PlainTextElement(line)); + addElement(new PlainTextElement(line)); continue; } @@ -59,9 +57,9 @@ protected void updateContent(List lines) { this.icon = PET_ICON_CACHE.getUnchecked(petName); this.prevString = petName; } - addComponent(Elements.iconTextComponent(this.icon, line)); + addElement(Elements.iconTextComponent(this.icon, line)); - } else addComponent(new PlainTextElement(line)); + } else addElement(new PlainTextElement(line)); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlaceholderWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlaceholderWidget.java new file mode 100644 index 00000000000..292b5cfe33e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlaceholderWidget.java @@ -0,0 +1,27 @@ +package de.hysky.skyblocker.skyblock.tabhud.widget; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.network.chat.Component; + +public class PlaceholderWidget extends HudWidget { + + public PlaceholderWidget(String id) { + super(new Information(id, Component.literal(id), _ -> false)); + } + + @Override + protected void extractWidgetRenderState(GuiGraphicsExtractor context, float delta) { + w = h = 0; + } + + @Override + public void extractWidgetRenderStateForConfig(GuiGraphicsExtractor context, float delta) { + Font textRenderer = Minecraft.getInstance().font; + h = 15; + w = textRenderer.width(getInformation().displayName()) + 10; + context.fill(0, 0, getWidth(), getHeight(), 0xAA_00_00_00); + context.centeredText(textRenderer, getInformation().displayName(), getWidth() / 2, 3, -1); + } +} 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 index 81d30a0094a..7ac84a2814a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PlayerListWidget.java @@ -2,15 +2,11 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlayerElement; -import java.util.List; - import net.minecraft.ChatFormatting; -import net.minecraft.client.multiplayer.PlayerInfo; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; -import org.jspecify.annotations.Nullable; @RegisterWidget public class PlayerListWidget extends TabHudWidget { @@ -21,14 +17,7 @@ public PlayerListWidget() { } @Override - protected void updateContent(List lines, @Nullable List playerListEntries) { - if (playerListEntries == null) { - lines.forEach(text -> addComponent(new PlainTextElement(text))); - } else { - playerListEntries.stream().sorted(SkyblockerConfigManager.get().uiAndVisuals.tabHud.nameSorting.comparator).forEach(playerListEntry -> addComponent(new PlayerElement(playerListEntry))); - } + protected void updateContent(PlayerListManager.Widget widget) { + widget.playerListEntries().stream().sorted(SkyblockerConfigManager.get().uiAndVisuals.tabHud.nameSorting.comparator).forEach(playerListEntry -> addElement(new PlayerElement(playerListEntry))); } - - @Override - protected void updateContent(List lines) {} } 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 index 41b7139267d..592dfc447ae 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/PowderWidget.java @@ -3,15 +3,16 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.utils.Formatters; +import de.hysky.skyblocker.utils.Location; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.util.Util; import org.apache.commons.lang3.math.NumberUtils; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -41,15 +42,15 @@ public class PowderWidget extends TabHudWidget { private long lastUpdate = 0; public PowderWidget() { - super("Powders", TITLE, ChatFormatting.DARK_AQUA.getColor()); + super("Powders", TITLE, ChatFormatting.DARK_AQUA.getColor(), Location.DWARVEN_MINES, Location.CRYSTAL_HOLLOWS, Location.GLACITE_MINESHAFTS); } @Override - public void updateContent(List lines) { + public void updateContent(PlayerListManager.Widget widget) { Matcher matcher = Pattern.compile("").matcher(""); // Placeholder pattern and input to construct a matcher that can be reused long msAfterLastUpdate = Util.getMillis() - lastUpdate; - for (Component line : lines) { + for (Component line : widget.lines()) { switch (matcher.reset(line.getString())) { case Matcher m when m.usePattern(MITHRIL_PATTERN).matches() -> { int mithril = parseAmount(m); @@ -57,10 +58,10 @@ public void updateContent(List lines) { if (mithril != lastMithril || msAfterLastUpdate > UPDATE_INTERVAL) { lastMithrilDiff = mithril - lastMithril; updated |= 0b1000; - addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("MITHRIL_ORE", Ico.MITHRIL), getTextToDisplay(lastMithrilDiff, line, ChatFormatting.DARK_GREEN))); + addElement(Elements.iconTextComponent(ItemRepository.getItemStack("MITHRIL_ORE", Ico.MITHRIL), getTextToDisplay(lastMithrilDiff, line, ChatFormatting.DARK_GREEN))); lastMithril = mithril; } else { - addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("MITHRIL_ORE", Ico.MITHRIL), getTextToDisplay(lastMithrilDiff, line, ChatFormatting.DARK_GREEN))); + addElement(Elements.iconTextComponent(ItemRepository.getItemStack("MITHRIL_ORE", Ico.MITHRIL), getTextToDisplay(lastMithrilDiff, line, ChatFormatting.DARK_GREEN))); } updated |= 0b001; } @@ -70,10 +71,10 @@ public void updateContent(List lines) { if (gemstone != lastGemstone || msAfterLastUpdate > UPDATE_INTERVAL) { lastGemstoneDiff = gemstone - lastGemstone; updated |= 0b1000; - addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("GEMSTONE_COLLECTION", Ico.GEMSTONE), getTextToDisplay(lastGemstoneDiff, line, ChatFormatting.LIGHT_PURPLE))); + addElement(Elements.iconTextComponent(ItemRepository.getItemStack("GEMSTONE_COLLECTION", Ico.GEMSTONE), getTextToDisplay(lastGemstoneDiff, line, ChatFormatting.LIGHT_PURPLE))); lastGemstone = gemstone; } else { - addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("GEMSTONE_COLLECTION", Ico.GEMSTONE), getTextToDisplay(lastGemstoneDiff, line, ChatFormatting.LIGHT_PURPLE))); + addElement(Elements.iconTextComponent(ItemRepository.getItemStack("GEMSTONE_COLLECTION", Ico.GEMSTONE), getTextToDisplay(lastGemstoneDiff, line, ChatFormatting.LIGHT_PURPLE))); } updated |= 0b010; } @@ -83,10 +84,10 @@ public void updateContent(List lines) { if (glacite != lastGlacite || msAfterLastUpdate > UPDATE_INTERVAL) { lastGlaciteDiff = glacite - lastGlacite; updated |= 0b1000; - addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("GLACITE", Ico.PACKED_ICE), getTextToDisplay(lastGlaciteDiff, line, ChatFormatting.AQUA))); + addElement(Elements.iconTextComponent(ItemRepository.getItemStack("GLACITE", Ico.PACKED_ICE), getTextToDisplay(lastGlaciteDiff, line, ChatFormatting.AQUA))); lastGlacite = glacite; } else { - addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("GLACITE", Ico.PACKED_ICE), getTextToDisplay(lastGlaciteDiff, line, ChatFormatting.AQUA))); + addElement(Elements.iconTextComponent(ItemRepository.getItemStack("GLACITE", Ico.PACKED_ICE), getTextToDisplay(lastGlaciteDiff, line, ChatFormatting.AQUA))); } updated |= 0b100; } 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 index 4ea020e054e..e272dc18098 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ProfileWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ProfileWidget.java @@ -2,15 +2,15 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.List; -import java.util.Locale; - import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.Locale; + // this widget shows info about your profile and bank @RegisterWidget public class ProfileWidget extends TabHudWidget { @@ -22,16 +22,15 @@ public ProfileWidget() { } @Override - public void updateContent(List lines) { - this.addComponent(Elements.iconTextComponent(Ico.SIGN, Component.literal("Profile: ").append(lines.getFirst()))); - for (int i = 1; i < lines.size(); i++) { - Component text = lines.get(i); + public void updateContent(PlayerListManager.Widget widget) { + this.addElement(Elements.iconTextComponent(Ico.SIGN, Component.literal("Profile: ").append(widget.detail()))); + for (Component text : widget.lines()) { switch (text.getString().toLowerCase(Locale.ENGLISH)) { - case String s when s.contains("bank") -> this.addComponent(Elements.iconTextComponent(Ico.GOLD, text)); - case String s when s.contains("interest") -> this.addComponent(Elements.iconTextComponent(Ico.CLOCK, text)); - case String s when s.contains("pet") -> this.addComponent(Elements.iconTextComponent(Ico.BONE, text)); - case String s when s.contains("sb level") -> this.addComponent(Elements.iconTextComponent(Ico.EXPERIENCE_BOTTLE, text)); - default -> this.addComponent(new PlainTextElement(text)); + case String s when s.contains("bank") -> this.addElement(Elements.iconTextComponent(Ico.GOLD, text)); + case String s when s.contains("interest") -> this.addElement(Elements.iconTextComponent(Ico.CLOCK, text)); + case String s when s.contains("pet") -> this.addElement(Elements.iconTextComponent(Ico.BONE, text)); + case String s when s.contains("sb level") -> this.addElement(Elements.iconTextComponent(Ico.EXPERIENCE_BOTTLE, text)); + default -> this.addElement(new PlainTextElement(text)); } } 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 index 89b579f5e4f..ec426b87153 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ServerWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/ServerWidget.java @@ -4,15 +4,15 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.List; -import java.util.Locale; - import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.Locale; + // 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 @@ -26,21 +26,20 @@ public ServerWidget() { } @Override - public void updateContent(List lines) { - this.addComponent(Elements.iconTextComponent(Ico.MAP, Component.literal("Area: ").append(lines.getFirst().copy().withStyle(ChatFormatting.DARK_AQUA)))); - for (int i = 1; i < lines.size(); i++) { - Component text = lines.get(i); + public void updateContent(PlayerListManager.Widget widget) { + this.addElement(Elements.iconTextComponent(Ico.MAP, Component.literal("Area: ").append(widget.detail().copy().withStyle(ChatFormatting.DARK_AQUA)))); + for (Component text : widget.lines()) { String string = text.getString(); switch (string.toLowerCase(Locale.ENGLISH)) { case String s when s.contains("server") -> this.addSimpleIcoText(Ico.NTAG, "Server ID:", ChatFormatting.GRAY, string.split(":", 2)[1]); - case String s when s.contains("gems") -> this.addComponent(Elements.iconTextComponent(Ico.EMERALD, text)); - case String s when s.contains("crystals") -> this.addComponent(Elements.iconTextComponent(Ico.EMERALD, text)); - case String s when s.contains("copper") -> this.addComponent(Elements.iconTextComponent(Ico.COPPER, text)); - case String s when s.contains("garden") -> this.addComponent(Elements.iconTextComponent(Ico.EXPERIENCE_BOTTLE, text)); - case String s when s.contains("fairy") -> this.addComponent(Elements.iconTextComponent(ItemRepository.getItemStack("PLACEABLE_FAIRY_SOUL_RIFT", Ico.FAIRY_SOUL), text)); - case String s when s.contains("rain") -> this.addComponent(Elements.iconTextComponent(Ico.WATER, text)); - case String s when s.contains("brood") -> this.addComponent(Elements.iconTextComponent(Ico.SPIDER_EYE, text)); - default -> this.addComponent(new PlainTextElement(text)); + case String s when s.contains("gems") -> this.addElement(Elements.iconTextComponent(Ico.EMERALD, text)); + case String s when s.contains("crystals") -> this.addElement(Elements.iconTextComponent(Ico.EMERALD, text)); + case String s when s.contains("copper") -> this.addElement(Elements.iconTextComponent(Ico.COPPER, text)); + case String s when s.contains("garden") -> this.addElement(Elements.iconTextComponent(Ico.EXPERIENCE_BOTTLE, text)); + case String s when s.contains("fairy") -> this.addElement(Elements.iconTextComponent(ItemRepository.getItemStack("PLACEABLE_FAIRY_SOUL_RIFT", Ico.FAIRY_SOUL), text)); + case String s when s.contains("rain") -> this.addElement(Elements.iconTextComponent(Ico.WATER, text)); + case String s when s.contains("brood") -> this.addElement(Elements.iconTextComponent(Ico.SPIDER_EYE, text)); + default -> this.addElement(new PlainTextElement(text)); } } } 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 index 8661010cc72..df377154fab 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/SkillsWidget.java @@ -2,16 +2,17 @@ import de.hysky.skyblocker.annotations.RegisterWidget; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Element; import de.hysky.skyblocker.skyblock.tabhud.widget.element.Elements; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + // this widget shows info about a skill and some stats, // as seen in the rightmost column of the default HUD @RegisterWidget @@ -31,8 +32,8 @@ public SkillsWidget() { } @Override - public void updateContent(List lines) { - for (Component line : lines) { + public void updateContent(PlayerListManager.Widget widget) { + for (Component line : widget.lines()) { Element progress; Matcher m = SKILL_PATTERN.matcher(line.getString()); if (m.matches()) { @@ -49,7 +50,7 @@ public void updateContent(List lines) { } else { progress = new PlainTextElement(line); } - this.addComponent(progress); + this.addElement(progress); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TabHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TabHudWidget.java index 42c11ed628d..59d0787dbf7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TabHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TabHudWidget.java @@ -1,105 +1,75 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; -import de.hysky.skyblocker.skyblock.tabhud.widget.element.Element; +import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.WidgetManager; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; +import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; import de.hysky.skyblocker.utils.Location; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import net.minecraft.client.multiplayer.PlayerInfo; +import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import org.jspecify.annotations.Nullable; +import java.util.List; + +/** + * An {@link ElementBasedWidget} specialized to replace/use information from one hypixel TAB widget.

+ * {@link TabHudWidget#updateContent(PlayerListManager.Widget)} is automatically called when the player list updates with the specified + * hypixel TAB widget's contents. + */ public abstract class TabHudWidget extends ElementBasedWidget { private final String hypixelWidgetName; - private final List cachedElements = new ArrayList<>(); - public TabHudWidget(String hypixelWidgetName, MutableComponent title, @Nullable Integer colorValue) { - super(title, colorValue, hypixelWidgetName.toLowerCase(Locale.ENGLISH).replace(' ', '_').replace("'", "")); + public TabHudWidget(String hypixelWidgetName, MutableComponent title, @Nullable Integer colorValue, Information information) { + super(title, colorValue, information); this.hypixelWidgetName = hypixelWidgetName; + registerAutoUpdate(); + PlayerListManager.addHandledTabWidget(hypixelWidgetName, this); } - public String getHypixelWidgetName() { - return hypixelWidgetName; - } - - @Override - public Component getDisplayName() { - return Component.literal(getHypixelWidgetName()); + public TabHudWidget(String hypixelWidgetName, MutableComponent title, @Nullable Integer colorValue) { + this(hypixelWidgetName, title, colorValue, new Information(nameToId(hypixelWidgetName), title.plainCopy())); } - @Override - public void updateContent() { - cachedElements.forEach(super::addComponent); + public TabHudWidget(String hypixelWidgetName, MutableComponent title, @Nullable Integer colorValue, Location location) { + this(hypixelWidgetName, title, colorValue, new Information(nameToId(hypixelWidgetName), title.plainCopy(), location)); } - public void updateFromTab(List lines, @Nullable List playerListEntries) { - cachedElements.clear(); - updateContent(lines, playerListEntries); + public TabHudWidget(String hypixelWidgetName, MutableComponent title, @Nullable Integer colorValue, Location location, Location... otherLocations) { + this(hypixelWidgetName, title, colorValue, new Information(nameToId(hypixelWidgetName), title.plainCopy(), location, otherLocations)); } - /** - * Controlled by Hypixel and {@link de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager PlayerListManager}. - * {@link de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder#updateWidgetLists(boolean) ScreenBuilder#updateWidgetLists} - * take the widgets directly from {@link de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager#tabWidgetsToShow PlayerListManager#tabWidgetsToShow}. - */ - @Override - public final boolean shouldRender(Location location) { - return false; + protected void registerAutoUpdate() { + PlayerListManager.registerTabListener(() -> { + if (WidgetManager.isWidgetInCurrentScreen(this)) update(); + }); } - /** - * Controlled by Hypixel and {@link de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager PlayerListManager}. - * {@link de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder#updateWidgetLists(boolean) ScreenBuilder#updateWidgetLists} - * take the widgets directly from {@link de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager#tabWidgetsToShow PlayerListManager#tabWidgetsToShow}. - */ - @Override - public final Set availableLocations() { - return Set.of(); + public String getHypixelWidgetName() { + return hypixelWidgetName; } - /** - * Controlled by Hypixel and {@link de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager PlayerListManager}. - * {@link de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder#updateWidgetLists(boolean) ScreenBuilder#updateWidgetLists} - * take the widgets directly from {@link de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager#tabWidgetsToShow PlayerListManager#tabWidgetsToShow}. - */ @Override - public final void setEnabledIn(Location location, boolean enabled) {} - - /** - * Controlled by Hypixel and {@link de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager PlayerListManager}. - * {@link de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder#updateWidgetLists(boolean) ScreenBuilder#updateWidgetLists} - * take the widgets directly from {@link de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager#tabWidgetsToShow PlayerListManager#tabWidgetsToShow}. - */ - @Override - public final boolean isEnabledIn(Location location) { - return false; + public void updateContent() { + PlayerListManager.Widget widget = PlayerListManager.getListWidget(hypixelWidgetName); + if (widget != null) updateContent(widget); + else updateContentMissing(); } - /** - * Same as {@link #updateContent(List)} but only override if you need access to {@code playerListEntries}. - * - * @param playerListEntries the player list entries, which should match the lines. - * Null in dungeons. - * @see #updateContent(List) - */ - protected void updateContent(List lines, @Nullable List playerListEntries) { - updateContent(lines); + protected void updateContentMissing() { + for (Component component : createErrorMessage()) addElement(new PlainTextElement(component)); } /** * Updates the content from the hypixel widget's lines - * - * @param lines the lines, they are formatted and trimmed, no blank lines will be present. - * If the vanilla tab widget has text right after the : they will be put on the first line. */ - protected abstract void updateContent(List lines); + protected abstract void updateContent(PlayerListManager.Widget widget); - @Override - public final void addComponent(Element c) { - cachedElements.add(c); + public List createErrorMessage() { + return List.of( + Component.translatable("skyblocker.hud.missingTabWidget[0]", Component.literal(hypixelWidgetName).withStyle(ChatFormatting.YELLOW)), + Component.translatable("skyblocker.hud.missingTabWidget[1]") + ); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TableWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TableWidget.java index 9776f7036bd..7c81efea412 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TableWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/TableWidget.java @@ -2,9 +2,9 @@ import de.hysky.skyblocker.skyblock.tabhud.widget.element.Element; import de.hysky.skyblocker.skyblock.tabhud.widget.element.TableElement; -import java.util.List; import net.minecraft.network.chat.MutableComponent; -import net.minecraft.util.CommonColors; + +import java.util.List; /** * Generic widget that arranges rows of components in equal width columns. @@ -16,25 +16,13 @@ public abstract class TableWidget extends ElementBasedWidget { private final int lineColor; private final boolean drawLines; - protected TableWidget(MutableComponent title, int colorValue, String internalId, int columns, int lineColor, boolean drawLines) { - super(title, colorValue, internalId); + protected TableWidget(MutableComponent title, int colorValue, int columns, int lineColor, boolean drawLines, Information information) { + super(title, colorValue, information); this.columns = columns; this.lineColor = lineColor; this.drawLines = drawLines; } - protected TableWidget(MutableComponent title, int colorValue, String internalId, int columns, int lineColor) { - this(title, colorValue, internalId, columns, lineColor, true); - } - - protected TableWidget(MutableComponent title, int colorValue, String internalId, int columns, boolean drawLines) { - this(title, colorValue, internalId, columns, CommonColors.WHITE, drawLines); - } - - protected TableWidget(MutableComponent title, int colorValue, String internalId, int columns) { - this(title, colorValue, internalId, columns, CommonColors.WHITE, true); - } - /** * Container class describing a single table row. */ @@ -68,6 +56,6 @@ public void updateContent() { table.setRowBorder(y, row.borderColor); } } - addComponent(table); + addElement(table); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/VisitorsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/VisitorsWidget.java index 7966984b1c7..7e723846077 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/VisitorsWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/VisitorsWidget.java @@ -1,8 +1,8 @@ package de.hysky.skyblocker.skyblock.tabhud.widget; import de.hysky.skyblocker.annotations.RegisterWidget; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.skyblock.tabhud.widget.element.PlainTextElement; -import java.util.List; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; @@ -18,16 +18,16 @@ public VisitorsWidget() { } @Override - protected void updateContent(List lines) { - String string = lines.getFirst().getString().replaceAll("[()]", ""); - addComponent(new PlainTextElement( + protected void updateContent(PlayerListManager.Widget widget) { + String string = widget.detail().getString().replaceAll("[()]", ""); + addElement(new PlainTextElement( Component.literal(string).withStyle(ChatFormatting.YELLOW, ChatFormatting.BOLD).append( Component.literal(" visitor(s)").withStyle(ChatFormatting.WHITE)) ) ); - for (int i = 1; i < lines.size(); i++) { - addComponent(new PlainTextElement(lines.get(i))); + for (Component line : widget.lines()) { + addElement(new PlainTextElement(line)); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/Element.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/Element.java index a0ea597775b..8b13fed48cf 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/Element.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/Element.java @@ -3,8 +3,6 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.utils.FlexibleItemStack; import de.hysky.skyblocker.utils.ItemUtils; - -import java.util.function.Supplier; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphicsExtractor; @@ -13,6 +11,8 @@ import net.minecraft.util.CommonColors; import net.minecraft.world.item.ItemStack; +import java.util.function.Supplier; + /** * Abstract base class for an element that may be added to a Widget. */ diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/ElementCollector.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/ElementCollector.java new file mode 100644 index 00000000000..fb5d4477813 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/ElementCollector.java @@ -0,0 +1,86 @@ +package de.hysky.skyblocker.skyblock.tabhud.widget.element; + + +import com.demonwav.mcdev.annotations.Translatable; +import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; +import de.hysky.skyblocker.utils.FlexibleItemStack; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public interface ElementCollector { + E addElement(E element); + default void addSimpleIcoText(@Nullable FlexibleItemStack ico, String string, ChatFormatting fmt, int idx) { + Component txt = simpleEntryText(idx, string, fmt); + this.addElement(Elements.iconTextComponent(ico, txt)); + } + + default void addSimpleIcoText(@Nullable FlexibleItemStack ico, String string, ChatFormatting fmt, String content) { + Component txt = simpleEntryText(content, string, fmt); + this.addElement(Elements.iconTextComponent(ico, txt)); + } + + default void addSimpleIconTranslatableText(@Nullable FlexibleItemStack icon, @Translatable String translationKey, ChatFormatting formatting, String content) { + Component text = simpleEntryTranslatableText(translationKey, content, formatting); + this.addElement(Elements.iconTextComponent(icon, text)); + } + + default void addSimpleIconTranslatableText(FlexibleItemStack icon, @Translatable String translationKey, ChatFormatting formatting, Component content) { + Component text = simpleEntryTranslatableText(translationKey, content, formatting); + this.addElement(Elements.iconTextComponent(icon, text)); + } + /** + * If the entry at idx has the format "[textA]: [textB]", the following is + * returned: + * [entryName] [textB.formatted(contentFmt)] + */ + static @Nullable Component simpleEntryText(int idx, String entryName, ChatFormatting contentFmt) { + + String src = PlayerListManager.strAt(idx); + + if (src == null) { + return null; + } + + int cidx = src.indexOf(':'); + if (cidx == -1) { + return null; + } + + src = src.substring(src.indexOf(':') + 1); + return simpleEntryText(src, entryName, contentFmt); + } + + /** + * @return [entryName] [entryContent.formatted(contentFmt)] + */ + static Component simpleEntryText(String entryContent, String entryName, ChatFormatting contentFmt) { + return Component.literal(entryName).append(Component.literal(entryContent).withStyle(contentFmt)); + } + + static Component simpleEntryTranslatableText(String translationKey, String content, ChatFormatting contentFormatting) { + return Component.translatable(translationKey, Component.literal(content).withStyle(contentFormatting)); + } + + static Component simpleEntryTranslatableText(String translationKey, Component content, ChatFormatting contentFormatting) { + return Component.translatable(translationKey, content.copy().withStyle(contentFormatting)); + } + + + class ElementCollection implements ElementCollector { + private final List elements = new ArrayList<>(); + + @Override + public E addElement(E element) { + elements.add(element); + return element; + } + + public List getElements() { + return elements; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/PlainTextElement.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/PlainTextElement.java index b49df15ce80..d93702fccab 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/PlainTextElement.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/PlainTextElement.java @@ -1,13 +1,14 @@ package de.hysky.skyblocker.skyblock.tabhud.widget.element; -import java.util.ArrayList; -import java.util.List; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.network.chat.Component; import net.minecraft.util.CommonColors; import org.jspecify.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; + /** * Element that consists of 1 or 2 lines of text. */ diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/ProgressElement.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/ProgressElement.java index 12971e7d95f..8923ab5a972 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/ProgressElement.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/element/ProgressElement.java @@ -1,15 +1,14 @@ package de.hysky.skyblocker.skyblock.tabhud.widget.element; -import net.minecraft.network.chat.Component; -import org.jspecify.annotations.Nullable; - import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.FlexibleItemStack; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.network.chat.Component; import net.minecraft.util.CommonColors; +import org.jspecify.annotations.Nullable; /** * Element that consists of an icon, some text and a progress bar. diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java index 3c0ae6a7e05..ac09335481d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/AbstractWaypointsScreen.java @@ -96,7 +96,7 @@ protected void repositionElements() { waypointsListWidget.updateSize(width, layout); waypointsListWidget.updateEntries(); islandWidget.setX(width - islandWidget.getWidth() - 10); - FrameLayout.alignInDimension(0, layout.getHeaderHeight(), DropdownWidget.HEADER_HEIGHT, islandWidget::setY, 0.5f); + FrameLayout.alignInDimension(0, layout.getHeaderHeight(), islandWidget.getHeaderHeight(), islandWidget::setY, 0.5f); islandWidget.setMaxHeight(Math.max(height - islandWidget.getY() - 8, 20)); } diff --git a/src/main/java/de/hysky/skyblocker/utils/CodecUtils.java b/src/main/java/de/hysky/skyblocker/utils/CodecUtils.java index 621659261c3..9bfde74aac7 100644 --- a/src/main/java/de/hysky/skyblocker/utils/CodecUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/CodecUtils.java @@ -10,7 +10,9 @@ import java.util.OptionalInt; import java.util.function.Function; +import com.google.gson.JsonObject; import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -24,11 +26,16 @@ import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.util.ExtraCodecs; import org.joml.Vector2i; import org.joml.Vector2ic; public final class CodecUtils { public static final Codec COLOR_CODEC = Codec.INT.xmap(argb -> new Color(argb, true), Color::getRGB); + public static final Codec JSON_OBJECT_CODEC = ExtraCodecs.JSON.flatXmap( + element -> element.isJsonObject() ? DataResult.success(element.getAsJsonObject()) : DataResult.error(() -> "Not a json object."), + DataResult::success + ); private CodecUtils() { throw new IllegalStateException("Uhhhh no? like just no. What are you trying to do? D- Do you think this will be useful to instantiate this? Like it's private, so you went through the effort of putting an accessor actually i'm not sure you can accessor a constructor. can you? so if not did you really put an access widener for that? like really? honestly this is just sad. Plus there aren't even any method in here that requires an instance. There's only static methods. like bruh. you know what i'm done typing shit for you to read, bye i'm leaving *voice lowers as I leave* I swear those modders think they can access all they want sheesh *comes back instantly* AND I SWEAR IF YOU INJECT SO THIS ERROR CANNOT BE THROWN I WILL SEND YOU TO HELL'S FREEZER"); diff --git a/src/main/java/de/hysky/skyblocker/utils/JsonValueInput.java b/src/main/java/de/hysky/skyblocker/utils/JsonValueInput.java new file mode 100644 index 00000000000..082f1b08c56 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/JsonValueInput.java @@ -0,0 +1,138 @@ +package de.hysky.skyblocker.utils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.MapCodec; +import net.minecraft.util.ProblemReporter; +import org.jspecify.annotations.Nullable; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +public class JsonValueInput { + private final JsonObject input; + private final ProblemReporter reporter; + + public JsonValueInput(ProblemReporter reporter, JsonObject input) { + this.input = input; + this.reporter = reporter; + } + + public JsonValueInput(JsonObject input) { + this(ProblemReporter.DISCARDING, input); + } + + public Optional read(String name, Codec codec) { + JsonElement element = input.get(name); + if (element == null) return Optional.empty(); + return switch (codec.parse(JsonOps.INSTANCE, element)) { + case DataResult.Success success -> Optional.of(success.value()); + case DataResult.Error error -> { + reporter.report(() -> "Failed to decode value '" + element + "' from field '" + name + "': " + error.message()); + yield error.partialValue(); + } + }; + } + + public Optional read(MapCodec codec) { + return switch (JsonOps.INSTANCE.getMap(this.input).flatMap(/* lambda$read$0 */ map -> codec.decode(JsonOps.INSTANCE, map))) { + case DataResult.Success success -> Optional.of(success.value()); + case DataResult.Error error -> { + this.reporter.report(() -> "Failed to decode from map: " + error.message()); + yield error.partialValue(); + } + }; + } + + public OptionalInt readInt(String name) { + Number element = readNumber(name); + if (element == null) return OptionalInt.empty(); + return OptionalInt.of(element.intValue()); + } + + public int readIntOr(String name, int defaultValue) { + Number element = readNumber(name); + if (element == null) return defaultValue; + return element.intValue(); + } + + public OptionalLong readLong(String name) { + Number element = readNumber(name); + if (element == null) return OptionalLong.empty(); + return OptionalLong.of(element.longValue()); + } + + public long readLongOr(String name, long defaultValue) { + Number element = readNumber(name); + if (element == null) return defaultValue; + return element.longValue(); + } + + public OptionalDouble readDouble(String name) { + Number element = readNumber(name); + if (element == null) return OptionalDouble.empty(); + return OptionalDouble.of(element.longValue()); + } + + public double readDoubleOr(String name, double defaultValue) { + Number element = readNumber(name); + if (element == null) return defaultValue; + return element.doubleValue(); + } + + public Optional readFloat(String name) { + Number element = readNumber(name); + if (element == null) return Optional.empty(); + return Optional.of(element.floatValue()); + } + + public float readFloatOr(String name, float defaultValue) { + Number element = readNumber(name); + if (element == null) return defaultValue; + return element.floatValue(); + } + + public Optional readBoolean(String name) { + JsonElement element = input.get(name); + if (element == null) return Optional.empty(); + if (!element.isJsonPrimitive()) { + reporter.report(expectedPrimtiveProblem(name, element)); + return Optional.empty(); + } + return Optional.of(element.getAsBoolean()); + } + + public boolean readBooleanOr(String name, boolean defaultValue) { + JsonElement element = input.get(name); + if (element == null) return defaultValue; + if (!element.isJsonPrimitive()) { + reporter.report(expectedPrimtiveProblem(name, element)); + return defaultValue; + } + return element.getAsBoolean(); + } + + private @Nullable Number readNumber(String name) { + JsonElement element = input.get(name); + if (element == null) return null; + if (!element.isJsonPrimitive()) { + reporter.report(expectedPrimtiveProblem(name, element)); + return null; + } + try { + return element.getAsJsonPrimitive().getAsNumber(); + } catch (NumberFormatException _) { + reporter.report(() -> "Expected valid number for " + name + ", got " + element.getAsString() + " instead."); + return null; + } + } + + private static ProblemReporter.Problem expectedPrimtiveProblem(String name, JsonElement element) { + return () -> "Expected json primitive for " + name + ", got " + element.getClass().getSimpleName() + " instead."; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/DropdownWidget.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/DropdownWidget.java index 1f1bf35b8ef..77b88d5716d 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/gui/DropdownWidget.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/DropdownWidget.java @@ -1,11 +1,13 @@ package de.hysky.skyblocker.utils.render.gui; +import com.mojang.blaze3d.platform.cursor.CursorTypes; +import de.hysky.skyblocker.utils.render.GuiHelper; import java.util.List; import java.util.function.Consumer; +import java.util.function.Function; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.AbstractContainerWidget; -import net.minecraft.client.gui.components.AbstractScrollArea; import net.minecraft.client.gui.components.ContainerObjectSelectionList; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; @@ -14,13 +16,11 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; import net.minecraft.util.CommonColors; -import com.mojang.blaze3d.platform.cursor.CursorTypes; -import de.hysky.skyblocker.utils.render.GuiHelper; public class DropdownWidget extends AbstractContainerWidget { - private static final Minecraft client = Minecraft.getInstance(); - public static final int ENTRY_HEIGHT = 15; - public static final int HEADER_HEIGHT = ENTRY_HEIGHT + 4; + public final int entryHeight; + protected int headerHeight; + protected final Minecraft client; protected final List entries; protected final Consumer selectCallback; protected final Consumer openedCallback; @@ -29,18 +29,45 @@ public class DropdownWidget extends AbstractContainerWidget { protected T selected; protected boolean open; private int maxHeight; + protected Function formatter = t -> Component.literal(t.toString()); - public DropdownWidget(Minecraft minecraftClient, int x, int y, int width, int maxHeight, List entries, Consumer selectCallback, T selected, Consumer openedCallback) { - super(x, y, width, HEADER_HEIGHT, Component.empty(), AbstractScrollArea.defaultSettings(4)); + + public DropdownWidget(Minecraft minecraftClient, int x, int y, int width, int maxHeight, int entryHeight, List entries, Consumer selectCallback, T selected, Consumer openedCallback) { + super(x, y, width, 0, Component.empty(), defaultSettings(4)); + this.client = minecraftClient; + this.entryHeight = entryHeight; + this.headerHeight = entryHeight + 4; this.maxHeight = maxHeight; this.entries = entries; this.selectCallback = selectCallback; this.openedCallback = openedCallback; this.selected = selected; - dropdownList = new DropdownList(minecraftClient, x + 1, y + HEADER_HEIGHT, width - 2, maxHeight - HEADER_HEIGHT); + dropdownList = createDropdown(); + dropdownList.setRectangle(width - 2, maxHeight - headerHeight, x + 1, y + headerHeight); for (T element : entries) { - dropdownList.addEntry(new Entry(element)); + dropdownList.addEntry(createEntry(element)); } + setHeight(headerHeight); + } + + public int getHeaderHeight() { + return headerHeight; + } + + protected DropdownList createDropdown() { + return new DropdownList(client); + } + + protected Entry createEntry(T element) { + return new Entry(element); + } + + public void setFormatter(Function formatter) { + this.formatter = formatter; + } + + public DropdownWidget(Minecraft minecraftClient, int x, int y, int width, int maxHeight, List entries, Consumer selectCallback, T selected, Consumer openedCallback) { + this(minecraftClient, x, y, width, maxHeight, 15, entries, selectCallback, selected, openedCallback); } public void setMaxHeight(int maxHeight) { @@ -57,11 +84,15 @@ public List children() { protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { dropdownList.visible = open; dropdownList.extractRenderState(graphics, mouseX, mouseY, delta); - graphics.fill(getX(), getY(), getRight(), getY() + HEADER_HEIGHT + 1, CommonColors.BLACK); - GuiHelper.border(graphics, getX(), getY(), getWidth(), HEADER_HEIGHT + 1, CommonColors.WHITE); + extractHeader(graphics, mouseX, mouseY, delta); + if (isMouseOver(mouseX, mouseY)) graphics.requestCursor(CursorTypes.POINTING_HAND); + } + + protected void extractHeader(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { + graphics.fill(getX(), getY(), getRight(), getY() + headerHeight + 1, CommonColors.BLACK); + GuiHelper.border(graphics, getX(), getY(), getWidth(), headerHeight + 1, CommonColors.WHITE); graphics.text(client.font, ">", getX() + 4, getY() + 6, CommonColors.LIGHTER_GRAY, true); graphics.text(client.font, selected.toString(), getX() + 12, getY() + 6, CommonColors.WHITE, true); - if (isMouseOver(mouseX, mouseY)) graphics.requestCursor(CursorTypes.POINTING_HAND); } @Override @@ -71,9 +102,9 @@ private void setOpen(boolean open) { this.open = open; if (this.open) { setHeight(maxHeight); - dropdownList.setHeight(Math.min(entries.size() * ENTRY_HEIGHT + 4, maxHeight - HEADER_HEIGHT)); + dropdownList.setHeight(Math.min(entries.size() * entryHeight + 4, maxHeight - headerHeight)); } else { - setHeight(HEADER_HEIGHT); + setHeight(headerHeight); } this.openedCallback.accept(open); } @@ -101,7 +132,7 @@ public void setX(int x) { @Override public void setY(int y) { super.setY(y); - dropdownList.setY(getY() + HEADER_HEIGHT); + dropdownList.setY(getY() + headerHeight); } @Override @@ -118,7 +149,7 @@ public void setHeight(int height) { @Override public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { if (!visible) return false; - if (getX() <= click.x() && click.x() < getX() + getWidth() && getY() <= click.y() && click.y() < getY() + HEADER_HEIGHT) { + if (getX() <= click.x() && click.x() < getX() + getWidth() && getY() <= click.y() && click.y() < getY() + headerHeight) { setOpen(!open); playDownSound(client.getSoundManager()); return true; @@ -144,11 +175,10 @@ public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmou return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); } - private class DropdownList extends ContainerObjectSelectionList { + protected class DropdownList extends ContainerObjectSelectionList { - private DropdownList(Minecraft minecraftClient, int x, int y, int width, int height) { - super(minecraftClient, width, height, y, ENTRY_HEIGHT); - setX(x); + protected DropdownList(Minecraft minecraftClient) { + super(minecraftClient, 0, 0, 0, entryHeight); } @Override @@ -228,11 +258,10 @@ protected void enableScissor(GuiGraphicsExtractor graphics) { } } - private class Entry extends ContainerObjectSelectionList.Entry { - - private final T entry; + protected class Entry extends ContainerObjectSelectionList.Entry { + protected final T entry; - private Entry(T element) { + protected Entry(T element) { this.entry = element; } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index f676c2b91a2..8cec853c039 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -807,6 +807,35 @@ "skyblocker.config.helpers.mythologicalRitual.enableMythologicalRitualHelper": "Enable Mythological Ritual Helper", "skyblocker.config.helpers.mythologicalRitual.enableMythologicalRitualHelper.@Tooltip": "Adds waypoints for nearby Griffin Burrows during Diana's Mythological Ritual event.", + "skyblocker.config.hud.fancyTab.enable": "Enable Fancy Tab", + "skyblocker.config.hud.fancyTab.hiddenWidgets": "Hidden Widgets", + "skyblocker.config.hud.fancyTab.positioner": "Positioner: %s", + + "skyblocker.config.hud.globalOptionsScreen.title": "Skyblock HUD and TAB options", + + "skyblocker.config.hud.globalScale": "Global Scale", + + "skyblocker.config.hud.helpText": "Use right click to add widgets and edit their options.\nYou can delete a widget by pressing the delete key.\n\nWidgets are per island. Widget options apply per location, use the 'Copy to...' button.\n\nYou can hold SHIFT to snap to other widgets.", + + "skyblocker.config.hud.location.everywhere": "Everywhere", + + "skyblocker.config.hud.position.parent": "Parent: %s", + "skyblocker.config.hud.position.parent.screen": "Screen", + "skyblocker.config.hud.position.pointParent": "Parent Point", + "skyblocker.config.hud.position.pointThis": "This Point", + + "skyblocker.config.hud.screen.rightClick": "Right click to add widgets and edit things.", + + "skyblocker.config.hud.topBar.autoAnchor": "Auto Screen Anchor", + "skyblocker.config.hud.topBar.autoAnchor.@Tooltip": "Automatically change the anchor of the widget based on its position to avoid it going off screen when the screen is resized.", + "skyblocker.config.hud.topBar.help": "Help", + "skyblocker.config.hud.topBar.options": "Options", + "skyblocker.config.hud.topBar.options.@Tooltip": "More Options", + + "skyblocker.config.hud.widget.copy": "Create Copy", + "skyblocker.config.hud.widget.inherited": "This widget is inherited from the \"Everything\" screen, edit there for the change to take place everywhere or create a copy to have it only affect here.", + "skyblocker.config.hud.widget.remove": "Remove", + "skyblocker.config.hunting": "Hunting", "skyblocker.config.hunting.huntingBoxHelper": "Enable Hunting Box Helper", @@ -1398,13 +1427,15 @@ "skyblocker.config.uiAndVisuals.tabHud.compactWidgets": "Compact Widgets", "skyblocker.config.uiAndVisuals.tabHud.compactWidgets.@Tooltip": "Display widgets in a compact form.", "skyblocker.config.uiAndVisuals.tabHud.configScreen": "Open Widget Config Screen", - "skyblocker.config.uiAndVisuals.tabHud.configScreen.@Tooltip": "Use this to move Tab and HUD widgets.\nUse `/skyblocker hud` to get here quicker!", + "skyblocker.config.uiAndVisuals.tabHud.configScreen.@Tooltip": "Add, edit and remove HUD widgets here!\n\nCan also be accessed with /skyblocker hud", "skyblocker.config.uiAndVisuals.tabHud.defaultPosition.CENTERED": "Centered", "skyblocker.config.uiAndVisuals.tabHud.defaultPosition.TOP": "Top", "skyblocker.config.uiAndVisuals.tabHud.defaultPositioning": "Default Positioning Behavior", "skyblocker.config.uiAndVisuals.tabHud.displayIcons": "Display Icons", "skyblocker.config.uiAndVisuals.tabHud.effectsFooter": "Effects from footer", "skyblocker.config.uiAndVisuals.tabHud.effectsFooter.@Tooltip": "If on, will fetch current effects from the tab footer if the Hypixel Effects Widget is disabled.", + "skyblocker.config.uiAndVisuals.tabHud.enableFancyTab": "Enable fancy TAB", + "skyblocker.config.uiAndVisuals.tabHud.enableFancyTab.@Tooltip": "Replaces the vanilla tab with a fancier version", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground": "Enable HUD Background", "skyblocker.config.uiAndVisuals.tabHud.enableHudBackground.@Tooltip": "Enables the background of the non-tab HUD.", "skyblocker.config.uiAndVisuals.tabHud.nameSorting": "Player Name Sorting Method", @@ -1420,8 +1451,7 @@ "skyblocker.config.uiAndVisuals.tabHud.style.FANCY": "Fancy", "skyblocker.config.uiAndVisuals.tabHud.style.MINIMAL": "Minimal", "skyblocker.config.uiAndVisuals.tabHud.style.SIMPLE": "Simple", - "skyblocker.config.uiAndVisuals.tabHud.tabHudEnabled": "Enable fancy tab HUD", - "skyblocker.config.uiAndVisuals.tabHud.tabHudScale": "Scale factor of fancy tab HUD", + "skyblocker.config.uiAndVisuals.tabHud.tabHudScale": "Global scale factor of the HUD", "skyblocker.config.uiAndVisuals.tabHud.tabHudScale.@Tooltip": "Value in %, relative to your vanilla GUI scale", "skyblocker.config.uiAndVisuals.teleportOverlay": "Teleport Overlay", @@ -1722,6 +1752,9 @@ "skyblocker.helpers.hoppitysHunt.shareEggPrompt": "Click to share this egg's location in chat!", "skyblocker.helpers.hoppitysHunt.unableToShareEgg": "Unable to share this egg.", + "skyblocker.hud.missingTabWidget[0]": "Missing data for %s TAB widget!", + "skyblocker.hud.missingTabWidget[1]": "Add it to the tab using /widgets!", + "skyblocker.inventorySearch.clickHereToSearch": "Click here to search.", "skyblocker.inventorySearch.searchInventory": "Search Inventory", @@ -1978,6 +2011,12 @@ "skyblocker.webSocket.receivedCrystalsWaypoint": "Received a waypoint to %s from the Skyblocker WebSocket.", + "skyblocker.widgetsList.info.overflowWarning": "It would seem you've run out of widget space in the player list! Some widgets are not being displayed correctly!", + "skyblocker.widgetsList.info.overflowWarning.columnTip": "Be sure to enabled the third column with the button to the right.", + "skyblocker.widgetsList.info.overflowWarning.wrappingSpacingTip": "Enable wrapping and disable spacing on widgets to save on space.", + "skyblocker.widgetsList.info.preview": "Hold CTRL to see a preview.", + "skyblocker.widgetsList.playerColumn": "This column is being used to display online players.\nEnable the 3rd column to have widgets instead!", + "skyblocker.wikiLookup.noArticleFound.independent": "Unable to locate an independent wiki article for this item...", "skyblocker.wikiLookup.noArticleFound.official": "Unable to locate an Official wiki article for this item...", diff --git a/src/main/resources/assets/skyblocker/textures/gui/sprites/menu_outer_space.png b/src/main/resources/assets/skyblocker/textures/gui/sprites/menu_outer_space.png new file mode 100644 index 00000000000..9038b71b39f Binary files /dev/null and b/src/main/resources/assets/skyblocker/textures/gui/sprites/menu_outer_space.png differ diff --git a/src/main/resources/assets/skyblocker/textures/gui/sprites/menu_outer_space.png.mcmeta b/src/main/resources/assets/skyblocker/textures/gui/sprites/menu_outer_space.png.mcmeta new file mode 100644 index 00000000000..91e55a1552e --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/sprites/menu_outer_space.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 64, + "height": 64, + "border": 2 + } + } +}