From 70d8113a1cd3ea7a5b54fff6f8bdd7e60ac12e1f Mon Sep 17 00:00:00 2001 From: RettichLP Date: Thu, 28 May 2026 12:16:10 +0200 Subject: [PATCH 1/7] Rename FisherListener to DeepSeaFisherListener and update related logic and patterns --- .../impl/job/DeepSeaFisherListener.java | 138 ++++++++++++++++++ .../listener/impl/job/FisherListener.java | 138 ------------------ 2 files changed, 138 insertions(+), 138 deletions(-) create mode 100644 src/main/java/de/rettichlp/ucutils/listener/impl/job/DeepSeaFisherListener.java delete mode 100644 src/main/java/de/rettichlp/ucutils/listener/impl/job/FisherListener.java diff --git a/src/main/java/de/rettichlp/ucutils/listener/impl/job/DeepSeaFisherListener.java b/src/main/java/de/rettichlp/ucutils/listener/impl/job/DeepSeaFisherListener.java new file mode 100644 index 00000000..c520b8e8 --- /dev/null +++ b/src/main/java/de/rettichlp/ucutils/listener/impl/job/DeepSeaFisherListener.java @@ -0,0 +1,138 @@ +package de.rettichlp.ucutils.listener.impl.job; + +import de.rettichlp.ucutils.common.registry.UCUtilsListener; +import de.rettichlp.ucutils.listener.IMessageReceiveListener; +import de.rettichlp.ucutils.listener.INaviSpotReachedListener; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static de.rettichlp.ucutils.UCUtils.commandService; +import static de.rettichlp.ucutils.UCUtils.player; +import static java.lang.Double.compare; +import static java.util.Arrays.stream; +import static java.util.regex.Pattern.compile; + +@UCUtilsListener +public class DeepSeaFisherListener implements IMessageReceiveListener, INaviSpotReachedListener { + + private static final Pattern DEEP_SEA_FISHER_START_PATTERN = compile("^\\[Fischer] Mit /findschwarm kannst du dir den nächsten Fischschwarm anzeigen lassen\\.$"); + private static final Pattern DEEP_SEA_FISHER_SPOT_FOUND_PATTERN = compile("^\\[Fischer] Du hast einen Fischschwarm gefunden!$"); + private static final Pattern DEEP_SEA_FISHER_SPOT_LOST_PATTERN = compile("^\\[Fischer] Du hast dich dem Fischschwarm zu weit entfernt\\.$"); + private static final Pattern DEEP_SEA_FISHER_CATCH_SUCCESS_PATTERN = compile("^\\[Fischer] Du hast \\d+kg frischen Fisch gefangen! \\(\\d+kg\\)$"); + private static final Pattern DEEP_SEA_FISHER_CATCH_FAILURE_PATTERN = compile("^\\[Fischer] Du hast das Fischernetz verloren\\.\\.\\.$"); + private static final int NET_AMOUNT = 5; + + private Collection visitedDeepSeaFisherJobSpots = new ArrayList<>(); + private boolean onDeepSeaFisherSpot = false; + private boolean canCatchFish = true; + + @Override + public boolean onMessageReceive(Text text, String message) { + Matcher deepSeaFisherStartMatcher = DEEP_SEA_FISHER_START_PATTERN.matcher(message); + if (deepSeaFisherStartMatcher.find()) { + this.visitedDeepSeaFisherJobSpots = new ArrayList<>(); + DeepSeaFisherJobSpot.SPOT_1.startNavigation(); + return true; + } + + Matcher deepSeaFisherSpotFoundMatcher = DEEP_SEA_FISHER_SPOT_FOUND_PATTERN.matcher(message); + if (deepSeaFisherSpotFoundMatcher.find()) { + commandService.sendCommand("stoproute"); + this.onDeepSeaFisherSpot = true; + + if (this.canCatchFish) { + startFishing(); + } + + return true; + } + + Matcher deepSeaFisherSpotLostMatcher = DEEP_SEA_FISHER_SPOT_LOST_PATTERN.matcher(message); + if (deepSeaFisherSpotLostMatcher.find()) { + this.onDeepSeaFisherSpot = false; + return true; + } + + Matcher deepSeaFisherCatchSuccessMatcher = DEEP_SEA_FISHER_CATCH_SUCCESS_PATTERN.matcher(message); + Matcher deepSeaFisherCatchFailureMatcher = DEEP_SEA_FISHER_CATCH_FAILURE_PATTERN.matcher(message); + if (deepSeaFisherCatchSuccessMatcher.find() || deepSeaFisherCatchFailureMatcher.find()) { + this.canCatchFish = true; + + // if already on the next fishing spot, start fishing again + if (this.onDeepSeaFisherSpot) { + startFishing(); + } + } + + return true; + } + + @Override + public void onNaviSpotReached() { + if (this.visitedDeepSeaFisherJobSpots.size() == NET_AMOUNT) { + this.visitedDeepSeaFisherJobSpots = new ArrayList<>(); + commandService.sendCommand("dropfish"); + } + } + + private void startFishing() { + this.onDeepSeaFisherSpot = false; + this.canCatchFish = false; + commandService.sendCommand("catchfish"); + + // add the current spot to visited spots + getNearestFisherJobSpot(getNotVisitedFisherJobSpots()).ifPresent(this.visitedDeepSeaFisherJobSpots::add); + + if (this.visitedDeepSeaFisherJobSpots.size() == NET_AMOUNT) { + return; + } + + // get nearest next spot and start navigation + Optional nearestNextFisherJobSpot = getNearestFisherJobSpot(getNotVisitedFisherJobSpots()); + nearestNextFisherJobSpot.ifPresent(DeepSeaFisherJobSpot::startNavigation); + } + + private @NotNull Optional getNearestFisherJobSpot(@NotNull Collection deepSeaFisherJobSpots) { + return deepSeaFisherJobSpots.stream() + .min((spot1, spot2) -> { + double distance1 = player.squaredDistanceTo(spot1.getPosition().getX(), spot1.getPosition().getY(), spot1.getPosition().getZ()); + double distance2 = player.squaredDistanceTo(spot2.getPosition().getX(), spot2.getPosition().getY(), spot2.getPosition().getZ()); + return compare(distance1, distance2); + }); + } + + private @NotNull @Unmodifiable List getNotVisitedFisherJobSpots() { + return stream(DeepSeaFisherJobSpot.values()) + .filter(deepSeaFisherJobSpot -> !this.visitedDeepSeaFisherJobSpots.contains(deepSeaFisherJobSpot)) + .toList(); + } + + @Getter + @AllArgsConstructor + private enum DeepSeaFisherJobSpot { + + SPOT_1(new BlockPos(-571, 63, 160)), + SPOT_2(new BlockPos(-554, 63, 107)), + SPOT_3(new BlockPos(-568, 63, 50)), + SPOT_4(new BlockPos(-522, 63, 10)), + SPOT_5(new BlockPos(-521, 63, 78)); + + private final BlockPos position; + + public void startNavigation() { + String naviCommandString = "navi " + this.position.getX() + "/" + this.position.getY() + "/" + this.position.getZ(); + commandService.sendCommand(naviCommandString); + } + } +} diff --git a/src/main/java/de/rettichlp/ucutils/listener/impl/job/FisherListener.java b/src/main/java/de/rettichlp/ucutils/listener/impl/job/FisherListener.java deleted file mode 100644 index e4c211bc..00000000 --- a/src/main/java/de/rettichlp/ucutils/listener/impl/job/FisherListener.java +++ /dev/null @@ -1,138 +0,0 @@ -package de.rettichlp.ucutils.listener.impl.job; - -import de.rettichlp.ucutils.common.registry.UCUtilsListener; -import de.rettichlp.ucutils.listener.IMessageReceiveListener; -import de.rettichlp.ucutils.listener.INaviSpotReachedListener; -import lombok.AllArgsConstructor; -import lombok.Getter; -import net.minecraft.text.Text; -import net.minecraft.util.math.BlockPos; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Unmodifiable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static de.rettichlp.ucutils.UCUtils.commandService; -import static de.rettichlp.ucutils.UCUtils.player; -import static java.lang.Double.compare; -import static java.util.Arrays.stream; -import static java.util.regex.Pattern.compile; - -@UCUtilsListener -public class FisherListener implements IMessageReceiveListener, INaviSpotReachedListener { - - private static final Pattern FISHER_START = compile("^\\[Fischer] Mit /findschwarm kannst du dir den nächsten Fischschwarm anzeigen lassen\\.$"); - private static final Pattern FISHER_SPOT_FOUND_PATTERN = compile("^\\[Fischer] Du hast einen Fischschwarm gefunden!$"); - private static final Pattern FISHER_SPOT_LOST_PATTERN = compile("^\\[Fischer] Du hast dich dem Fischschwarm zu weit entfernt\\.$"); - private static final Pattern FISHER_CATCH_SUCCESS = compile("^\\[Fischer] Du hast \\d+kg frischen Fisch gefangen! \\(\\d+kg\\)$"); - private static final Pattern FISHER_CATCH_FAILURE = compile("^\\[Fischer] Du hast das Fischernetz verloren\\.\\.\\.$"); - private static final int NET_AMOUNT = 5; - - private Collection visitedFisherJobSpots = new ArrayList<>(); - private boolean onFisherSpot = false; - private boolean canCatchFish = true; - - @Override - public boolean onMessageReceive(Text text, String message) { - Matcher fisherStartMatcher = FISHER_START.matcher(message); - if (fisherStartMatcher.find()) { - this.visitedFisherJobSpots = new ArrayList<>(); - FisherJobSpot.SPOT_1.startNavigation(); - return true; - } - - Matcher fisherSpotFoundMatcher = FISHER_SPOT_FOUND_PATTERN.matcher(message); - if (fisherSpotFoundMatcher.find()) { - commandService.sendCommand("stoproute"); - this.onFisherSpot = true; - - if (this.canCatchFish) { - startFishing(); - } - - return true; - } - - Matcher fisherSpotLostMatcher = FISHER_SPOT_LOST_PATTERN.matcher(message); - if (fisherSpotLostMatcher.find()) { - this.onFisherSpot = false; - return true; - } - - Matcher fisherCatchSuccessMatcher = FISHER_CATCH_SUCCESS.matcher(message); - Matcher fisherCatchFailureMatcher = FISHER_CATCH_FAILURE.matcher(message); - if (fisherCatchSuccessMatcher.find() || fisherCatchFailureMatcher.find()) { - this.canCatchFish = true; - - // if already on the next fishing spot, start fishing again - if (this.onFisherSpot) { - startFishing(); - } - } - - return true; - } - - @Override - public void onNaviSpotReached() { - if (this.visitedFisherJobSpots.size() == NET_AMOUNT) { - this.visitedFisherJobSpots = new ArrayList<>(); - commandService.sendCommand("dropfish"); - } - } - - private void startFishing() { - this.onFisherSpot = false; - this.canCatchFish = false; - commandService.sendCommand("catchfish"); - - // add the current spot to visited spots - getNearestFisherJobSpot(getNotVisitedFisherJobSpots()).ifPresent(this.visitedFisherJobSpots::add); - - if (this.visitedFisherJobSpots.size() == NET_AMOUNT) { - return; - } - - // get nearest next spot and start navigation - Optional nearestNextFisherJobSpot = getNearestFisherJobSpot(getNotVisitedFisherJobSpots()); - nearestNextFisherJobSpot.ifPresent(FisherJobSpot::startNavigation); - } - - private @NotNull Optional getNearestFisherJobSpot(@NotNull Collection fisherJobSpots) { - return fisherJobSpots.stream() - .min((spot1, spot2) -> { - double distance1 = player.squaredDistanceTo(spot1.getPosition().getX(), spot1.getPosition().getY(), spot1.getPosition().getZ()); - double distance2 = player.squaredDistanceTo(spot2.getPosition().getX(), spot2.getPosition().getY(), spot2.getPosition().getZ()); - return compare(distance1, distance2); - }); - } - - private @NotNull @Unmodifiable List getNotVisitedFisherJobSpots() { - return stream(FisherJobSpot.values()) - .filter(fisherJobSpot -> !this.visitedFisherJobSpots.contains(fisherJobSpot)) - .toList(); - } - - @Getter - @AllArgsConstructor - private enum FisherJobSpot { - - SPOT_1(new BlockPos(-571, 63, 160)), - SPOT_2(new BlockPos(-554, 63, 107)), - SPOT_3(new BlockPos(-568, 63, 50)), - SPOT_4(new BlockPos(-522, 63, 10)), - SPOT_5(new BlockPos(-521, 63, 78)); - - private final BlockPos position; - - public void startNavigation() { - String naviCommandString = "navi " + this.position.getX() + "/" + this.position.getY() + "/" + this.position.getZ(); - commandService.sendCommand(naviCommandString); - } - } -} From 51f3e9d06f910fbe20c5060c0a6d916f8917a8e0 Mon Sep 17 00:00:00 2001 From: RettichLP Date: Thu, 28 May 2026 12:29:13 +0200 Subject: [PATCH 2/7] Add AnglerListener to handle anti-bot fishing captcha detection and interaction logic --- .../de/rettichlp/ucutils/common/Storage.java | 8 ++ .../listener/impl/job/AnglerListener.java | 87 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java diff --git a/src/main/java/de/rettichlp/ucutils/common/Storage.java b/src/main/java/de/rettichlp/ucutils/common/Storage.java index 492152b5..b9b9ce3e 100644 --- a/src/main/java/de/rettichlp/ucutils/common/Storage.java +++ b/src/main/java/de/rettichlp/ucutils/common/Storage.java @@ -16,6 +16,7 @@ import net.minecraft.util.math.Vec3d; import org.jetbrains.annotations.Nullable; +import java.awt.image.BufferedImage; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; @@ -64,6 +65,11 @@ public class Storage { @Nullable private Vec3d blackMarketPosition; + @Getter + @Setter + @Nullable + private BufferedImage captchaMapImage; + @Getter @Setter private boolean carLocked = true; @@ -119,6 +125,8 @@ public void print() { LOGGER.info("activeShutdowns[{}]: {}", this.activeShutdowns.size(), this.activeShutdowns); // blackMarketPosition LOGGER.info("blackMarketPosition: {}", this.blackMarketPosition); + // captchaMapImage + LOGGER.info("captchaMapImage: {}", this.captchaMapImage); // countdowns LOGGER.info("countdowns[{}]: {}", this.countdowns.size(), this.countdowns); // dealerPosition diff --git a/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java b/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java new file mode 100644 index 00000000..7be28ebd --- /dev/null +++ b/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java @@ -0,0 +1,87 @@ +package de.rettichlp.ucutils.listener.impl.job; + +import de.rettichlp.ucutils.common.registry.UCUtilsListener; +import de.rettichlp.ucutils.listener.IMessageReceiveListener; +import lombok.NonNull; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.component.type.MapIdComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.item.map.MapState; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.awt.image.BufferedImage; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static de.rettichlp.ucutils.UCUtils.player; +import static de.rettichlp.ucutils.UCUtils.storage; +import static java.awt.image.BufferedImage.TYPE_INT_RGB; +import static java.util.regex.Pattern.compile; +import static net.minecraft.block.MapColor.getRenderColor; +import static net.minecraft.component.DataComponentTypes.MAP_ID; +import static net.minecraft.item.FilledMapItem.getMapState; +import static net.minecraft.item.Items.FILLED_MAP; +import static net.minecraft.util.Hand.MAIN_HAND; + +@UCUtilsListener +public class AnglerListener implements IMessageReceiveListener { + + private static final Pattern ANGLER_CAPTCHA_PATTERN = compile("^\\[AntiBot] Zufällige Überprüfung$"); + private static final Pattern ANGLER_CAPTCHA_CONTINUED_PATTERN = compile("^Deine Combo wurde fortgesetzt! \\(\\d+\\)$"); + + @Override + public boolean onMessageReceive(Text text, String message) { + MinecraftClient client = MinecraftClient.getInstance(); + + Matcher anglerCaptchaMatcher = ANGLER_CAPTCHA_PATTERN.matcher(message); + if (anglerCaptchaMatcher.find()) { + ItemStack offHandStack = player.getOffHandStack(); + if (!offHandStack.isOf(FILLED_MAP)) { + return true; + } + + storage.setCaptchaMapImage(getMapImage(offHandStack)); + return true; + } + + Matcher anglerCaptchaContinuedMatcher = ANGLER_CAPTCHA_CONTINUED_PATTERN.matcher(message); + if (anglerCaptchaContinuedMatcher.find()) { + ClientPlayerInteractionManager interactionManager = client.interactionManager; + if (interactionManager == null) { + return true; + } + + interactionManager.interactItem(player, MAIN_HAND); + player.swingHand(MAIN_HAND); + return true; + } + + return true; + } + + private @Nullable BufferedImage getMapImage(@NonNull ItemStack mapItemStack) { + MapIdComponent mapId = mapItemStack.get(MAP_ID); + if (mapId == null) { + return null; + } + + MapState mapState = getMapState(mapId, MinecraftClient.getInstance().world); + if (mapState == null) { + return null; + } + + BufferedImage mapImage = new BufferedImage(128, 128, TYPE_INT_RGB); + + for (int i = 0; i < mapState.colors.length; i++) { + int x = i % 128; + int y = i / 128; + + int argb = getRenderColor(mapState.colors[i]); + mapImage.setRGB(x, y, argb); + } + + return mapImage; + } +} From e4f34b58b141a00e1dcdc90531d58ad8e58f3f96 Mon Sep 17 00:00:00 2001 From: RettichLP Date: Thu, 28 May 2026 12:30:54 +0200 Subject: [PATCH 3/7] Update README: display fishing captcha centered and auto-cast after solving --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d7cb4435..85cbcb84 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ Folgende Funktionen sind nur für Spieler ohne Premium-Rang verfügbar, da Spiel - Für den Pizzalieferanten-Job wird `/getpizza` automatisch ausgeführt, bis 10 Pizzen gesammelt wurden - Es werden Countdowns angezeigt, bis ein Job wieder ausgeführt werden kann - Aktive Mining XP-Booster werden angezeigt +- Das Angel-Captcha wird zusätzlich in der Mitte des Bildschirms angezeigt, damit es nicht vom Chat verdeckt wird +- Nach der erfolgreichen Eingabe des Angel-Captchas wird die Angel automatisch wieder ausgeworfen ### Widgets From 32e503ec68428d6818585d5b87434da04c173b4b Mon Sep 17 00:00:00 2001 From: RettichLP Date: Thu, 28 May 2026 12:52:49 +0200 Subject: [PATCH 4/7] Refactor AnglerListener and Storage: replace `captchaMapImage` with `captchaMap` for handling map identifiers --- src/main/java/de/rettichlp/ucutils/common/Storage.java | 6 +++--- .../rettichlp/ucutils/listener/impl/job/AnglerListener.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/rettichlp/ucutils/common/Storage.java b/src/main/java/de/rettichlp/ucutils/common/Storage.java index b9b9ce3e..5d83faca 100644 --- a/src/main/java/de/rettichlp/ucutils/common/Storage.java +++ b/src/main/java/de/rettichlp/ucutils/common/Storage.java @@ -11,12 +11,12 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import net.minecraft.component.type.MapIdComponent; import net.minecraft.entity.vehicle.MinecartEntity; import net.minecraft.text.Text; import net.minecraft.util.math.Vec3d; import org.jetbrains.annotations.Nullable; -import java.awt.image.BufferedImage; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; @@ -68,7 +68,7 @@ public class Storage { @Getter @Setter @Nullable - private BufferedImage captchaMapImage; + private MapIdComponent captchaMap; @Getter @Setter @@ -126,7 +126,7 @@ public void print() { // blackMarketPosition LOGGER.info("blackMarketPosition: {}", this.blackMarketPosition); // captchaMapImage - LOGGER.info("captchaMapImage: {}", this.captchaMapImage); + LOGGER.info("captchaMap: {}", this.captchaMap); // countdowns LOGGER.info("countdowns[{}]: {}", this.countdowns.size(), this.countdowns); // dealerPosition diff --git a/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java b/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java index 7be28ebd..88e8bbf3 100644 --- a/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java +++ b/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java @@ -42,7 +42,7 @@ public boolean onMessageReceive(Text text, String message) { return true; } - storage.setCaptchaMapImage(getMapImage(offHandStack)); + storage.setCaptchaMap(offHandStack.get(MAP_ID)); return true; } From fdbc959c03cd0bff737bb3eccce5bbb40e7e5db2 Mon Sep 17 00:00:00 2001 From: RettichLP Date: Thu, 28 May 2026 21:15:33 +0200 Subject: [PATCH 5/7] Add fishing rod check in AnglerListener to ensure proper tool is equipped during captcha handling --- .../rettichlp/ucutils/listener/impl/job/AnglerListener.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java b/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java index 88e8bbf3..19385dc7 100644 --- a/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java +++ b/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java @@ -23,6 +23,7 @@ import static net.minecraft.component.DataComponentTypes.MAP_ID; import static net.minecraft.item.FilledMapItem.getMapState; import static net.minecraft.item.Items.FILLED_MAP; +import static net.minecraft.item.Items.FISHING_ROD; import static net.minecraft.util.Hand.MAIN_HAND; @UCUtilsListener @@ -48,8 +49,10 @@ public boolean onMessageReceive(Text text, String message) { Matcher anglerCaptchaContinuedMatcher = ANGLER_CAPTCHA_CONTINUED_PATTERN.matcher(message); if (anglerCaptchaContinuedMatcher.find()) { + storage.setCaptchaMap(null); + ClientPlayerInteractionManager interactionManager = client.interactionManager; - if (interactionManager == null) { + if (interactionManager == null || !player.getMainHandStack().isOf(FISHING_ROD)) { return true; } From 477ebc156e41f25d071843470c7c0a3319a8cb8c Mon Sep 17 00:00:00 2001 From: RettichLP Date: Fri, 29 May 2026 22:30:21 +0200 Subject: [PATCH 6/7] Save captcha map image to file system in `AnglerListener` for render purposes --- .../listener/impl/job/AnglerListener.java | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java b/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java index 19385dc7..a8a2b70a 100644 --- a/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java +++ b/src/main/java/de/rettichlp/ucutils/listener/impl/job/AnglerListener.java @@ -2,7 +2,6 @@ import de.rettichlp.ucutils.common.registry.UCUtilsListener; import de.rettichlp.ucutils.listener.IMessageReceiveListener; -import lombok.NonNull; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerInteractionManager; import net.minecraft.component.type.MapIdComponent; @@ -12,13 +11,16 @@ import org.jetbrains.annotations.Nullable; import java.awt.image.BufferedImage; +import java.io.File; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static de.rettichlp.ucutils.UCUtils.LOGGER; import static de.rettichlp.ucutils.UCUtils.player; import static de.rettichlp.ucutils.UCUtils.storage; import static java.awt.image.BufferedImage.TYPE_INT_RGB; import static java.util.regex.Pattern.compile; +import static javax.imageio.ImageIO.write; import static net.minecraft.block.MapColor.getRenderColor; import static net.minecraft.component.DataComponentTypes.MAP_ID; import static net.minecraft.item.FilledMapItem.getMapState; @@ -43,7 +45,11 @@ public boolean onMessageReceive(Text text, String message) { return true; } - storage.setCaptchaMap(offHandStack.get(MAP_ID)); + MapIdComponent captchaMap = offHandStack.get(MAP_ID); + storage.setCaptchaMap(captchaMap); + + saveCaptchaImage(captchaMap); + return true; } @@ -64,13 +70,25 @@ public boolean onMessageReceive(Text text, String message) { return true; } - private @Nullable BufferedImage getMapImage(@NonNull ItemStack mapItemStack) { - MapIdComponent mapId = mapItemStack.get(MAP_ID); - if (mapId == null) { - return null; + private void saveCaptchaImage(MapIdComponent captchaMap) { + BufferedImage mapImage = getMapImage(captchaMap); + if (mapImage == null) { + return; } - MapState mapState = getMapState(mapId, MinecraftClient.getInstance().world); + File outputFile = new File("screenshots/ucutils/captcha.png"); + + try { + outputFile.mkdirs(); + outputFile.createNewFile(); + write(mapImage, "PNG", outputFile); + } catch (Exception e) { + LOGGER.error("Failed to save captcha image", e); + } + } + + private @Nullable BufferedImage getMapImage(MapIdComponent mapIdComponent) { + MapState mapState = getMapState(mapIdComponent, MinecraftClient.getInstance().world); if (mapState == null) { return null; } From d6a42873123fcfab8b68a02a386afc71ebe476df Mon Sep 17 00:00:00 2001 From: RettichLP Date: Fri, 29 May 2026 22:31:42 +0200 Subject: [PATCH 7/7] Render captcha image in front of chat window using custom texture manager --- .../ucutils/mixin/InGameHudMixin.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/de/rettichlp/ucutils/mixin/InGameHudMixin.java b/src/main/java/de/rettichlp/ucutils/mixin/InGameHudMixin.java index e9af07e5..50c27a29 100644 --- a/src/main/java/de/rettichlp/ucutils/mixin/InGameHudMixin.java +++ b/src/main/java/de/rettichlp/ucutils/mixin/InGameHudMixin.java @@ -1,13 +1,17 @@ package de.rettichlp.ucutils.mixin; import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.InGameHud; import net.minecraft.client.render.RenderTickCounter; +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.NativeImageBackedTexture; import net.minecraft.entity.LivingEntity; import net.minecraft.util.Identifier; import net.minecraft.util.profiler.Profilers; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; @@ -15,6 +19,10 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.io.InputStream; +import java.nio.file.Path; + +import static de.rettichlp.ucutils.UCUtils.LOGGER; import static de.rettichlp.ucutils.UCUtils.MOD_ID; import static de.rettichlp.ucutils.UCUtils.configuration; import static de.rettichlp.ucutils.UCUtils.nameTagService; @@ -26,6 +34,7 @@ import static java.lang.Math.clamp; import static java.lang.Math.round; import static java.lang.System.currentTimeMillis; +import static java.nio.file.Files.newInputStream; import static net.minecraft.client.gl.RenderPipelines.GUI_TEXTURED; import static net.minecraft.item.Items.GOLDEN_HOE; import static net.minecraft.registry.tag.FluidTags.WATER; @@ -43,9 +52,40 @@ public abstract class InGameHudMixin { @Unique private static final Identifier HYDRATION_FULL_TEXTURE = Identifier.of(MOD_ID, "textures/hud/hydration_full.png"); + @Unique + private static final Identifier CAPTCHA_IDENTIFIER = Identifier.of("ucutils", "captcha"); + + @Shadow + @Final + private MinecraftClient client; + @Shadow public abstract TextRenderer getTextRenderer(); + @Inject(method = "renderChat", at = @At("TAIL")) + private void ucutils$renderChatTail(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { + if (storage.getCaptchaMap() == null) { + return; + } + + NativeImage nativeImage; + try (InputStream is = newInputStream(Path.of("captcha.png"))) { + nativeImage = NativeImage.read(is); + } catch (Exception e) { + LOGGER.error("Failed to read captcha image", e); + return; + } + + NativeImageBackedTexture nativeImageBackedTexture = new NativeImageBackedTexture(() -> "captcha", nativeImage); + this.client.getTextureManager().registerTexture(CAPTCHA_IDENTIFIER, nativeImageBackedTexture); + + int side = context.getScaledWindowWidth() / 4; + int x = context.getScaledWindowWidth() / 2 - side / 2; + int y = context.getScaledWindowHeight() / 4 - side / 2; + + context.drawTexture(GUI_TEXTURED, CAPTCHA_IDENTIFIER, x, y, 0, 0, side, side, side, side); + } + @Inject(method = "renderCrosshair", at = @At( value = "INVOKE",