From 74bbab68c89af0258b45007a5f47df11d4b27ecb Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Tue, 31 Dec 2024 00:35:55 -0600 Subject: [PATCH 01/10] Sum visitors with alike crops, fix Farming XP/h regex, and improve precision for XP/h and Blocks/s --- .../skyblock/garden/FarmingHud.java | 258 ++++++++++-------- .../skyblock/garden/FarmingHudWidget.java | 4 +- .../skyblock/garden/VisitorHelper.java | 123 ++++++--- 3 files changed, 235 insertions(+), 150 deletions(-) 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 eea3a3b7ab..441c8e6f42 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java @@ -35,119 +35,147 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; public class FarmingHud { - private static final Logger LOGGER = LoggerFactory.getLogger(FarmingHud.class); - public static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US); - private static final Pattern FARMING_XP = Pattern.compile("§3\\+(?\\d+.?\\d*) Farming \\((?[\\d,]+.?\\d*)%\\)"); - private static final MinecraftClient client = MinecraftClient.getInstance(); - private static CounterType counterType = CounterType.NONE; - private static final Deque counter = new ArrayDeque<>(); - private static final LongPriorityQueue blockBreaks = new LongArrayFIFOQueue(); - private static final Queue farmingXp = new ArrayDeque<>(); - private static float farmingXpPercentProgress; - - @Init - public static void init() { - HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { - if (shouldRender()) { - if (!counter.isEmpty() && counter.peek().rightLong() + 5000 < System.currentTimeMillis()) { - counter.poll(); - } - if (!blockBreaks.isEmpty() && blockBreaks.firstLong() + 1000 < System.currentTimeMillis()) { - blockBreaks.dequeueLong(); - } - if (!farmingXp.isEmpty() && farmingXp.peek().rightLong() + 1000 < System.currentTimeMillis()) { - farmingXp.poll(); - } - - ItemStack stack = client.player.getMainHandStack(); - if (stack == null || !tryGetCounter(stack, CounterType.CULTIVATING) && !tryGetCounter(stack, CounterType.COUNTER)) { - counterType = CounterType.NONE; - } - } - }); - ClientPlayerBlockBreakEvents.AFTER.register((world, player, pos, state) -> { - if (shouldRender()) { - blockBreaks.enqueue(System.currentTimeMillis()); - } - }); - ClientReceiveMessageEvents.GAME.register((message, overlay) -> { - if (shouldRender() && overlay) { - Matcher matcher = FARMING_XP.matcher(message.getString()); - if (matcher.matches()) { - try { - farmingXp.offer(FloatLongPair.of(NUMBER_FORMAT.parse(matcher.group("xp")).floatValue(), System.currentTimeMillis())); - farmingXpPercentProgress = NUMBER_FORMAT.parse(matcher.group("percent")).floatValue(); - } catch (ParseException e) { - LOGGER.error("[Skyblocker Farming HUD] Failed to parse farming xp", e); - } - } - } - }); - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("hud").then(literal("farming") - .executes(Scheduler.queueOpenScreenCommand(() -> new WidgetsConfigurationScreen(Location.GARDEN, "hud_garden", null))))))); - } - - private static boolean tryGetCounter(ItemStack stack, CounterType counterType) { - NbtCompound customData = ItemUtils.getCustomData(stack); - if (customData == null || !customData.contains(counterType.nbtKey, NbtElement.NUMBER_TYPE)) return false; - int count = customData.getInt(counterType.nbtKey); - if (FarmingHud.counterType != counterType) { - counter.clear(); - FarmingHud.counterType = counterType; - } - if (counter.isEmpty() || counter.peekLast().leftInt() != count) { - counter.offer(IntLongPair.of(count, System.currentTimeMillis())); - } - return true; - } - - private static boolean shouldRender() { - return SkyblockerConfigManager.get().farming.garden.farmingHud.enableHud && client.player != null && Utils.getLocation() == Location.GARDEN; - } - - public static String counterText() { - return counterType.text; - } - - public static int counter() { - return counter.isEmpty() ? 0 : counter.peekLast().leftInt(); - } - - public static float cropsPerMinute() { - if (counter.isEmpty()) { - return 0; - } - IntLongPair first = counter.peek(); - IntLongPair last = counter.peekLast(); - return (float) (last.leftInt() - first.leftInt()) / (last.rightLong() - first.rightLong()) * 60_000f; - } - - public static int blockBreaks() { - return blockBreaks.size(); - } - - public static float farmingXpPercentProgress() { - return farmingXpPercentProgress; - } - - public static double farmingXpPerHour() { - return farmingXp.stream().mapToDouble(FloatLongPair::leftFloat).sum() * blockBreaks() * 1800; // Hypixel only sends xp updates around every half a second - } - - public enum CounterType { - NONE("", "No Counter"), - COUNTER("mined_crops", "Counter: "), - CULTIVATING("farmed_cultivating", "Cultivating Counter: "); - - private final String nbtKey; - private final String text; - - CounterType(String nbtKey, String text) { - this.nbtKey = nbtKey; - this.text = text; - } - public boolean matchesText(String textToMatch) { - return this.text.equals(textToMatch); - } - } + private static final Logger LOGGER = LoggerFactory.getLogger(FarmingHud.class); + public static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US); + private static final Pattern FARMING_XP = Pattern.compile("§3\\+(?\\d+(?:\\.\\d+)?) Farming \\((?[\\d,]+(?:\\.\\d+)?%|[\\d,]+/[\\d,]+)\\)"); + private static final MinecraftClient client = MinecraftClient.getInstance(); + private static CounterType counterType = CounterType.NONE; + private static final Deque counter = new ArrayDeque<>(); + private static final LongPriorityQueue blockBreaks = new LongArrayFIFOQueue(); + private static final Queue farmingXp = new ArrayDeque<>(); + private static float farmingXpPercentProgress; + private static double smoothedBlocksPerSecond = 0.0; + private static double smoothedFarmingXpPerHour = 0.0; + + @Init + public static void init() { + HudRenderEvents.AFTER_MAIN_HUD.register((context, tickCounter) -> { + if (shouldRender()) { + if (!counter.isEmpty() && counter.peek().rightLong() + 5000 < System.currentTimeMillis()) { + counter.poll(); + } + if (!blockBreaks.isEmpty() && blockBreaks.firstLong() + 1000 < System.currentTimeMillis()) { + blockBreaks.dequeueLong(); + } + if (!farmingXp.isEmpty() && farmingXp.peek().rightLong() + 1000 < System.currentTimeMillis()) { + farmingXp.poll(); + } + + assert client.player != null; + ItemStack stack = client.player.getMainHandStack(); + if (stack == null || !tryGetCounter(stack, CounterType.CULTIVATING) && !tryGetCounter(stack, CounterType.COUNTER)) { + counterType = CounterType.NONE; + } + } + }); + ClientPlayerBlockBreakEvents.AFTER.register((world, player, pos, state) -> { + if (shouldRender()) { + blockBreaks.enqueue(System.currentTimeMillis()); + } + }); + ClientReceiveMessageEvents.GAME.register((message, overlay) -> { + if (shouldRender() && overlay) { + Matcher matcher = FARMING_XP.matcher(message.getString()); + if (matcher.matches()) { + try { + farmingXp.offer(FloatLongPair.of(NUMBER_FORMAT.parse(matcher.group("xp")).floatValue(), System.currentTimeMillis())); + farmingXpPercentProgress = NUMBER_FORMAT.parse(matcher.group("percent")).floatValue(); + } catch (ParseException e) { + LOGGER.error("[Skyblocker Farming HUD] Failed to parse farming xp", e); + } + } + } + }); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("hud").then(literal("farming") + .executes(Scheduler.queueOpenScreenCommand(() -> new WidgetsConfigurationScreen(Location.GARDEN, "hud_garden", null))))))); + } + + private static boolean tryGetCounter(ItemStack stack, CounterType counterType) { + NbtCompound customData = ItemUtils.getCustomData(stack); + if (customData == null || !customData.contains(counterType.nbtKey, NbtElement.NUMBER_TYPE)) return false; + int count = customData.getInt(counterType.nbtKey); + if (FarmingHud.counterType != counterType) { + counter.clear(); + FarmingHud.counterType = counterType; + } + if (counter.isEmpty() || counter.peekLast().leftInt() != count) { + counter.offer(IntLongPair.of(count, System.currentTimeMillis())); + } + return true; + } + + private static boolean shouldRender() { + return SkyblockerConfigManager.get().farming.garden.farmingHud.enableHud && client.player != null && Utils.getLocation() == Location.GARDEN; + } + + public static String counterText() { + return counterType.text; + } + + public static int counter() { + return counter.isEmpty() ? 0 : counter.peekLast().leftInt(); + } + + public static float cropsPerMinute() { + + if (counter.isEmpty()) { + + return 0; + + } + + IntLongPair first = counter.peek(); + + IntLongPair last = counter.peekLast(); + + return (float) (last.leftInt() - first.leftInt()) / (last.rightLong() - first.rightLong()) * 60_000f; + + } + + public static double blockBreaks() { + long currentTime = System.currentTimeMillis(); + while (!blockBreaks.isEmpty() && blockBreaks.firstLong() + 1000 < currentTime) { + blockBreaks.dequeueLong(); + } + + double rawBlocksPerSecond = blockBreaks.size(); + double t = 0.01; + smoothedBlocksPerSecond += (rawBlocksPerSecond - smoothedBlocksPerSecond) * t; + + return Math.round(smoothedBlocksPerSecond * 10) / 10.0; + } + + public static float farmingXpPercentProgress() { + return farmingXpPercentProgress; + } + + public static double farmingXpPerHour() { + double xpPerCrop = farmingXp.isEmpty() ? 0 : farmingXp.peek().leftFloat(); + double cropsPerSecond = blockBreaks(); + double xpPerSecond = xpPerCrop * cropsPerSecond; + double t = 0.1; + + smoothedFarmingXpPerHour += (xpPerSecond * 3600 - smoothedFarmingXpPerHour) * t; + + return Math.round(smoothedFarmingXpPerHour * 10) / 10.0; + } + + + public enum CounterType { + NONE("", "No Counter"), + COUNTER("mined_crops", "Counter: "), + CULTIVATING("farmed_cultivating", "Cultivating Counter: "); + + private final String nbtKey; + private final String text; + + CounterType(String nbtKey, String text) { + this.nbtKey = nbtKey; + this.text = text; + } + + public boolean matchesText(String textToMatch) { + return this.text.equals(textToMatch); + } + } } 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 868bf4cfe2..2f452bd634 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java @@ -94,10 +94,10 @@ public void updateContent() { float cropsPerMinute = FarmingHud.cropsPerMinute(); addSimpleIcoText(cropStack, "Crops/min: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format((int) cropsPerMinute / 10 * 10)); addSimpleIcoText(Ico.GOLD, "Coins/h: ", Formatting.GOLD, getPriceText(cropItemId, cropsPerMinute)); - addSimpleIcoText(cropStack, "Blocks/s: ", Formatting.YELLOW, Integer.toString(FarmingHud.blockBreaks())); + addSimpleIcoText(cropStack, "Blocks/s: ", Formatting.YELLOW, Double.toString(FarmingHud.blockBreaks())); //noinspection DataFlowIssue addComponent(new ProgressComponent(Ico.LANTERN, Text.literal("Farming Level: "), FarmingHud.farmingXpPercentProgress(), Formatting.GOLD.getColorValue())); - addSimpleIcoText(Ico.LIME_DYE, "Farming XP/h: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format((int) FarmingHud.farmingXpPerHour())); + addSimpleIcoText(Ico.LIME_DYE, "Farming XP/h: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format(FarmingHud.farmingXpPerHour())); Entity cameraEntity = client.getCameraEntity(); String yaw = cameraEntity == null ? "No Camera Entity" : String.format("%.2f", MathHelper.wrapDegrees(cameraEntity.getYaw())); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java index db946317fa..2bb0768eba 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java @@ -29,12 +29,10 @@ import org.slf4j.LoggerFactory; import java.text.NumberFormat; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; -//TODO: check inventory items, sum all repeated items into one +//TODO: check inventory items, sum all repeated items into one (should work) +//TODO: Get visitors "rarity" and apply it to their name in the helper list public class VisitorHelper { private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Visitor Helper"); private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US); @@ -132,37 +130,96 @@ else if (saveRequiredItems) } } - private static void updateItemMap(String visitorName, @Nullable String visitorTexture, Text lore) { - String[] splitItemText = lore.getString().split(" x"); - String itemName = splitItemText[0].trim(); - if (itemName.isEmpty()) return; - try { - int amount = splitItemText.length == 2 ? NUMBER_FORMAT.parse(splitItemText[1].trim()).intValue() : 1; - Pair key = Pair.of(visitorName, visitorTexture); - Object2IntMap visitorMap = itemMap.computeIfAbsent(key, _key -> new Object2IntOpenHashMap<>()); - visitorMap.put(itemName, amount); - } catch (Exception e) { - LOGGER.error("[Skyblocker Visitor Helper] Failed to parse item: {}", lore.getString(), e); - } - } + private static void updateItemMap(String visitorName, @Nullable String visitorTexture, Text lore) { + String[] splitItemText = lore.getString().split(" x"); + String itemName = splitItemText[0].trim(); + if (itemName.isEmpty()) return; + try { + int amount = splitItemText.length == 2 ? NUMBER_FORMAT.parse(splitItemText[1].trim()).intValue() : 1; - private static void drawScreenItems(DrawContext context, TextRenderer textRenderer, int mouseX, int mouseY) { - context.getMatrices().push(); - context.getMatrices().translate(0, 0, 200); - int index = 0; - for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { - Pair visitorName = visitorEntry.getKey(); - drawTextWithOptionalUnderline(context, textRenderer, Text.literal(visitorName.left()), TEXT_START_X, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); - index++; + Pair visitorKey = Pair.of(visitorName, visitorTexture); + Object2IntMap visitorMap = itemMap.computeIfAbsent(visitorKey, _key -> new Object2IntOpenHashMap<>()); + + visitorMap.put(itemName, amount); // Replace instead of accumulating repeatedly + } catch (Exception e) { + LOGGER.error("[Skyblocker Visitor Helper] Failed to parse item: {}", lore.getString(), e); + } + } + + private static void drawScreenItems(DrawContext context, TextRenderer textRenderer, int mouseX, int mouseY) { + context.getMatrices().push(); + context.getMatrices().translate(0, 0, 200); + + int index = 0; + + // Process visitors grouped by their requested items + Map> itemToVisitorsMap = new LinkedHashMap<>(); + Map itemToTotalAmountMap = new LinkedHashMap<>(); + + for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { + Pair visitorName = visitorEntry.getKey(); + Object2IntMap visitorItems = visitorEntry.getValue(); + + for (Object2IntMap.Entry itemEntry : visitorItems.object2IntEntrySet()) { + String itemName = itemEntry.getKey(); + int amount = itemEntry.getIntValue(); + + itemToVisitorsMap.computeIfAbsent(itemName, k -> new ArrayList<>()).add(visitorName.left()); + itemToTotalAmountMap.put(itemName, itemToTotalAmountMap.getOrDefault(itemName, 0) + amount); + } + } + + // Draw grouped visitors and items at the top + Set processedVisitors = new HashSet<>(); + for (Map.Entry> groupedEntry : itemToVisitorsMap.entrySet()) { + String itemName = groupedEntry.getKey(); + List visitors = groupedEntry.getValue(); + int totalAmount = itemToTotalAmountMap.get(itemName); + + // Draw visitor names grouped together + for (String visitor : visitors) { + if (!processedVisitors.contains(visitor)) { + drawTextWithOptionalUnderline(context, textRenderer, Text.literal(visitor), TEXT_START_X, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); + index++; + processedVisitors.add(visitor); + } + } + + // Draw the combined item line + Text combinedText = Text.literal(" ") + .append(Text.literal(itemName + " x" + totalAmount)) + .append(Text.literal(" [Copy Amount]").formatted(Formatting.YELLOW)); + + drawTextWithOptionalUnderline(context, textRenderer, combinedText, TEXT_START_X + ENTRY_INDENT, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); + index++; + } + + // Draw remaining visitors with unshared items below + for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { + Pair visitorName = visitorEntry.getKey(); + if (processedVisitors.contains(visitorName.left())) continue; + + drawTextWithOptionalUnderline(context, textRenderer, Text.literal(visitorName.left()), TEXT_START_X, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); + index++; + + for (Object2IntMap.Entry itemEntry : visitorEntry.getValue().object2IntEntrySet()) { + String itemName = itemEntry.getKey(); + int amount = itemEntry.getIntValue(); + + Text itemText = Text.literal(" ") + .append(Text.literal(itemName + " x" + amount)) + .append(Text.literal(" [Copy Amount]").formatted(Formatting.YELLOW)); + + drawTextWithOptionalUnderline(context, textRenderer, itemText, TEXT_START_X + ENTRY_INDENT, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); + index++; + } + } + + context.getMatrices().pop(); + } - for (Object2IntMap.Entry itemEntry : visitorEntry.getValue().object2IntEntrySet()) { - index = drawItemEntryWithHover(context, textRenderer, itemEntry, index, mouseX, mouseY); - } - } - context.getMatrices().pop(); - } - private static int drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, Object2IntMap.Entry itemEntry, int index, int mouseX, int mouseY) { + private static int drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, Object2IntMap.Entry itemEntry, int index, int mouseX, int mouseY) { String itemName = itemEntry.getKey(); int amount = itemEntry.getIntValue(); ItemStack stack = getCachedItem(itemName); From 6ebe2be8eb7bfb81abaa73fa8bce10e045eb7ba9 Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Tue, 31 Dec 2024 01:36:33 -0600 Subject: [PATCH 02/10] Added Show Stacks in Visitor Helper --- .../config/categories/FarmingCategory.java | 19 +- .../config/configs/FarmingConfig.java | 6 + .../skyblocker/mixins/HandledScreenMixin.java | 2 +- .../skyblock/garden/VisitorHelper.java | 182 +++++++++++++----- .../assets/skyblocker/lang/en_us.json | 6 + 5 files changed, 163 insertions(+), 52 deletions(-) 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 929256c61c..96a68ced1b 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java @@ -40,11 +40,28 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .build()) .option(Option.createBuilder() .name(Text.translatable("skyblocker.config.farming.garden.visitorHelper")) - .binding(defaults.farming.garden.visitorHelper, + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.visitorHelper.@Tooltip"))) + .binding(defaults.farming.garden.visitorHelper, () -> config.farming.garden.visitorHelper, newValue -> config.farming.garden.visitorHelper = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.farming.garden.showStacksInVisitorHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.showStacksInVisitorHelper.@Tooltip"))) + .binding(defaults.farming.garden.showStacksInVisitorHelper, + () -> config.farming.garden.showStacksInVisitorHelper, + newValue -> config.farming.garden.showStacksInVisitorHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.farming.garden.visitorHelperGardenOnly")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.visitorHelperGardenOnly.@Tooltip"))) + .binding(defaults.farming.garden.visitorHelperGardenOnly, + () -> config.farming.garden.visitorHelperGardenOnly, + newValue -> config.farming.garden.visitorHelperGardenOnly = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) .option(Option.createBuilder() .name(Text.translatable("skyblocker.config.farming.garden.lockMouseTool")) .binding(defaults.farming.garden.lockMouseTool, diff --git a/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java index 568165a707..029c457937 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java @@ -17,6 +17,12 @@ public static class Garden { @SerialEntry public boolean visitorHelper = true; + @SerialEntry + public boolean visitorHelperGardenOnly = true; + + @SerialEntry + public boolean showStacksInVisitorHelper = false; + @SerialEntry public boolean lockMouseTool = false; diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index d092de6e2e..ccbd74e7ad 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -138,7 +138,7 @@ protected HandledScreenMixin(Text title) { @Inject(at = @At("HEAD"), method = "mouseClicked") public void skyblocker$mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { - if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (Utils.getLocationRaw().equals("garden") && !getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) { + if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (!getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) { VisitorHelper.onMouseClicked(mouseX, mouseY, button, this.textRenderer); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java index 2bb0768eba..e7816068f3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java @@ -48,55 +48,134 @@ public class VisitorHelper { private static boolean shouldProcessVisitorItems = true; - @Init - public static void init() { - ScreenEvents.BEFORE_INIT.register((client, screen, scaledWidth, scaledHeight) -> { - String title = screen.getTitle().getString(); - if (SkyblockerConfigManager.get().farming.garden.visitorHelper && screen instanceof HandledScreen handledScreen && (Utils.getLocationRaw().equals("garden") && !title.contains("Logbook") || title.startsWith("Bazaar"))) { - ScreenEvents.afterRender(screen).register((screen_, context, mouseX, mouseY, delta) -> renderScreen(title, context, client.textRenderer, handledScreen.getScreenHandler(), mouseX, mouseY)); - ScreenEvents.remove(screen).register(screen_ -> shouldProcessVisitorItems = true); - } - }); - } + @Init + public static void init() { + ScreenEvents.BEFORE_INIT.register((client, screen, scaledWidth, scaledHeight) -> { + + String title = screen.getTitle().getString(); + boolean isGardenLocation = Utils.getLocationRaw().equals("garden"); + + if (SkyblockerConfigManager.get().farming.garden.visitorHelper && + (!SkyblockerConfigManager.get().farming.garden.visitorHelperGardenOnly || isGardenLocation) && + screen instanceof HandledScreen handledScreen && + (!title.contains("Logbook") || title.startsWith("Bazaar"))) { + + ScreenEvents.afterRender(screen).register((screen_, context, mouseX, mouseY, delta) -> + renderScreen(title, context, client.textRenderer, handledScreen.getScreenHandler(), mouseX, mouseY)); + + ScreenEvents.remove(screen).register(screen_ -> shouldProcessVisitorItems = true); + } + }); + } public static void renderScreen(String title, DrawContext context, TextRenderer textRenderer, ScreenHandler handler, int mouseX, int mouseY) { if (handler.getCursorStack() == ItemStack.EMPTY && shouldProcessVisitorItems) processVisitorItem(title, handler); drawScreenItems(context, textRenderer, mouseX, mouseY); } - public static void onMouseClicked(double mouseX, double mouseY, int mouseButton, TextRenderer textRenderer) { - int yPosition = TEXT_START_Y; - for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { - yPosition += LINE_SPACING + textRenderer.fontHeight; - - for (Object2IntMap.Entry itemEntry : visitorEntry.getValue().object2IntEntrySet()) { - String itemText = itemEntry.getKey(); - int textWidth = textRenderer.getWidth(itemText + " x" + itemEntry.getIntValue()); - - // Check if the mouse is over the item text - // The text starts at `TEXT_START_X + ENTRY_INDENT + ITEM_INDENT` - if (isMouseOverText(mouseX, mouseY, TEXT_START_X + ENTRY_INDENT + ITEM_INDENT, yPosition, textWidth, textRenderer.fontHeight)) { - // Send command to buy the item from the bazaar - MessageScheduler.INSTANCE.sendMessageAfterCooldown("/bz " + itemText, true); - return; - } - - // Check if the mouse is over the copy amount text - // The copy amount text starts at `TEXT_START_X + ENTRY_INDENT + ITEM_INDENT + textWidth` - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player != null && isMouseOverText(mouseX, mouseY, TEXT_START_X + ENTRY_INDENT + ITEM_INDENT + textWidth, yPosition, textRenderer.getWidth(" [Copy Amount]"), textRenderer.fontHeight)) { - // Copy the amount to the clipboard - client.keyboard.setClipboard(String.valueOf(itemEntry.getIntValue())); - client.player.sendMessage(Constants.PREFIX.get().append("Copied amount successfully"), false); - return; - } - - yPosition += LINE_SPACING + textRenderer.fontHeight; - } - } - } + // The location of copy amount, and item text seem to be overlapping so clicking on part of the itemText run copy amount. + public static void onMouseClicked(double mouseX, double mouseY, int mouseButton, TextRenderer textRenderer) { + + int yPosition = TEXT_START_Y; + boolean showStacks = SkyblockerConfigManager.get().farming.garden.showStacksInVisitorHelper; + + // Group visitors and items like in drawScreenItems + Map> itemToVisitorsMap = new LinkedHashMap<>(); + Map itemToTotalAmountMap = new LinkedHashMap<>(); + + for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { + Pair visitorName = visitorEntry.getKey(); + Object2IntMap visitorItems = visitorEntry.getValue(); - public static void onSlotClick(Slot slot, int slotId, String title, ItemStack visitorHeadStack) { + for (Object2IntMap.Entry itemEntry : visitorItems.object2IntEntrySet()) { + String itemName = itemEntry.getKey(); + int amount = itemEntry.getIntValue(); + + itemToVisitorsMap.computeIfAbsent(itemName, k -> new ArrayList<>()).add(visitorName.left()); + itemToTotalAmountMap.put(itemName, itemToTotalAmountMap.getOrDefault(itemName, 0) + amount); + } + } + + Set processedVisitors = new HashSet<>(); + for (Map.Entry> groupedEntry : itemToVisitorsMap.entrySet()) { + String itemName = groupedEntry.getKey(); + List visitors = groupedEntry.getValue(); + int totalAmount = itemToTotalAmountMap.get(itemName); + + // Check grouped visitor names + for (String visitor : visitors) { + if (!processedVisitors.contains(visitor)) { + yPosition += LINE_SPACING + textRenderer.fontHeight; + processedVisitors.add(visitor); + } + } + + // Adjust itemText and copy amount positions for stack-based display + String amountText = showStacks && totalAmount >= 64 + ? (totalAmount / 64) + " stacks" + (totalAmount % 64 > 0 ? " + " + (totalAmount % 64) : "") + : "" + totalAmount; + + String combinedText = itemName + " x" + amountText; + int itemTextWidth = textRenderer.getWidth(combinedText); + int copyAmountX = TEXT_START_X + ENTRY_INDENT + itemTextWidth; + int copyAmountWidth = textRenderer.getWidth(" [Copy Amount]"); + + if (isMouseOverText(mouseX, mouseY, TEXT_START_X + ENTRY_INDENT, yPosition, itemTextWidth, textRenderer.fontHeight)) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/bz " + itemName, true); + return; + } + if (isMouseOverText(mouseX, mouseY, copyAmountX, yPosition, copyAmountWidth, textRenderer.fontHeight)) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player != null) { + client.keyboard.setClipboard(String.valueOf(totalAmount)); + client.player.sendMessage(Constants.PREFIX.get().append("Copied amount successfully"), false); + } + return; + } + + yPosition += LINE_SPACING + textRenderer.fontHeight; + } + + // Check remaining visitors with unshared items + for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { + Pair visitorName = visitorEntry.getKey(); + if (processedVisitors.contains(visitorName.left())) continue; + + yPosition += LINE_SPACING + textRenderer.fontHeight; + + for (Object2IntMap.Entry itemEntry : visitorEntry.getValue().object2IntEntrySet()) { + String itemName = itemEntry.getKey(); + int amount = itemEntry.getIntValue(); + + String amountText = showStacks && amount >= 64 + ? (amount / 64) + " stacks" + (amount % 64 > 0 ? " + " + (amount % 64) : "") + : "" + amount; + + String combinedText = itemName + " x" + amountText; + int itemTextX = TEXT_START_X + ENTRY_INDENT; + int itemTextWidth = textRenderer.getWidth(combinedText); + int copyAmountX = itemTextX + itemTextWidth; + int copyAmountWidth = textRenderer.getWidth(" [Copy Amount]"); + + if (isMouseOverText(mouseX, mouseY, itemTextX, yPosition, itemTextWidth, textRenderer.fontHeight)) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/bz " + itemName, true); + return; + } + + if (isMouseOverText(mouseX, mouseY, copyAmountX, yPosition, copyAmountWidth, textRenderer.fontHeight)) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player != null) { + client.keyboard.setClipboard(amountText); + client.player.sendMessage(Constants.PREFIX.get().append("Copied amount successfully"), false); + } + return; + } + yPosition += LINE_SPACING + textRenderer.fontHeight; + } + } + } + + public static void onSlotClick(Slot slot, int slotId, String title, ItemStack visitorHeadStack) { if ((slotId == 29 || slotId == 13 || slotId == 33) && slot.hasStack() && ItemUtils.getLoreLineIf(slot.getStack(), s -> s.equals("Click to give!") || s.equals("Click to refuse!")) != null) { itemMap.remove(new ObjectObjectImmutablePair<>(title, getTextureOrNull(visitorHeadStack))); shouldProcessVisitorItems = false; @@ -151,8 +230,8 @@ private static void drawScreenItems(DrawContext context, TextRenderer textRender context.getMatrices().translate(0, 0, 200); int index = 0; + boolean showStacks = SkyblockerConfigManager.get().farming.garden.showStacksInVisitorHelper; - // Process visitors grouped by their requested items Map> itemToVisitorsMap = new LinkedHashMap<>(); Map itemToTotalAmountMap = new LinkedHashMap<>(); @@ -169,14 +248,12 @@ private static void drawScreenItems(DrawContext context, TextRenderer textRender } } - // Draw grouped visitors and items at the top Set processedVisitors = new HashSet<>(); for (Map.Entry> groupedEntry : itemToVisitorsMap.entrySet()) { String itemName = groupedEntry.getKey(); List visitors = groupedEntry.getValue(); int totalAmount = itemToTotalAmountMap.get(itemName); - // Draw visitor names grouped together for (String visitor : visitors) { if (!processedVisitors.contains(visitor)) { drawTextWithOptionalUnderline(context, textRenderer, Text.literal(visitor), TEXT_START_X, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); @@ -185,16 +262,18 @@ private static void drawScreenItems(DrawContext context, TextRenderer textRender } } - // Draw the combined item line + String amountText = showStacks && totalAmount >= 64 + ? (totalAmount / 64) + " stacks" + (totalAmount % 64 > 0 ? " + " + (totalAmount % 64) : "") + : "" + totalAmount; + Text combinedText = Text.literal(" ") - .append(Text.literal(itemName + " x" + totalAmount)) + .append(Text.literal(itemName + " x" + amountText)) .append(Text.literal(" [Copy Amount]").formatted(Formatting.YELLOW)); drawTextWithOptionalUnderline(context, textRenderer, combinedText, TEXT_START_X + ENTRY_INDENT, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); index++; } - // Draw remaining visitors with unshared items below for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { Pair visitorName = visitorEntry.getKey(); if (processedVisitors.contains(visitorName.left())) continue; @@ -206,8 +285,12 @@ private static void drawScreenItems(DrawContext context, TextRenderer textRender String itemName = itemEntry.getKey(); int amount = itemEntry.getIntValue(); + String amountText = showStacks && amount >= 64 + ? (amount / 64) + " stacks" + (amount % 64 > 0 ? " + " + (amount % 64) : "") + : "" + amount; + Text itemText = Text.literal(" ") - .append(Text.literal(itemName + " x" + amount)) + .append(Text.literal(itemName + " x" + amountText)) .append(Text.literal(" [Copy Amount]").formatted(Formatting.YELLOW)); drawTextWithOptionalUnderline(context, textRenderer, itemText, TEXT_START_X + ENTRY_INDENT, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); @@ -218,7 +301,6 @@ private static void drawScreenItems(DrawContext context, TextRenderer textRender context.getMatrices().pop(); } - private static int drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, Object2IntMap.Entry itemEntry, int index, int mouseX, int mouseY) { String itemName = itemEntry.getKey(); int amount = itemEntry.getIntValue(); diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 0fa2d2a5dd..03bbce37a0 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -241,6 +241,12 @@ "skyblocker.config.farming.garden.lockMouseGround": "Only lock camera when on the ground", "skyblocker.config.farming.garden.lockMouseTool": "Lock camera when holding a farming tool", "skyblocker.config.farming.garden.visitorHelper": "Visitor helper", + "skyblocker.config.farming.garden.visitorHelper.@Tooltip": "Makes it easier to manage visitors by grouping similar requests, copying item amounts, and quickly opening the Bazaar to buy items.", + "skyblocker.config.farming.garden.visitorHelperGardenOnly": "Visitor helper (Garden Only)", + "skyblocker.config.farming.garden.visitorHelperGardenOnly.@Tooltip": "Locks the Visitor Helper so it only works on the Garden.\n§cRequires Visitor Helper to be enabled.", + "skyblocker.config.farming.garden.showStacksInVisitorHelper": "Show Stacks in Visitor Helper", + "skyblocker.config.farming.garden.showStacksInVisitorHelper.@Tooltip": "Show's the required items as (5 stacks + 4 [324]) rather than just a total number.", + "skyblocker.config.foraging": "Foraging", "skyblocker.config.foraging.hunting": "Hunting", From 5904edc1b02b3c6f01462e8eb60bb5c34f9b5a30 Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Tue, 31 Dec 2024 02:49:46 -0600 Subject: [PATCH 03/10] Re-added the drawItemEntry into drawScreen --- .../skyblock/garden/VisitorHelper.java | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java index e7816068f3..e301d12d8d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java @@ -31,7 +31,6 @@ import java.text.NumberFormat; import java.util.*; -//TODO: check inventory items, sum all repeated items into one (should work) //TODO: Get visitors "rarity" and apply it to their name in the helper list public class VisitorHelper { private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Visitor Helper"); @@ -73,7 +72,7 @@ public static void renderScreen(String title, DrawContext context, TextRenderer drawScreenItems(context, textRenderer, mouseX, mouseY); } - // The location of copy amount, and item text seem to be overlapping so clicking on part of the itemText run copy amount. + // The location of copy amount, and item text seem to be overlapping so clicking on part of the itemText will run copy amount. public static void onMouseClicked(double mouseX, double mouseY, int mouseButton, TextRenderer textRenderer) { int yPosition = TEXT_START_Y; @@ -266,11 +265,9 @@ private static void drawScreenItems(DrawContext context, TextRenderer textRender ? (totalAmount / 64) + " stacks" + (totalAmount % 64 > 0 ? " + " + (totalAmount % 64) : "") : "" + totalAmount; - Text combinedText = Text.literal(" ") - .append(Text.literal(itemName + " x" + amountText)) - .append(Text.literal(" [Copy Amount]").formatted(Formatting.YELLOW)); + ItemStack stack = getCachedItem(itemName); + drawItemEntryWithHover(context, textRenderer, stack, itemName, amountText, index, mouseX, mouseY); - drawTextWithOptionalUnderline(context, textRenderer, combinedText, TEXT_START_X + ENTRY_INDENT, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); index++; } @@ -289,11 +286,8 @@ private static void drawScreenItems(DrawContext context, TextRenderer textRender ? (amount / 64) + " stacks" + (amount % 64 > 0 ? " + " + (amount % 64) : "") : "" + amount; - Text itemText = Text.literal(" ") - .append(Text.literal(itemName + " x" + amountText)) - .append(Text.literal(" [Copy Amount]").formatted(Formatting.YELLOW)); - - drawTextWithOptionalUnderline(context, textRenderer, itemText, TEXT_START_X + ENTRY_INDENT, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); + ItemStack stack = getCachedItem(itemName); + drawItemEntryWithHover(context, textRenderer, stack, itemName + " x" + amountText, String.valueOf(amount), index, mouseX, mouseY); index++; } } @@ -305,11 +299,12 @@ private static int drawItemEntryWithHover(DrawContext context, TextRenderer text String itemName = itemEntry.getKey(); int amount = itemEntry.getIntValue(); ItemStack stack = getCachedItem(itemName); - drawItemEntryWithHover(context, textRenderer, stack, itemName, amount, index, mouseX, mouseY); + drawItemEntryWithHover(context, textRenderer, stack, itemName, String.valueOf(amount), index, mouseX, mouseY); return index + 1; } - private static ItemStack getCachedItem(String displayName) { + + private static ItemStack getCachedItem(String displayName) { String strippedName = Formatting.strip(displayName); ItemStack cachedStack = itemCache.get(strippedName); if (cachedStack != null) return cachedStack; @@ -327,27 +322,33 @@ private static ItemStack getCachedItem(String displayName) { return stack; } - /** - * Draws the item entry, amount, and copy amount text with optional underline and the item icon - */ - private static void drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, @Nullable ItemStack stack, String itemName, int amount, int index, int mouseX, int mouseY) { - Text text = stack != null ? stack.getName().copy().append(" x" + amount) : Text.literal(itemName + " x" + amount); - Text copyAmount = Text.literal(" [Copy Amount]"); - - // Calculate the y position of the text with index as the line number - int y = TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight); - // Draw the item and amount text - drawTextWithOptionalUnderline(context, textRenderer, text, TEXT_START_X + ENTRY_INDENT + ITEM_INDENT, y, mouseX, mouseY); - // Draw the copy amount text separately after the item and amount text - drawTextWithOptionalUnderline(context, textRenderer, copyAmount, TEXT_START_X + ENTRY_INDENT + ITEM_INDENT + textRenderer.getWidth(text), y, mouseX, mouseY); - - // drawItem adds 150 to the z, which puts our z at 350, above the item in the slot (250) and their text (300) and below the cursor stack (382) and their text (432) - if (stack != null) { - context.drawItem(stack, TEXT_START_X + ENTRY_INDENT, y - textRenderer.fontHeight + 5); - } - } + /** + * Draws the item entry, amount, and copy amount text with optional underline and the item icon. + */ + private static void drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, @Nullable ItemStack stack, String itemName, String amountText, int index, int mouseX, int mouseY) { + + Text itemText = Text.literal(itemName).formatted(Formatting.GREEN) + .append(Text.literal(" x" + amountText).formatted(Formatting.WHITE)); + Text copyAmountText = Text.literal(" [Copy Amount]").formatted(Formatting.YELLOW); + + int y = TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight); + int itemTextX = TEXT_START_X + ENTRY_INDENT + ITEM_INDENT; + int itemIconX = TEXT_START_X + ENTRY_INDENT; + + drawTextWithOptionalUnderline(context, textRenderer, itemText, itemTextX, y, mouseX, mouseY); + + int copyAmountX = itemTextX + textRenderer.getWidth(itemText.getString()); + + drawTextWithOptionalUnderline(context, textRenderer, copyAmountText, copyAmountX, y, mouseX, mouseY); + + if (stack != null) { + context.drawItem(stack, itemIconX, y - textRenderer.fontHeight + 5); + } + } + + - private static void drawTextWithOptionalUnderline(DrawContext context, TextRenderer textRenderer, Text text, int x, int y, int mouseX, int mouseY) { + private static void drawTextWithOptionalUnderline(DrawContext context, TextRenderer textRenderer, Text text, int x, int y, int mouseX, int mouseY) { context.getMatrices().push(); context.getMatrices().translate(0, 0, 150); // This also puts our z at 350 context.drawText(textRenderer, text, x, y, -1, true); From aa270bf3477cb0c4c6aa505b00d84ba216822528 Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Sun, 5 Jan 2025 21:27:29 -0600 Subject: [PATCH 04/10] VacuumSolver, VisitorHelper, PestHighlighter --- .../categories/CrimsonIsleCategory.java | 4 +- .../config/categories/FarmingCategory.java | 57 ++- .../config/configs/FarmingConfig.java | 21 +- .../mixins/ClientPlayNetworkHandlerMixin.java | 2 + .../skyblocker/mixins/HandledScreenMixin.java | 19 +- .../skyblock/entity/MobBoundingBoxes.java | 4 +- .../skyblocker/skyblock/entity/MobGlow.java | 43 ++- .../skyblock/garden/FarmingHud.java | 35 +- .../skyblock/garden/FarmingHudWidget.java | 2 +- .../skyblock/garden/VacuumSolver.java | 122 ++++++ .../skyblock/garden/VisitorHelper.java | 364 ------------------ .../garden/visitorhelper/Visitor.java | 50 +++ .../garden/visitorhelper/VisitorHelper.java | 276 +++++++++++++ .../widget/component/ProgressComponent.java | 1 + .../de/hysky/skyblocker/utils/ItemUtils.java | 3 +- .../java/de/hysky/skyblocker/utils/Utils.java | 4 + .../skyblocker/utils/mayor/MayorUtils.java | 15 +- .../skyblocker/utils/render/RenderHelper.java | 2 +- .../assets/skyblocker/lang/en_us.json | 19 +- 19 files changed, 610 insertions(+), 433 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/garden/VacuumSolver.java delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/Visitor.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/VisitorHelper.java diff --git a/src/main/java/de/hysky/skyblocker/config/categories/CrimsonIsleCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/CrimsonIsleCategory.java index 242bfd24d4..5a210017c8 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/CrimsonIsleCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/CrimsonIsleCategory.java @@ -36,8 +36,8 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .build()) .option(Option.createBuilder() .name(Text.translatable("skyblocker.config.crimsonIsle.kuudra.suppliesAndFuelWaypointType")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.dungeons.secretWaypoints.waypointType.@Tooltip"), - Text.translatable("skyblocker.config.dungeons.secretWaypoints.waypointType.generalNote"))) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType.@Tooltip"), + Text.translatable("skyblocker.config.uiAndVisuals.waypoints.waypointType.generalNote"))) .binding(defaults.crimsonIsle.kuudra.suppliesAndFuelWaypointType, () -> config.crimsonIsle.kuudra.suppliesAndFuelWaypointType, newValue -> config.crimsonIsle.kuudra.suppliesAndFuelWaypointType = newValue) 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 96a68ced1b..76b5883602 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/FarmingCategory.java @@ -37,29 +37,21 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig () -> config.farming.garden.dicerTitlePrevent, newValue -> config.farming.garden.dicerTitlePrevent = newValue) .controller(ConfigUtils::createBooleanController) - .build()) - .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.farming.garden.visitorHelper")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.visitorHelper.@Tooltip"))) - .binding(defaults.farming.garden.visitorHelper, - () -> config.farming.garden.visitorHelper, - newValue -> config.farming.garden.visitorHelper = newValue) - .controller(ConfigUtils::createBooleanController) .build()) .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.farming.garden.showStacksInVisitorHelper")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.showStacksInVisitorHelper.@Tooltip"))) - .binding(defaults.farming.garden.showStacksInVisitorHelper, - () -> config.farming.garden.showStacksInVisitorHelper, - newValue -> config.farming.garden.showStacksInVisitorHelper = newValue) + .name(Text.translatable("skyblocker.config.farming.garden.pestHighlighter")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.pestHighlighter.@Tooltip"))) + .binding(defaults.farming.garden.pestHighlighter, + () -> config.farming.garden.pestHighlighter, + newValue -> config.farming.garden.pestHighlighter = newValue) .controller(ConfigUtils::createBooleanController) .build()) .option(Option.createBuilder() - .name(Text.translatable("skyblocker.config.farming.garden.visitorHelperGardenOnly")) - .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.visitorHelperGardenOnly.@Tooltip"))) - .binding(defaults.farming.garden.visitorHelperGardenOnly, - () -> config.farming.garden.visitorHelperGardenOnly, - newValue -> config.farming.garden.visitorHelperGardenOnly = newValue) + .name(Text.translatable("skyblocker.config.farming.garden.vacuumSolver")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.garden.vacuumSolver.@Tooltip"))) + .binding(defaults.farming.garden.vacuumSolver, + () -> config.farming.garden.vacuumSolver, + newValue -> config.farming.garden.vacuumSolver = newValue) .controller(ConfigUtils::createBooleanController) .build()) .option(Option.createBuilder() @@ -93,6 +85,35 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .controller(ConfigUtils::createBooleanController) .build()) .build()) + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.farming.visitorHelper")) + .collapsed(false) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.farming.visitorHelper.visitorHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.visitorHelper.visitorHelper.@Tooltip"))) + .binding(defaults.farming.visitorHelper.visitorHelper, + () -> config.farming.visitorHelper.visitorHelper, + newValue -> config.farming.visitorHelper.visitorHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.farming.visitorHelper.visitorHelperGardenOnly")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.visitorHelper.visitorHelperGardenOnly.@Tooltip"))) + .binding(defaults.farming.visitorHelper.visitorHelperGardenOnly, + () -> config.farming.visitorHelper.visitorHelperGardenOnly, + newValue -> config.farming.visitorHelper.visitorHelperGardenOnly = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.farming.visitorHelper.showStacksInVisitorHelper")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.farming.visitorHelper.showStacksInVisitorHelper.@Tooltip"))) + .binding(defaults.farming.visitorHelper.showStacksInVisitorHelper, + () -> config.farming.visitorHelper.showStacksInVisitorHelper, + newValue -> config.farming.visitorHelper.showStacksInVisitorHelper = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) .build(); + } } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java index 029c457937..18ac104d2a 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/FarmingConfig.java @@ -7,6 +7,9 @@ public class FarmingConfig { @SerialEntry public Garden garden = new Garden(); + @SerialEntry + public visitorHelper visitorHelper = new visitorHelper(); + public static class Garden { @SerialEntry public FarmingHud farmingHud = new FarmingHud(); @@ -14,14 +17,11 @@ public static class Garden { @SerialEntry public boolean dicerTitlePrevent = true; - @SerialEntry - public boolean visitorHelper = true; - @SerialEntry - public boolean visitorHelperGardenOnly = true; + public boolean pestHighlighter = true; @SerialEntry - public boolean showStacksInVisitorHelper = false; + public boolean vacuumSolver = false; @SerialEntry public boolean lockMouseTool = false; @@ -36,6 +36,17 @@ public static class Garden { public boolean closeScreenOnPlotClick = false; } + public static class visitorHelper { + @SerialEntry + public boolean visitorHelper = true; + + @SerialEntry + public boolean visitorHelperGardenOnly = true; + + @SerialEntry + public boolean showStacksInVisitorHelper = false; + } + public static class FarmingHud { @SerialEntry public boolean enableHud = true; diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index 79f45a3205..36fc711269 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -15,6 +15,7 @@ import de.hysky.skyblocker.skyblock.dwarven.WishingCompassSolver; import de.hysky.skyblocker.skyblock.end.EnderNodes; import de.hysky.skyblocker.skyblock.end.TheEnd; +import de.hysky.skyblocker.skyblock.garden.VacuumSolver; import de.hysky.skyblocker.skyblock.slayers.SlayerManager; import de.hysky.skyblocker.skyblock.slayers.boss.demonlord.FirePillarAnnouncer; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; @@ -146,5 +147,6 @@ public abstract class ClientPlayNetworkHandlerMixin { CrystalsChestHighlighter.onParticle(packet); EnderNodes.onParticle(packet); WishingCompassSolver.onParticle(packet); + VacuumSolver.onParticle(packet); } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java index ccbd74e7ad..7dc8f82e57 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java @@ -11,7 +11,7 @@ import de.hysky.skyblocker.skyblock.experiment.ExperimentSolver; import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver; import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver; -import de.hysky.skyblocker.skyblock.garden.VisitorHelper; +import de.hysky.skyblocker.skyblock.garden.visitorhelper.VisitorHelper; import de.hysky.skyblocker.skyblock.item.*; import de.hysky.skyblocker.skyblock.item.slottext.SlotTextManager; import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview; @@ -136,13 +136,6 @@ protected HandledScreenMixin(Text title) { } } - @Inject(at = @At("HEAD"), method = "mouseClicked") - public void skyblocker$mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { - if (SkyblockerConfigManager.get().farming.garden.visitorHelper && (!getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) { - VisitorHelper.onMouseClicked(mouseX, mouseY, button, this.textRenderer); - } - } - @ModifyExpressionValue(method = "mouseClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z")) public boolean skyblocker$passThroughSearchFieldUnfocusedClicks(boolean superClicked, double mouseX, double mouseY, int button) { //Handle Search Field clicks - as of 1.21.4 the game will only send clicks to the selected element rather than trying to send one to each and stopping when the first returns true (if any). @@ -294,8 +287,7 @@ protected HandledScreenMixin(Text title) { switch (this.handler) { case GenericContainerScreenHandler genericContainerScreenHandler when genericContainerScreenHandler.getRows() == 6 -> { - VisitorHelper.onSlotClick(slot, slotId, title, genericContainerScreenHandler.getSlot(13).getStack()); - + VisitorHelper.onSlotClick(slot, slotId, title); // Prevent selling to NPC shops ItemStack sellStack = this.handler.slots.get(49).getStack(); if (sellStack.getName().getString().equals("Sell Item") || ItemUtils.getLoreLineIf(sellStack, text -> text.contains("buyback")) != null) { @@ -326,6 +318,13 @@ protected HandledScreenMixin(Text title) { } } + @Inject(at = @At("HEAD"), method = "mouseClicked") + public void skyblocker$mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + if (SkyblockerConfigManager.get().farming.visitorHelper.visitorHelper && (Utils.getLocationRaw().equals("garden") && !getTitle().getString().contains("Logbook") || getTitle().getString().startsWith("Bazaar"))) { + VisitorHelper.handleMouseClick(mouseX, mouseY, button, this.textRenderer); + } + } + @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V")) private void skyblocker$drawOnItem(DrawContext context, Slot slot, CallbackInfo ci) { if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java index 65ad691841..75403dc511 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java @@ -14,6 +14,8 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.minecraft.entity.Entity; import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.mob.SilverfishEntity; +import net.minecraft.entity.passive.BatEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.math.Box; @@ -50,7 +52,7 @@ public static boolean shouldDrawMobBoundingBox(Entity entity) { return false; } - + public static float[] getBoxColor(Entity entity) { int color = MobGlow.getMobGlow(entity); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java index 909eea7e88..9d0d7440ed 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java @@ -1,6 +1,8 @@ package de.hysky.skyblocker.skyblock.entity; import com.google.common.collect.Streams; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.SlayersConfig; @@ -26,12 +28,13 @@ import net.minecraft.entity.mob.*; import net.minecraft.entity.passive.BatEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Items; import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.util.Formatting; import net.minecraft.util.math.Box; import net.minecraft.world.World; -import java.util.List; +import java.util.*; public class MobGlow { public static final int NO_GLOW = 0; @@ -40,6 +43,31 @@ public class MobGlow { */ private static final String NUKEKUBI_HEAD_TEXTURE = "eyJ0aW1lc3RhbXAiOjE1MzQ5NjM0MzU5NjIsInByb2ZpbGVJZCI6ImQzNGFhMmI4MzFkYTRkMjY5NjU1ZTMzYzE0M2YwOTZjIiwicHJvZmlsZU5hbWUiOiJFbmRlckRyYWdvbiIsInNpZ25hdHVyZVJlcXVpcmVkIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWIwNzU5NGUyZGYyNzM5MjFhNzdjMTAxZDBiZmRmYTExMTVhYmVkNWI5YjIwMjllYjQ5NmNlYmE5YmRiYjRiMyJ9fX0="; private static final String FEL_HEAD_TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTcyMDAyNTQ4Njg2MywKICAicHJvZmlsZUlkIiA6ICIzZDIxZTYyMTk2NzQ0Y2QwYjM3NjNkNTU3MWNlNGJlZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJTcl83MUJsYWNrYmlyZCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jMjg2ZGFjYjBmMjE0NGQ3YTQxODdiZTM2YmJhYmU4YTk4ODI4ZjdjNzlkZmY1Y2UwMTM2OGI2MzAwMTU1NjYzIiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0="; + private static final Set PEST_HEAD_TEXTURES = Set.of( + // Mosquito + "ewogICJ0aW1lc3RhbXAiIDogMTY5Njk0NTAyOTQ2MSwKICAicHJvZmlsZUlkIiA6ICI3NTE0NDQ4MTkxZTY0NTQ2OGM5NzM5YTZlMzk1N2JlYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJUaGFua3NNb2phbmciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTJhOWZlMDViYzY2M2VmY2QxMmU1NmEzY2NjNWVjMDM1YmY1NzdiNzg3MDg1NDhiNmY0ZmZjZjFkMzBlY2NmZSIKICAgIH0KICB9Cn0=", + // Rat + "ewogICJ0aW1lc3RhbXAiIDogMTYxODQxOTcwMTc1MywKICAicHJvZmlsZUlkIiA6ICI3MzgyZGRmYmU0ODU0NTVjODI1ZjkwMGY4OGZkMzJmOCIsCiAgInByb2ZpbGVOYW1lIiA6ICJCdUlJZXQiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYThhYmI0NzFkYjBhYjc4NzAzMDExOTc5ZGM4YjQwNzk4YTk0MWYzYTRkZWMzZWM2MWNiZWVjMmFmOGNmZmU4IiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0=", + // Locust + "ewogICJ0aW1lc3RhbXAiIDogMTY5NzU1NzA3NzAzNywKICAicHJvZmlsZUlkIiA6ICI0YjJlMGM1ODliZjU0ZTk1OWM1ZmJlMzg5MjQ1MzQzZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJfTmVvdHJvbl8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGIyNGE0ODJhMzJkYjFlYTc4ZmI5ODA2MGIwYzJmYTRhMzczY2JkMThhNjhlZGRkZWI3NDE5NDU1YTU5Y2RhOSIKICAgIH0KICB9Cn0=", + // Cricket + "ewogICJ0aW1lc3RhbXAiIDogMTcyMzE3OTgxMTI2NCwKICAicHJvZmlsZUlkIiA6ICJjZjc4YzFkZjE3ZTI0Y2Q5YTIxYmU4NWQ0NDk5ZWE4ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNYXR0c0FybW9yU3RhbmRzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2EyNGM2OWY5NmNlNTU2MjIxZTE5NWM4ZWYyYmZhZDcxZWJmN2Y5NWY1YWU5MTRhNDg0YThkMGVjMjE2NzI2NzQiCiAgICB9CiAgfQp9", + // Fly + "ewogICJ0aW1lc3RhbXAiIDogMTY5Njk0NTA2MzI4MSwKICAicHJvZmlsZUlkIiA6ICJjN2FmMWNkNjNiNTE0Y2YzOGY4NWQ2ZDUxNzhjYThlNCIsCiAgInByb2ZpbGVOYW1lIiA6ICJtb25zdGVyZ2FtZXIzMTUiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWQ5MGU3Nzc4MjZhNTI0NjEzNjhlMjZkMWIyZTE5YmZhMWJhNTgyZDYwMjQ4M2U1NDVmNDEyNGQwZjczMTg0MiIKICAgIH0KICB9Cn0=", + // Beetle + "ewogICJ0aW1lc3RhbXAiIDogMTcyMzE3OTc4OTkzNCwKICAicHJvZmlsZUlkIiA6ICJlMjc5NjliODYyNWY0NDg1YjkyNmM5NTBhMDljMWMwMSIsCiAgInByb2ZpbGVOYW1lIiA6ICJLRVZJTktFTE9LRSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83MGExZTgzNmJmMTk2OGIyZWFhNDgzNzIyN2ExOTIwNGYxNzI5NWQ4NzBlZTllNzU0YmQ2YjZkNjBkZGJlZDNjIgogICAgfQogIH0KfQ==", + // Slug + "ewogICJ0aW1lc3RhbXAiIDogMTY5NzQ3MDQ0MzA4MiwKICAicHJvZmlsZUlkIiA6ICJkOGNkMTNjZGRmNGU0Y2IzODJmYWZiYWIwOGIyNzQ4OSIsCiAgInByb2ZpbGVOYW1lIiA6ICJaYWNoeVphY2giLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2E3OWQwZmQ2NzdiNTQ1MzA5NjExMTdlZjg0YWRjMjA2ZTJjYzUwNDVjMTM0NGQ2MWQ3NzZiZjhhYzJmZTFiYSIKICAgIH0KICB9Cn0=", + // Moth + "ewogICJ0aW1lc3RhbXAiIDogMTY5Njg3MDQwNTk1NCwKICAicHJvZmlsZUlkIiA6ICJiMTUyZDlhZTE1MTM0OWNmOWM2NmI0Y2RjMTA5NTZjOCIsCiAgInByb2ZpbGVOYW1lIiA6ICJNaXNxdW90aCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82NTQ4NWM0YjM0ZTViNTQ3MGJlOTRkZTEwMGU2MWY3ODE2ZjgxYmM1YTExZGZkZjBlY2NmODkwMTcyZGE1ZDBhIgogICAgfQogIH0KfQ==", + // Mite + "ewogICJ0aW1lc3RhbXAiIDogMTY5Njg3MDQxOTcyNSwKICAicHJvZmlsZUlkIiA6ICJkYjYzNWE3MWI4N2U0MzQ5YThhYTgwOTMwOWFhODA3NyIsCiAgInByb2ZpbGVOYW1lIiA6ICJFbmdlbHMxNzQiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmU2YmFmNjQzMWE5ZGFhMmNhNjA0ZDVhM2MyNmU5YTc2MWQ1OTUyZjA4MTcxNzRhNGZlMGI3NjQ2MTZlMjFmZiIKICAgIH0KICB9Cn0=", + // Earthworm + "ewogICJ0aW1lc3RhbXAiIDogMTY5NzQ3MDQ1OTc0NywKICAicHJvZmlsZUlkIiA6ICIyNTBlNzc5MjZkNDM0ZDIyYWM2MTQ4N2EyY2M3YzAwNCIsCiAgInByb2ZpbGVOYW1lIiA6ICJMdW5hMTIxMDUiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjQwM2JhNDAyN2EzMzNkOGQyZmQzMmFiNTlkMWNmZGJhYTdkOTA4ZDgwZDIzODFkYjJhNjljYmU2NTQ1MGFkOCIKICAgIH0KICB9Cn0=", + // Field Mouse + "ewogICJ0aW1lc3RhbXAiIDogMTcyNzkwNDc5NzQ1OSwKICAicHJvZmlsZUlkIiA6ICI0MmIwOTMyZDUwMWI0MWQ1YTM4YjEwOTcxYTYwYmYxMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJBaXJib2x0MDc4IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2YzNzllMDkyNTI4MTczMTRiZDBiNjk0ZjdkNTNiNDhhZjJjN2ZhODQ5OTEwOTgwMmE0MWJiMjk0ZDJmOTNlM2UiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ==" + ); + /** * Cache for mob glow. Absence means the entity does not have custom glow. * If an entity is in the cache, it must have custom glow. @@ -139,6 +167,9 @@ private static int computeMobGlow(Entity entity) { // Enderman Slayer's Nukekubi Skulls case ArmorStandEntity armorStand when SkyblockerConfigManager.get().slayers.endermanSlayer.highlightNukekubiHeads && Utils.isInTheEnd() && armorStand.isMarker() && SlayerManager.isInSlayer() && isNukekubiHead(armorStand) -> 0x990099; + // pests + case ArmorStandEntity armorStand when SkyblockerConfigManager.get().farming.garden.pestHighlighter && Utils.isOnGarden() && isPestHead(armorStand) -> 0xb62f00; + // Blaze Slayer's Demonic minions case WitherSkeletonEntity e when SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW && SlayerManager.isInSlayerType(SlayerType.DEMONLORD) && e.distanceTo(MinecraftClient.getInstance().player) <= 15 -> AttunementColors.getColor(e); case ZombifiedPiglinEntity e when SkyblockerConfigManager.get().slayers.highlightBosses == SlayersConfig.HighlightSlayerEntities.GLOW && SlayerManager.isInSlayerType(SlayerType.DEMONLORD) && e.distanceTo(MinecraftClient.getInstance().player) <= 15 -> AttunementColors.getColor(e); @@ -186,4 +217,14 @@ public static List getArmorStands(World world, Box box) { private static boolean isNukekubiHead(ArmorStandEntity entity) { return Streams.stream(entity.getArmorItems()).map(ItemUtils::getHeadTexture).anyMatch(headTexture -> headTexture.contains(NUKEKUBI_HEAD_TEXTURE)); } + + /** + * Compares the armor items of an armor stand to the Pest head texture to determine if it is a Pest head. + */ + + private static boolean isPestHead(ArmorStandEntity entity) { + return Streams.stream(entity.getArmorItems()) + .map(ItemUtils::getHeadTexture) + .anyMatch(PEST_HEAD_TEXTURES::contains); + } } 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 441c8e6f42..0264add07e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java @@ -44,8 +44,6 @@ public class FarmingHud { private static final LongPriorityQueue blockBreaks = new LongArrayFIFOQueue(); private static final Queue farmingXp = new ArrayDeque<>(); private static float farmingXpPercentProgress; - private static double smoothedBlocksPerSecond = 0.0; - private static double smoothedFarmingXpPerHour = 0.0; @Init public static void init() { @@ -63,7 +61,7 @@ public static void init() { assert client.player != null; ItemStack stack = client.player.getMainHandStack(); - if (stack == null || !tryGetCounter(stack, CounterType.CULTIVATING) && !tryGetCounter(stack, CounterType.COUNTER)) { + if (stack == null || tryGetCounter(stack, CounterType.CULTIVATING) && tryGetCounter(stack, CounterType.COUNTER)) { counterType = CounterType.NONE; } } @@ -92,7 +90,7 @@ public static void init() { private static boolean tryGetCounter(ItemStack stack, CounterType counterType) { NbtCompound customData = ItemUtils.getCustomData(stack); - if (customData == null || !customData.contains(counterType.nbtKey, NbtElement.NUMBER_TYPE)) return false; + if (customData == null || !customData.contains(counterType.nbtKey, NbtElement.NUMBER_TYPE)) return true; int count = customData.getInt(counterType.nbtKey); if (FarmingHud.counterType != counterType) { counter.clear(); @@ -101,7 +99,7 @@ private static boolean tryGetCounter(ItemStack stack, CounterType counterType) { if (counter.isEmpty() || counter.peekLast().leftInt() != count) { counter.offer(IntLongPair.of(count, System.currentTimeMillis())); } - return true; + return false; } private static boolean shouldRender() { @@ -133,34 +131,35 @@ public static float cropsPerMinute() { } public static double blockBreaks() { - long currentTime = System.currentTimeMillis(); - while (!blockBreaks.isEmpty() && blockBreaks.firstLong() + 1000 < currentTime) { - blockBreaks.dequeueLong(); + if (blockBreaks.isEmpty()) { + return 0.0; } - double rawBlocksPerSecond = blockBreaks.size(); - double t = 0.01; - smoothedBlocksPerSecond += (rawBlocksPerSecond - smoothedBlocksPerSecond) * t; + long firstTimestamp = blockBreaks.firstLong(); + long lastTimestamp = System.currentTimeMillis(); - return Math.round(smoothedBlocksPerSecond * 10) / 10.0; + double timeDifferenceInSeconds = (lastTimestamp - firstTimestamp) / 1000.0; + + if (timeDifferenceInSeconds <= 0) { + return 0.0; + } + + return Math.round((blockBreaks.size() / timeDifferenceInSeconds) * 100) / 100.0; } + public static float farmingXpPercentProgress() { - return farmingXpPercentProgress; + return Math.min(Math.max(farmingXpPercentProgress, 0), 100); } public static double farmingXpPerHour() { double xpPerCrop = farmingXp.isEmpty() ? 0 : farmingXp.peek().leftFloat(); double cropsPerSecond = blockBreaks(); double xpPerSecond = xpPerCrop * cropsPerSecond; - double t = 0.1; - - smoothedFarmingXpPerHour += (xpPerSecond * 3600 - smoothedFarmingXpPerHour) * t; - return Math.round(smoothedFarmingXpPerHour * 10) / 10.0; + return Math.round(xpPerSecond * 3600 * 10) / 10.0; } - public enum CounterType { NONE("", "No Counter"), COUNTER("mined_crops", "Counter: "), 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 2f452bd634..a3c7d8d90e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHudWidget.java @@ -96,7 +96,7 @@ public void updateContent() { addSimpleIcoText(Ico.GOLD, "Coins/h: ", Formatting.GOLD, getPriceText(cropItemId, cropsPerMinute)); addSimpleIcoText(cropStack, "Blocks/s: ", Formatting.YELLOW, Double.toString(FarmingHud.blockBreaks())); //noinspection DataFlowIssue - addComponent(new ProgressComponent(Ico.LANTERN, Text.literal("Farming Level: "), FarmingHud.farmingXpPercentProgress(), Formatting.GOLD.getColorValue())); + addComponent(new ProgressComponent(Ico.LANTERN, Text.literal("Farming Level:"), FarmingHud.farmingXpPercentProgress(), Formatting.GOLD.getColorValue())); addSimpleIcoText(Ico.LIME_DYE, "Farming XP/h: ", Formatting.YELLOW, FarmingHud.NUMBER_FORMAT.format(FarmingHud.farmingXpPerHour())); Entity cameraEntity = client.getCameraEntity(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VacuumSolver.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VacuumSolver.java new file mode 100644 index 0000000000..821b98dea0 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/VacuumSolver.java @@ -0,0 +1,122 @@ +package de.hysky.skyblocker.skyblock.garden; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.render.RenderHelper; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.item.Item; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.network.packet.s2c.play.ParticleS2CPacket; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class VacuumSolver { + + private static final long PARTICLES_MAX_DELAY = 500; + private static final double PARTICLES_MAX_DISTANCE = 4.0; + private static final List particleTrail = new LinkedList<>(); + private static Vec3d fixedDestination = null; + private static long lastParticleUpdate = System.currentTimeMillis(); + private static final Map linkedMarkers = new HashMap<>(); + + @Init + public static void init() { + ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset()); + WorldRenderEvents.AFTER_TRANSLUCENT.register(VacuumSolver::renderTrail); + } + + private static void reset() { + particleTrail.clear(); + lastParticleUpdate = System.currentTimeMillis(); + linkedMarkers.clear(); + } + + public static void onParticle(ParticleS2CPacket packet) { + if (!Utils.isOnGarden() || SkyblockerConfigManager.get().farming.garden.vacuumSolver || !ParticleTypes.ANGRY_VILLAGER.equals(packet.getParameters().getType())) { + return; + } + + if (!isHoldingVacuum()) { + return; + } + + Vec3d particlePos = new Vec3d(packet.getX(), packet.getY(), packet.getZ()); + long currentTime = System.currentTimeMillis(); + + if (currentTime - lastParticleUpdate > PARTICLES_MAX_DELAY) { + particleTrail.clear(); + } + + if (!particleTrail.isEmpty() && !(particleTrail.getLast().distanceTo(particlePos) <= PARTICLES_MAX_DISTANCE)) { + particleTrail.clear(); + } + + particleTrail.add(particlePos); + fixedDestination = particlePos; + + lastParticleUpdate = currentTime; + linkMarkerWithDestination(); + } + + private static boolean isHoldingVacuum() { + MinecraftClient client = MinecraftClient.getInstance(); + + if (client.player == null || client.player.getMainHandStack().isEmpty()) { + return false; + } + + List tooltip = client.player.getMainHandStack().getTooltip(Item.TooltipContext.DEFAULT, client.player, TooltipType.BASIC); + + return tooltip.stream().anyMatch(text -> text.getString().contains("Pest Tracker")); + } + + + // Highlight flickers when no pest is nearby? it should render regardless + + private static void linkMarkerWithDestination() { + if (fixedDestination == null || MinecraftClient.getInstance().world == null) return; + + for (Entity entity : MinecraftClient.getInstance().world.getEntities()) { + if (entity instanceof ArmorStandEntity armorStand + && armorStand.isMarker() + && armorStand.hasCustomName() + && armorStand.getCustomName() != null + && armorStand.getCustomName().getString().startsWith("ൠ")) { + linkedMarkers.put(armorStand, new BlockPos((int) fixedDestination.getX(), (int) fixedDestination.getY(), (int) fixedDestination.getZ())); + break; + } + } + } + + private static void renderTrail(WorldRenderContext context) { + if (fixedDestination == null) return; + + float[] color = {1f, 0f, 0f}; + + RenderHelper.renderFilled(context, new BlockPos((int) fixedDestination.getX(), (int) fixedDestination.getY(), (int) fixedDestination.getZ()), color, 2.0f, false); + + linkedMarkers.entrySet().removeIf(entry -> entry.getKey().isRemoved()); + + for (BlockPos pos : linkedMarkers.values()) { + RenderHelper.renderFilled(context, pos, color, 2.0f, false); + } + + if (linkedMarkers.isEmpty()) { + fixedDestination = null; + } + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java deleted file mode 100644 index e301d12d8d..0000000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/VisitorHelper.java +++ /dev/null @@ -1,364 +0,0 @@ -package de.hysky.skyblocker.skyblock.garden; - -import de.hysky.skyblocker.annotations.Init; -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; -import de.hysky.skyblocker.utils.Constants; -import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.NEURepoManager; -import de.hysky.skyblocker.utils.Utils; -import de.hysky.skyblocker.utils.scheduler.MessageScheduler; -import io.github.moulberry.repo.data.NEUItem; -import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; -import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.ingame.HandledScreen; -import net.minecraft.component.DataComponentTypes; -import net.minecraft.item.ItemStack; -import net.minecraft.screen.ScreenHandler; -import net.minecraft.screen.slot.Slot; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.NumberFormat; -import java.util.*; - -//TODO: Get visitors "rarity" and apply it to their name in the helper list -public class VisitorHelper { - private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Visitor Helper"); - private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US); - - // The pair contains the name of the visitor and the texture if the icon is a player head - private static final Map, Object2IntMap> itemMap = new HashMap<>(); - private static final Map itemCache = new HashMap<>(); - private static final int TEXT_START_X = 4; - private static final int TEXT_START_Y = 4; - private static final int ENTRY_INDENT = 8; - private static final int ITEM_INDENT = 20; - private static final int LINE_SPACING = 3; - - private static boolean shouldProcessVisitorItems = true; - - @Init - public static void init() { - ScreenEvents.BEFORE_INIT.register((client, screen, scaledWidth, scaledHeight) -> { - - String title = screen.getTitle().getString(); - boolean isGardenLocation = Utils.getLocationRaw().equals("garden"); - - if (SkyblockerConfigManager.get().farming.garden.visitorHelper && - (!SkyblockerConfigManager.get().farming.garden.visitorHelperGardenOnly || isGardenLocation) && - screen instanceof HandledScreen handledScreen && - (!title.contains("Logbook") || title.startsWith("Bazaar"))) { - - ScreenEvents.afterRender(screen).register((screen_, context, mouseX, mouseY, delta) -> - renderScreen(title, context, client.textRenderer, handledScreen.getScreenHandler(), mouseX, mouseY)); - - ScreenEvents.remove(screen).register(screen_ -> shouldProcessVisitorItems = true); - } - }); - } - - public static void renderScreen(String title, DrawContext context, TextRenderer textRenderer, ScreenHandler handler, int mouseX, int mouseY) { - if (handler.getCursorStack() == ItemStack.EMPTY && shouldProcessVisitorItems) processVisitorItem(title, handler); - drawScreenItems(context, textRenderer, mouseX, mouseY); - } - - // The location of copy amount, and item text seem to be overlapping so clicking on part of the itemText will run copy amount. - public static void onMouseClicked(double mouseX, double mouseY, int mouseButton, TextRenderer textRenderer) { - - int yPosition = TEXT_START_Y; - boolean showStacks = SkyblockerConfigManager.get().farming.garden.showStacksInVisitorHelper; - - // Group visitors and items like in drawScreenItems - Map> itemToVisitorsMap = new LinkedHashMap<>(); - Map itemToTotalAmountMap = new LinkedHashMap<>(); - - for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { - Pair visitorName = visitorEntry.getKey(); - Object2IntMap visitorItems = visitorEntry.getValue(); - - for (Object2IntMap.Entry itemEntry : visitorItems.object2IntEntrySet()) { - String itemName = itemEntry.getKey(); - int amount = itemEntry.getIntValue(); - - itemToVisitorsMap.computeIfAbsent(itemName, k -> new ArrayList<>()).add(visitorName.left()); - itemToTotalAmountMap.put(itemName, itemToTotalAmountMap.getOrDefault(itemName, 0) + amount); - } - } - - Set processedVisitors = new HashSet<>(); - for (Map.Entry> groupedEntry : itemToVisitorsMap.entrySet()) { - String itemName = groupedEntry.getKey(); - List visitors = groupedEntry.getValue(); - int totalAmount = itemToTotalAmountMap.get(itemName); - - // Check grouped visitor names - for (String visitor : visitors) { - if (!processedVisitors.contains(visitor)) { - yPosition += LINE_SPACING + textRenderer.fontHeight; - processedVisitors.add(visitor); - } - } - - // Adjust itemText and copy amount positions for stack-based display - String amountText = showStacks && totalAmount >= 64 - ? (totalAmount / 64) + " stacks" + (totalAmount % 64 > 0 ? " + " + (totalAmount % 64) : "") - : "" + totalAmount; - - String combinedText = itemName + " x" + amountText; - int itemTextWidth = textRenderer.getWidth(combinedText); - int copyAmountX = TEXT_START_X + ENTRY_INDENT + itemTextWidth; - int copyAmountWidth = textRenderer.getWidth(" [Copy Amount]"); - - if (isMouseOverText(mouseX, mouseY, TEXT_START_X + ENTRY_INDENT, yPosition, itemTextWidth, textRenderer.fontHeight)) { - MessageScheduler.INSTANCE.sendMessageAfterCooldown("/bz " + itemName, true); - return; - } - if (isMouseOverText(mouseX, mouseY, copyAmountX, yPosition, copyAmountWidth, textRenderer.fontHeight)) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player != null) { - client.keyboard.setClipboard(String.valueOf(totalAmount)); - client.player.sendMessage(Constants.PREFIX.get().append("Copied amount successfully"), false); - } - return; - } - - yPosition += LINE_SPACING + textRenderer.fontHeight; - } - - // Check remaining visitors with unshared items - for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { - Pair visitorName = visitorEntry.getKey(); - if (processedVisitors.contains(visitorName.left())) continue; - - yPosition += LINE_SPACING + textRenderer.fontHeight; - - for (Object2IntMap.Entry itemEntry : visitorEntry.getValue().object2IntEntrySet()) { - String itemName = itemEntry.getKey(); - int amount = itemEntry.getIntValue(); - - String amountText = showStacks && amount >= 64 - ? (amount / 64) + " stacks" + (amount % 64 > 0 ? " + " + (amount % 64) : "") - : "" + amount; - - String combinedText = itemName + " x" + amountText; - int itemTextX = TEXT_START_X + ENTRY_INDENT; - int itemTextWidth = textRenderer.getWidth(combinedText); - int copyAmountX = itemTextX + itemTextWidth; - int copyAmountWidth = textRenderer.getWidth(" [Copy Amount]"); - - if (isMouseOverText(mouseX, mouseY, itemTextX, yPosition, itemTextWidth, textRenderer.fontHeight)) { - MessageScheduler.INSTANCE.sendMessageAfterCooldown("/bz " + itemName, true); - return; - } - - if (isMouseOverText(mouseX, mouseY, copyAmountX, yPosition, copyAmountWidth, textRenderer.fontHeight)) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player != null) { - client.keyboard.setClipboard(amountText); - client.player.sendMessage(Constants.PREFIX.get().append("Copied amount successfully"), false); - } - return; - } - yPosition += LINE_SPACING + textRenderer.fontHeight; - } - } - } - - public static void onSlotClick(Slot slot, int slotId, String title, ItemStack visitorHeadStack) { - if ((slotId == 29 || slotId == 13 || slotId == 33) && slot.hasStack() && ItemUtils.getLoreLineIf(slot.getStack(), s -> s.equals("Click to give!") || s.equals("Click to refuse!")) != null) { - itemMap.remove(new ObjectObjectImmutablePair<>(title, getTextureOrNull(visitorHeadStack))); - shouldProcessVisitorItems = false; - } - } - - private static void processVisitorItem(String visitorName, ScreenHandler handler) { - ItemStack visitorItem = handler.getSlot(13).getStack(); - if (visitorItem == null || !visitorItem.contains(DataComponentTypes.LORE) || ItemUtils.getLoreLineIf(visitorItem, t -> t.contains("Times Visited")) == null) return; - ItemStack acceptButton = handler.getSlot(29).getStack(); - if (acceptButton == null) return; - processLore(visitorName, getTextureOrNull(visitorItem), ItemUtils.getLore(acceptButton)); - } - - private static @Nullable String getTextureOrNull(ItemStack stack) { - String texture = ItemUtils.getHeadTexture(stack); - - return texture.isEmpty() ? null : texture; - } - - private static void processLore(String visitorName, @Nullable String visitorTexture, List loreList) { - boolean saveRequiredItems = false; - for (Text text : loreList) { - String lore = text.getString(); - if (lore.contains("Items Required")) - saveRequiredItems = true; - else if (lore.contains("Rewards")) - break; - else if (saveRequiredItems) - updateItemMap(visitorName, visitorTexture, text); - } - } - - private static void updateItemMap(String visitorName, @Nullable String visitorTexture, Text lore) { - String[] splitItemText = lore.getString().split(" x"); - String itemName = splitItemText[0].trim(); - if (itemName.isEmpty()) return; - try { - int amount = splitItemText.length == 2 ? NUMBER_FORMAT.parse(splitItemText[1].trim()).intValue() : 1; - - Pair visitorKey = Pair.of(visitorName, visitorTexture); - Object2IntMap visitorMap = itemMap.computeIfAbsent(visitorKey, _key -> new Object2IntOpenHashMap<>()); - - visitorMap.put(itemName, amount); // Replace instead of accumulating repeatedly - } catch (Exception e) { - LOGGER.error("[Skyblocker Visitor Helper] Failed to parse item: {}", lore.getString(), e); - } - } - - private static void drawScreenItems(DrawContext context, TextRenderer textRenderer, int mouseX, int mouseY) { - context.getMatrices().push(); - context.getMatrices().translate(0, 0, 200); - - int index = 0; - boolean showStacks = SkyblockerConfigManager.get().farming.garden.showStacksInVisitorHelper; - - Map> itemToVisitorsMap = new LinkedHashMap<>(); - Map itemToTotalAmountMap = new LinkedHashMap<>(); - - for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { - Pair visitorName = visitorEntry.getKey(); - Object2IntMap visitorItems = visitorEntry.getValue(); - - for (Object2IntMap.Entry itemEntry : visitorItems.object2IntEntrySet()) { - String itemName = itemEntry.getKey(); - int amount = itemEntry.getIntValue(); - - itemToVisitorsMap.computeIfAbsent(itemName, k -> new ArrayList<>()).add(visitorName.left()); - itemToTotalAmountMap.put(itemName, itemToTotalAmountMap.getOrDefault(itemName, 0) + amount); - } - } - - Set processedVisitors = new HashSet<>(); - for (Map.Entry> groupedEntry : itemToVisitorsMap.entrySet()) { - String itemName = groupedEntry.getKey(); - List visitors = groupedEntry.getValue(); - int totalAmount = itemToTotalAmountMap.get(itemName); - - for (String visitor : visitors) { - if (!processedVisitors.contains(visitor)) { - drawTextWithOptionalUnderline(context, textRenderer, Text.literal(visitor), TEXT_START_X, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); - index++; - processedVisitors.add(visitor); - } - } - - String amountText = showStacks && totalAmount >= 64 - ? (totalAmount / 64) + " stacks" + (totalAmount % 64 > 0 ? " + " + (totalAmount % 64) : "") - : "" + totalAmount; - - ItemStack stack = getCachedItem(itemName); - drawItemEntryWithHover(context, textRenderer, stack, itemName, amountText, index, mouseX, mouseY); - - index++; - } - - for (Map.Entry, Object2IntMap> visitorEntry : itemMap.entrySet()) { - Pair visitorName = visitorEntry.getKey(); - if (processedVisitors.contains(visitorName.left())) continue; - - drawTextWithOptionalUnderline(context, textRenderer, Text.literal(visitorName.left()), TEXT_START_X, TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight), mouseX, mouseY); - index++; - - for (Object2IntMap.Entry itemEntry : visitorEntry.getValue().object2IntEntrySet()) { - String itemName = itemEntry.getKey(); - int amount = itemEntry.getIntValue(); - - String amountText = showStacks && amount >= 64 - ? (amount / 64) + " stacks" + (amount % 64 > 0 ? " + " + (amount % 64) : "") - : "" + amount; - - ItemStack stack = getCachedItem(itemName); - drawItemEntryWithHover(context, textRenderer, stack, itemName + " x" + amountText, String.valueOf(amount), index, mouseX, mouseY); - index++; - } - } - - context.getMatrices().pop(); - } - - private static int drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, Object2IntMap.Entry itemEntry, int index, int mouseX, int mouseY) { - String itemName = itemEntry.getKey(); - int amount = itemEntry.getIntValue(); - ItemStack stack = getCachedItem(itemName); - drawItemEntryWithHover(context, textRenderer, stack, itemName, String.valueOf(amount), index, mouseX, mouseY); - return index + 1; - } - - - private static ItemStack getCachedItem(String displayName) { - String strippedName = Formatting.strip(displayName); - ItemStack cachedStack = itemCache.get(strippedName); - if (cachedStack != null) return cachedStack; - if (NEURepoManager.isLoading() || !ItemRepository.filesImported()) return null; // Item repo might be taking its sweet time doing things and cause concurrent modification error - Map items = NEURepoManager.NEU_REPO.getItems().getItems(); - if (items == null) return null; - ItemStack stack = items.values().stream() - .filter(i -> Formatting.strip(i.getDisplayName()).equals(strippedName)) - .findFirst() - .map(NEUItem::getSkyblockItemId) - .map(ItemRepository::getItemStack) - .orElse(null); - if (stack == null) return null; - itemCache.put(strippedName, stack); - return stack; - } - - /** - * Draws the item entry, amount, and copy amount text with optional underline and the item icon. - */ - private static void drawItemEntryWithHover(DrawContext context, TextRenderer textRenderer, @Nullable ItemStack stack, String itemName, String amountText, int index, int mouseX, int mouseY) { - - Text itemText = Text.literal(itemName).formatted(Formatting.GREEN) - .append(Text.literal(" x" + amountText).formatted(Formatting.WHITE)); - Text copyAmountText = Text.literal(" [Copy Amount]").formatted(Formatting.YELLOW); - - int y = TEXT_START_Y + index * (LINE_SPACING + textRenderer.fontHeight); - int itemTextX = TEXT_START_X + ENTRY_INDENT + ITEM_INDENT; - int itemIconX = TEXT_START_X + ENTRY_INDENT; - - drawTextWithOptionalUnderline(context, textRenderer, itemText, itemTextX, y, mouseX, mouseY); - - int copyAmountX = itemTextX + textRenderer.getWidth(itemText.getString()); - - drawTextWithOptionalUnderline(context, textRenderer, copyAmountText, copyAmountX, y, mouseX, mouseY); - - if (stack != null) { - context.drawItem(stack, itemIconX, y - textRenderer.fontHeight + 5); - } - } - - - - private static void drawTextWithOptionalUnderline(DrawContext context, TextRenderer textRenderer, Text text, int x, int y, int mouseX, int mouseY) { - context.getMatrices().push(); - context.getMatrices().translate(0, 0, 150); // This also puts our z at 350 - context.drawText(textRenderer, text, x, y, -1, true); - if (isMouseOverText(mouseX, mouseY, x, y, textRenderer.getWidth(text), textRenderer.fontHeight)) { - context.drawHorizontalLine(x, x + textRenderer.getWidth(text), y + textRenderer.fontHeight, -1); - } - context.getMatrices().pop(); - } - - private static boolean isMouseOverText(double mouseX, double mouseY, int x, int y, int width, int height) { - return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/Visitor.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/Visitor.java new file mode 100644 index 0000000000..eea307d704 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/Visitor.java @@ -0,0 +1,50 @@ +package de.hysky.skyblocker.skyblock.garden.visitorhelper; + +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import java.util.Objects; +import java.util.HashMap; +import java.util.Map; + +public class Visitor { + private final Text name; + private final ItemStack head; + private final Map requiredItems; + + public Visitor(Text name, ItemStack head) { + this.name = name; + this.head = head; + this.requiredItems = new HashMap<>(); + } + + public Text name() { + return name; + } + + public ItemStack head() { + return head; + } + + public Map requiredItems() { + return requiredItems; + } + + public void addRequiredItem(Text item, int amount) { + requiredItems.put(item, requiredItems.getOrDefault(item, 0) + amount); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Visitor visitor = (Visitor) o; + return Objects.equals(name, visitor.name) && + ItemStack.areEqual(head, visitor.head) && + Objects.equals(requiredItems, visitor.requiredItems); + } + + @Override + public int hashCode() { + return Objects.hash(name, head, requiredItems); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/VisitorHelper.java new file mode 100644 index 0000000000..9288d7213b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/VisitorHelper.java @@ -0,0 +1,276 @@ +package de.hysky.skyblocker.skyblock.garden.visitorhelper; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.NEURepoManager; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.scheduler.MessageScheduler; +import io.github.moulberry.repo.data.NEUItem; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.*; + +public class VisitorHelper { + + private static final Map activeVisitors = new LinkedHashMap<>(); + private static final Map cachedItems = new HashMap<>(); + private static final int X_OFFSET = 4; + private static final int Y_OFFSET = 4; + private static final int ICON_SIZE = 16; + private static final int LINE_HEIGHT = 3; + + @Init + public static void initialize() { + ScreenEvents.BEFORE_INIT.register((client, screen, scaledWidth, scaledHeight) -> { + if (!(screen instanceof HandledScreen handledScreen)) return; + + boolean isHelperEnabled = SkyblockerConfigManager.get().farming.visitorHelper.visitorHelper; + boolean isGardenMode = SkyblockerConfigManager.get().farming.visitorHelper.visitorHelperGardenOnly; + boolean canRenderScreen = isHelperEnabled && (!isGardenMode || Utils.isOnGarden() || Utils.getIslandArea().contains("Bazaar")); + + if (canRenderScreen) { + ScreenEvents.afterRender(screen).register((screen_, context, mouseX, mouseY, delta) -> + renderVisitorUI(context, client.textRenderer, handledScreen.getScreenHandler())); + } + }); + } + + /** + * Renders the visitor UI on the screen. + */ + public static void renderVisitorUI(DrawContext context, TextRenderer textRenderer, ScreenHandler handler) { + updateVisitors(handler); + drawVisitorItems(context, textRenderer); + } + + /** + * Updates the current visitors and their required items. + */ + private static void updateVisitors(ScreenHandler handler) { + ItemStack visitorHead = handler.getSlot(13).getStack(); + if (visitorHead == null || !visitorHead.contains(DataComponentTypes.LORE) || ItemUtils.getLoreLineIf(visitorHead, t -> t.contains("Times Visited")) == null) return; + + Text visitorName = visitorHead.getName(); + if (activeVisitors.keySet().stream().anyMatch(visitor -> visitor.name().equals(visitorName))) return; + + Visitor newVisitor = new Visitor(visitorName, visitorHead.copy()); + extractRequiredItems(handler, newVisitor); + + if (!newVisitor.requiredItems().isEmpty()) { + activeVisitors.put(newVisitor, true); + } + } + + /** + * Extracts the required items for the given visitor. + */ + private static void extractRequiredItems(ScreenHandler handler, Visitor visitor) { + ItemStack acceptButton = handler.getSlot(29).getStack(); + if (acceptButton == null || ItemUtils.getLoreLineIf(acceptButton, t -> t.contains("Items Required")) == null) return; + + ItemUtils.getLore(acceptButton).stream() + .map(Text::getString) + .map(String::trim) + .filter(lore -> !lore.isEmpty() && !lore.contains("Rewards")) + .filter(lore -> lore.contains(" x")) + .forEach(lore -> { + String[] parts = lore.split(" x"); + visitor.addRequiredItem(Text.literal(parts[0].trim()), Integer.parseInt(parts[1].trim())); + }); + } + + /** + * Retrieves a cached ItemStack or fetches it if not already cached. + */ + private static ItemStack getCachedItem(String itemName) { + String cleanName = Formatting.strip(itemName); + return cachedItems.computeIfAbsent(cleanName, name -> { + if (NEURepoManager.isLoading() || !ItemRepository.filesImported()) return null; + + return NEURepoManager.NEU_REPO.getItems().getItems() + .values().stream() + .filter(item -> Formatting.strip(item.getDisplayName()).equals(name)) + .findFirst() + .map(NEUItem::getSkyblockItemId) + .map(ItemRepository::getItemStack) + .orElse(null); + }); + } + + /** + * Draws the visitor items and their associated information. + */ + private static void drawVisitorItems(DrawContext context, TextRenderer textRenderer) { + int index = 0; + Map groupedItems = new LinkedHashMap<>(); + Map> visitorsByItem = new LinkedHashMap<>(); + + activeVisitors.keySet().forEach(visitor -> + visitor.requiredItems().forEach((itemName, amount) -> { + groupedItems.put(itemName, groupedItems.getOrDefault(itemName, 0) + amount); + visitorsByItem.computeIfAbsent(itemName, k -> new LinkedList<>()).add(visitor); + }) + ); + + context.getMatrices().push(); + context.getMatrices().translate(0, 0, 200); + + for (Map.Entry entry : groupedItems.entrySet()) { + Text itemName = entry.getKey(); + int totalAmount = entry.getValue(); + List visitors = visitorsByItem.get(itemName); + + if (visitors == null || visitors.isEmpty()) continue; + + for (Visitor visitor : visitors) { + int yPosition = Y_OFFSET + index * (LINE_HEIGHT + textRenderer.fontHeight); + + context.getMatrices().push(); + context.getMatrices().translate(X_OFFSET, yPosition + (float) textRenderer.fontHeight / 2 - ICON_SIZE * 0.95f / 2, 0); + context.getMatrices().scale(0.95f, 0.95f, 1.0f); + context.drawItem(visitor.head(), 0, 0); + context.getMatrices().pop(); + + context.drawText(textRenderer, visitor.name(), X_OFFSET + (int) (ICON_SIZE * 0.95f) + 4, yPosition, -1, true); + index++; + } + + int iconX = X_OFFSET + 12; + int textX = iconX + (int) (ICON_SIZE * 0.95f) + 4; + int yPosition = Y_OFFSET + index * (LINE_HEIGHT + textRenderer.fontHeight); + + ItemStack cachedStack = getCachedItem(itemName.getString()); + if (cachedStack != null) { + context.getMatrices().push(); + context.getMatrices().translate(iconX, yPosition + (float) textRenderer.fontHeight / 2 - ICON_SIZE * 0.95f / 2, 0); + context.getMatrices().scale(0.95f, 0.95f, 1.0f); + context.drawItem(cachedStack, 0, 0); + context.getMatrices().pop(); + } + + Text itemText = SkyblockerConfigManager.get().farming.visitorHelper.showStacksInVisitorHelper + ? cachedStack.getName().copy() + .append(" x" + (totalAmount / 64) + " stacks + " + (totalAmount % 64)) + : cachedStack.getName().copy() + .append(" x" + totalAmount); + + + int itemTextWidth = textRenderer.getWidth(itemText); + int copyTextX = textX + itemTextWidth; + + context.drawText(textRenderer, itemText, textX, yPosition, -1, true); + context.drawText(textRenderer, Text.literal(" [Copy Amount]").setStyle(Style.EMPTY.withColor(Formatting.YELLOW)), copyTextX, yPosition, -1, true); + + index++; + } + + context.getMatrices().pop(); + } + + /** + * Handles mouse click events on the visitor UI. + */ + public static void handleMouseClick(double mouseX, double mouseY, int mouseButton, TextRenderer textRenderer) { + if (mouseButton != 0) return; + + int index = 0; + int yOffsetAdjustment = -5; + + Map groupedItems = new LinkedHashMap<>(); + Map> visitorsByItem = new LinkedHashMap<>(); + + for (Visitor visitor : activeVisitors.keySet()) { + for (Map.Entry entry : visitor.requiredItems().entrySet()) { + Text itemName = entry.getKey(); + int amount = entry.getValue(); + + groupedItems.put(itemName, groupedItems.getOrDefault(itemName, 0) + amount); + visitorsByItem.computeIfAbsent(itemName, k -> new LinkedList<>()).add(visitor); + } + } + + for (Map.Entry entry : groupedItems.entrySet()) { + Text itemName = entry.getKey(); + int totalAmount = entry.getValue(); + List visitors = visitorsByItem.get(itemName); + + if (visitors != null && !visitors.isEmpty()) { + for (Visitor ignored : visitors) { + index++; + } + + int iconX = X_OFFSET + 12; + int textX = iconX + (int) (ICON_SIZE * 0.95f) + 4; + int yPosition = Y_OFFSET + index * (LINE_HEIGHT + textRenderer.fontHeight) - + (int) ((float) textRenderer.fontHeight / 2 - ICON_SIZE * 0.95f / 2) + yOffsetAdjustment; + + Text itemText = SkyblockerConfigManager.get().farming.visitorHelper.showStacksInVisitorHelper + ? itemName.copy() + .append(" x" + (totalAmount / 64) + " stacks + " + (totalAmount % 64)) + : itemName.copy() + .append(" x" + totalAmount); + + int itemTextWidth = textRenderer.getWidth(itemText); + int copyTextX = textX + itemTextWidth; + + if (isMouseOverText(mouseX, mouseY, textX, yPosition, itemTextWidth, textRenderer.fontHeight)) { + MessageScheduler.INSTANCE.sendMessageAfterCooldown("/bz " + itemName.getString(), true); + return; + } + + if (isMouseOverText(mouseX, mouseY, copyTextX, yPosition, textRenderer.getWidth(" [Copy Amount]"), textRenderer.fontHeight)) { + MinecraftClient.getInstance().keyboard.setClipboard(String.valueOf(totalAmount)); + MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get().append("Copied amount successfully"), false); + return; + } + + index++; + } + } + } + + /** + * Handles slot clicks to remove a visitor when certain conditions are met. + * + * @param title The visitor's name to match for removal. + */ + public static void onSlotClick(Slot slot, int slotId, String title) { + if ((slotId == 29 || slotId == 13 || slotId == 33) && slot.hasStack() && + ItemUtils.getLoreLineIf(slot.getStack(), s -> s.equals("Click to give!") || s.equals("Click to refuse!")) != null) { + + Visitor visitorToRemove = null; + + for (Visitor visitor : activeVisitors.keySet()) { + if (visitor.name().getString().equals(title)) { + visitorToRemove = visitor; + break; + } + } + + if (visitorToRemove != null) { + activeVisitors.remove(visitorToRemove); + } + } + } + + /** + * Checks if the mouse is over a specific rectangular region. + */ + private static boolean isMouseOverText(double mouseX, double mouseY, int x, int y, int width, int height) { + return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java index ae6d64c451..0ac20e0cb8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java @@ -3,6 +3,7 @@ import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import net.minecraft.client.gui.DrawContext; import net.minecraft.item.ItemStack; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index b886f40fc3..b5d8b834e9 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -31,6 +31,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; import net.minecraft.registry.Registries; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.text.Text; @@ -475,4 +476,4 @@ public static Matcher getLoreLineIfContainsMatch(ItemStack stack, Pattern patter } return stringBuilder.toString(); } -} \ No newline at end of file +} diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index c51a097e92..d2358f189b 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -128,6 +128,10 @@ public static boolean isInTheRift() { return location == Location.THE_RIFT; } + public static boolean isOnGarden() { + return location == Location.GARDEN; + } + /** * @return if the player is in the end island */ diff --git a/src/main/java/de/hysky/skyblocker/utils/mayor/MayorUtils.java b/src/main/java/de/hysky/skyblocker/utils/mayor/MayorUtils.java index ffe3594cbe..2ac1b92645 100644 --- a/src/main/java/de/hysky/skyblocker/utils/mayor/MayorUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/mayor/MayorUtils.java @@ -92,10 +92,17 @@ private static void tickMayorCache() { } try { JsonObject ministerObject = result.getAsJsonObject("minister"); - JsonObject ministerPerk = ministerObject.getAsJsonObject("perk"); - minister = new Minister(ministerObject.get("key").getAsString(), - ministerObject.get("name").getAsString(), - new Perk(ministerPerk.get("name").getAsString(), ministerPerk.get("description").getAsString())); + if (ministerObject != null) { // Check if ministerObject is not null stops NPE caused by Derpy + JsonObject ministerPerk = ministerObject.getAsJsonObject("perk"); + minister = new Minister( + ministerObject.get("key").getAsString(), + ministerObject.get("name").getAsString(), + new Perk(ministerPerk.get("name").getAsString(), ministerPerk.get("description").getAsString()) + ); + } else { + LOGGER.info("[Skyblocker] No minister data found for the current mayor."); + minister = Minister.EMPTY; + } } catch (Exception e) { LOGGER.warn("[Skyblocker] Failed to parse minister status from the API response. This might be due to a special mayor, in which case there are no ministers.", e); minister = Minister.EMPTY; diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java index ca7d021f81..1f6d480152 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java @@ -94,7 +94,7 @@ private static void renderFilledInternal(WorldRenderContext context, double minX matrices.pop(); } - private static void renderBeaconBeam(WorldRenderContext context, BlockPos pos, float[] colorComponents) { + public static void renderBeaconBeam(WorldRenderContext context, BlockPos pos, float[] colorComponents) { if (FrustumUtils.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, MAX_OVERWORLD_BUILD_HEIGHT, pos.getZ() + 1)) { MatrixStack matrices = context.matrixStack(); Vec3d camera = context.camera().getPos(); diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 03bbce37a0..247ba3caea 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -240,13 +240,18 @@ "skyblocker.config.farming.garden.gardenPlotsWidget.@Tooltip": "While in the garden, on the right of your inventory there will be a widget to quickly teleport to plots. It will also show plots that have pests (this requires the Pests widget to be enabled and visible in the Tab).", "skyblocker.config.farming.garden.lockMouseGround": "Only lock camera when on the ground", "skyblocker.config.farming.garden.lockMouseTool": "Lock camera when holding a farming tool", - "skyblocker.config.farming.garden.visitorHelper": "Visitor helper", - "skyblocker.config.farming.garden.visitorHelper.@Tooltip": "Makes it easier to manage visitors by grouping similar requests, copying item amounts, and quickly opening the Bazaar to buy items.", - "skyblocker.config.farming.garden.visitorHelperGardenOnly": "Visitor helper (Garden Only)", - "skyblocker.config.farming.garden.visitorHelperGardenOnly.@Tooltip": "Locks the Visitor Helper so it only works on the Garden.\n§cRequires Visitor Helper to be enabled.", - "skyblocker.config.farming.garden.showStacksInVisitorHelper": "Show Stacks in Visitor Helper", - "skyblocker.config.farming.garden.showStacksInVisitorHelper.@Tooltip": "Show's the required items as (5 stacks + 4 [324]) rather than just a total number.", - + "skyblocker.config.farming.garden.pestHighlighter": "Pest Highlighter", + "skyblocker.config.farming.garden.pestHighlighter.@Tooltip": "Make's all pest's on your island glow so you can see them easier :D", + "skyblocker.config.farming.garden.vacuumSolver": "Vacuum Solver", + "skyblocker.config.farming.garden.vacuumSolver.@Tooltip": "Calculates and adds a waypoint at the location indicated by InfiniVacuum.", + + "skyblocker.config.farming.visitorHelper": "Visitor Helper", + "skyblocker.config.farming.visitorHelper.visitorHelper": "Enable Visitor Helper", + "skyblocker.config.farming.visitorHelper.visitorHelper.@Tooltip": "Makes it easier to manage visitors by grouping similar requests, copying item amounts, and quickly opening the Bazaar to buy items.", + "skyblocker.config.farming.visitorHelper.visitorHelperGardenOnly": "Garden Only", + "skyblocker.config.farming.visitorHelper.visitorHelperGardenOnly.@Tooltip": "Locks the Visitor Helper so it only works on the Garden.\n§cRequires Visitor Helper to be enabled.", + "skyblocker.config.farming.visitorHelper.showStacksInVisitorHelper": "Show Stacks in Visitor Helper", + "skyblocker.config.farming.visitorHelper.showStacksInVisitorHelper.@Tooltip": "Show's the required items as (5 stacks + 45) rather than just a total number especially useful for ironman players.", "skyblocker.config.foraging": "Foraging", "skyblocker.config.foraging.hunting": "Hunting", From 8d85dfb71e758f1db7d965da2241aba5f89db344 Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Sun, 5 Jan 2025 21:34:38 -0600 Subject: [PATCH 05/10] Forgot to fix highlight flicker in VacuumSolver --- .../skyblock/garden/VacuumSolver.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/VacuumSolver.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/VacuumSolver.java index 821b98dea0..a5c08b8a43 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/VacuumSolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/VacuumSolver.java @@ -45,7 +45,7 @@ private static void reset() { } public static void onParticle(ParticleS2CPacket packet) { - if (!Utils.isOnGarden() || SkyblockerConfigManager.get().farming.garden.vacuumSolver || !ParticleTypes.ANGRY_VILLAGER.equals(packet.getParameters().getType())) { + if (!Utils.isOnGarden() || !SkyblockerConfigManager.get().farming.garden.vacuumSolver || !ParticleTypes.ANGRY_VILLAGER.equals(packet.getParameters().getType())) { return; } @@ -102,11 +102,17 @@ private static void linkMarkerWithDestination() { } private static void renderTrail(WorldRenderContext context) { - if (fixedDestination == null) return; + if (fixedDestination == null) { + return; + } float[] color = {1f, 0f, 0f}; - RenderHelper.renderFilled(context, new BlockPos((int) fixedDestination.getX(), (int) fixedDestination.getY(), (int) fixedDestination.getZ()), color, 2.0f, false); + RenderHelper.renderFilled(context, new BlockPos( + (int) fixedDestination.getX(), + (int) fixedDestination.getY(), + (int) fixedDestination.getZ() + ), color, 2.0f, false); linkedMarkers.entrySet().removeIf(entry -> entry.getKey().isRemoved()); @@ -114,9 +120,12 @@ private static void renderTrail(WorldRenderContext context) { RenderHelper.renderFilled(context, pos, color, 2.0f, false); } - if (linkedMarkers.isEmpty()) { - fixedDestination = null; + if (linkedMarkers.isEmpty() && fixedDestination != null) { + RenderHelper.renderFilled(context, new BlockPos( + (int) fixedDestination.getX(), + (int) fixedDestination.getY(), + (int) fixedDestination.getZ() + ), color, 2.0f, false); } } - } From 7c3edc065a208fd686d6d4bf2a10e44b4512981a Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Sun, 5 Jan 2025 21:39:04 -0600 Subject: [PATCH 06/10] removed imports from MBB --- .../de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java index 75403dc511..d5081668e4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobBoundingBoxes.java @@ -14,8 +14,6 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.minecraft.entity.Entity; import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.entity.mob.SilverfishEntity; -import net.minecraft.entity.passive.BatEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.math.Box; From fbe41b84c1a9cf723540dd0412a9773bf31f494f Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Sun, 5 Jan 2025 21:40:21 -0600 Subject: [PATCH 07/10] . --- src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java index 9d0d7440ed..d173f69d63 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java @@ -167,7 +167,7 @@ private static int computeMobGlow(Entity entity) { // Enderman Slayer's Nukekubi Skulls case ArmorStandEntity armorStand when SkyblockerConfigManager.get().slayers.endermanSlayer.highlightNukekubiHeads && Utils.isInTheEnd() && armorStand.isMarker() && SlayerManager.isInSlayer() && isNukekubiHead(armorStand) -> 0x990099; - // pests + // Pests case ArmorStandEntity armorStand when SkyblockerConfigManager.get().farming.garden.pestHighlighter && Utils.isOnGarden() && isPestHead(armorStand) -> 0xb62f00; // Blaze Slayer's Demonic minions From ec6e207d6b147bed6a745adfea9c66d30434db02 Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Sun, 5 Jan 2025 21:46:22 -0600 Subject: [PATCH 08/10] . --- src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java | 1 - .../skyblock/tabhud/widget/component/ProgressComponent.java | 1 - src/main/java/de/hysky/skyblocker/utils/ItemUtils.java | 1 - 3 files changed, 3 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java index d173f69d63..2d36d05c8f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java @@ -221,7 +221,6 @@ private static boolean isNukekubiHead(ArmorStandEntity entity) { /** * Compares the armor items of an armor stand to the Pest head texture to determine if it is a Pest head. */ - private static boolean isPestHead(ArmorStandEntity entity) { return Streams.stream(entity.getArmorItems()) .map(ItemUtils::getHeadTexture) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java index 0ac20e0cb8..ae6d64c451 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/ProgressComponent.java @@ -3,7 +3,6 @@ import de.hysky.skyblocker.skyblock.tabhud.util.Ico; import net.minecraft.client.gui.DrawContext; import net.minecraft.item.ItemStack; -import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index b5d8b834e9..ad74865766 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -31,7 +31,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtList; import net.minecraft.registry.Registries; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.text.Text; From 834e35411463bc4ae339041c730d3250921d1e95 Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Sun, 5 Jan 2025 23:38:11 -0600 Subject: [PATCH 09/10] Updated Regex & MayorUtils catch block --- .../java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java | 5 +++-- .../java/de/hysky/skyblocker/utils/mayor/MayorUtils.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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 0264add07e..c546dae449 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/FarmingHud.java @@ -20,6 +20,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; +import net.minecraft.util.Formatting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,7 +38,7 @@ public class FarmingHud { private static final Logger LOGGER = LoggerFactory.getLogger(FarmingHud.class); public static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US); - private static final Pattern FARMING_XP = Pattern.compile("§3\\+(?\\d+(?:\\.\\d+)?) Farming \\((?[\\d,]+(?:\\.\\d+)?%|[\\d,]+/[\\d,]+)\\)"); + private static final Pattern FARMING_XP = Pattern.compile("\\+(?\\d+(?:\\.\\d+)?) Farming \\((?[\\d,]+(?:\\.\\d+)?%|[\\d,]+/[\\d,]+)\\)"); private static final MinecraftClient client = MinecraftClient.getInstance(); private static CounterType counterType = CounterType.NONE; private static final Deque counter = new ArrayDeque<>(); @@ -73,7 +74,7 @@ public static void init() { }); ClientReceiveMessageEvents.GAME.register((message, overlay) -> { if (shouldRender() && overlay) { - Matcher matcher = FARMING_XP.matcher(message.getString()); + Matcher matcher = FARMING_XP.matcher(Formatting.strip(message.getString())); if (matcher.matches()) { try { farmingXp.offer(FloatLongPair.of(NUMBER_FORMAT.parse(matcher.group("xp")).floatValue(), System.currentTimeMillis())); diff --git a/src/main/java/de/hysky/skyblocker/utils/mayor/MayorUtils.java b/src/main/java/de/hysky/skyblocker/utils/mayor/MayorUtils.java index 2ac1b92645..6cf75a81a5 100644 --- a/src/main/java/de/hysky/skyblocker/utils/mayor/MayorUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/mayor/MayorUtils.java @@ -104,7 +104,7 @@ private static void tickMayorCache() { minister = Minister.EMPTY; } } catch (Exception e) { - LOGGER.warn("[Skyblocker] Failed to parse minister status from the API response. This might be due to a special mayor, in which case there are no ministers.", e); + LOGGER.warn("[Skyblocker] Failed to parse minister status from the API response.", e); minister = Minister.EMPTY; } LOGGER.info("[Skyblocker] Mayor set to {}, minister set to {}.", mayor, minister); From e140181169426236f583bc3d563ea6ddb44f5c10 Mon Sep 17 00:00:00 2001 From: WannaBeIan Date: Mon, 6 Jan 2025 11:08:28 -0600 Subject: [PATCH 10/10] Forgot to add the visitor head and name coloring back into the render method during some tests --- .../garden/visitorhelper/VisitorHelper.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/VisitorHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/VisitorHelper.java index 9288d7213b..7a1c7286e6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/VisitorHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/garden/visitorhelper/VisitorHelper.java @@ -115,16 +115,23 @@ private static ItemStack getCachedItem(String itemName) { * Draws the visitor items and their associated information. */ private static void drawVisitorItems(DrawContext context, TextRenderer textRenderer) { + int index = 0; + + // Map of grouped items with their total amount and associated visitors Map groupedItems = new LinkedHashMap<>(); Map> visitorsByItem = new LinkedHashMap<>(); - activeVisitors.keySet().forEach(visitor -> - visitor.requiredItems().forEach((itemName, amount) -> { - groupedItems.put(itemName, groupedItems.getOrDefault(itemName, 0) + amount); - visitorsByItem.computeIfAbsent(itemName, k -> new LinkedList<>()).add(visitor); - }) - ); + // Group items by their name and accumulate their counts + for (Visitor visitor : activeVisitors.keySet()) { + for (Map.Entry entry : visitor.requiredItems().entrySet()) { + Text itemName = entry.getKey(); + int amount = entry.getValue(); + + groupedItems.put(itemName, groupedItems.getOrDefault(itemName, 0) + amount); + visitorsByItem.computeIfAbsent(itemName, k -> new LinkedList<>()).add(visitor); + } + } context.getMatrices().push(); context.getMatrices().translate(0, 0, 200); @@ -136,6 +143,7 @@ private static void drawVisitorItems(DrawContext context, TextRenderer textRende if (visitors == null || visitors.isEmpty()) continue; + // Render visitors' heads for the shared item for (Visitor visitor : visitors) { int yPosition = Y_OFFSET + index * (LINE_HEIGHT + textRenderer.fontHeight); @@ -146,9 +154,11 @@ private static void drawVisitorItems(DrawContext context, TextRenderer textRende context.getMatrices().pop(); context.drawText(textRenderer, visitor.name(), X_OFFSET + (int) (ICON_SIZE * 0.95f) + 4, yPosition, -1, true); + index++; } + // Render the shared item with the total amount int iconX = X_OFFSET + 12; int textX = iconX + (int) (ICON_SIZE * 0.95f) + 4; int yPosition = Y_OFFSET + index * (LINE_HEIGHT + textRenderer.fontHeight); @@ -168,7 +178,6 @@ private static void drawVisitorItems(DrawContext context, TextRenderer textRende : cachedStack.getName().copy() .append(" x" + totalAmount); - int itemTextWidth = textRenderer.getWidth(itemText); int copyTextX = textX + itemTextWidth; @@ -181,6 +190,7 @@ private static void drawVisitorItems(DrawContext context, TextRenderer textRende context.getMatrices().pop(); } + /** * Handles mouse click events on the visitor UI. */