From a9e9b81702e9a5d553d5fc4e4f27c92775ee4600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6hr?= <105299503+Mars190@users.noreply.github.com> Date: Mon, 22 Dec 2025 22:16:41 +0100 Subject: [PATCH 1/6] Refactor of TanukiEgapFinder + Simple AutoSearch The Module EgapFinder is a much more readable and maintainable version fo TanukiEgapFinder with the option of a simple AutoSearch as well. I plan to write an AutoSearch that is not moving you on a straight line but in a spiral. --- src/main/java/cqb13/NumbyHack/NumbyHack.java | 2 +- .../NumbyHack/modules/general/EgapFinder.java | 296 ++++++++++++++++++ .../modules/general/TanukiEgapFinder.java | 183 ----------- 3 files changed, 297 insertions(+), 184 deletions(-) create mode 100644 src/main/java/cqb13/NumbyHack/modules/general/EgapFinder.java delete mode 100644 src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java diff --git a/src/main/java/cqb13/NumbyHack/NumbyHack.java b/src/main/java/cqb13/NumbyHack/NumbyHack.java index 0318722..fc430fb 100644 --- a/src/main/java/cqb13/NumbyHack/NumbyHack.java +++ b/src/main/java/cqb13/NumbyHack/NumbyHack.java @@ -61,7 +61,7 @@ public void onInitialize() { modules.add(new SafeFire()); modules.add(new SafetyNet()); modules.add(new SpawnerEsp()); - modules.add(new TanukiEgapFinder()); + modules.add(new EgapFinder()); modules.add(new WurstGlide()); Log("Adding HUD modules..."); diff --git a/src/main/java/cqb13/NumbyHack/modules/general/EgapFinder.java b/src/main/java/cqb13/NumbyHack/modules/general/EgapFinder.java new file mode 100644 index 0000000..e19972c --- /dev/null +++ b/src/main/java/cqb13/NumbyHack/modules/general/EgapFinder.java @@ -0,0 +1,296 @@ +package cqb13.NumbyHack.modules.general; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; + +import cqb13.NumbyHack.NumbyHack; +import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.settings.BoolSetting; +import meteordevelopment.meteorclient.settings.IntSetting; +import meteordevelopment.meteorclient.settings.Setting; +import meteordevelopment.meteorclient.settings.SettingGroup; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.utils.Utils; +import meteordevelopment.meteorclient.utils.player.ChatUtils; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.BlockPos; + +/** + * Original from Tanuki: https://gitlab.com/Walaryne/tanuki + */ +public class EgapFinder extends Module { + private static final String OUTPUT_FILE = "egap-coords.txt"; + private static final int COMPARATOR_DELAY_TICKS = 3; + private static final int NO_CHEST_TICK_THRESHOLD = 2; + private static final int CHUNK_SEARCHED_THRESHOLD = 10 * 20; + private static final boolean DEBUG = true; + + private final SettingGroup sgDefault = settings.getDefaultGroup(); + private final SettingGroup sgAutoSearch = settings.createGroup("Auto-Search"); + + private final Setting playSound = sgDefault.add(new BoolSetting.Builder() + .name("play-sound") + .description("Plays a sound when you find an egap.") + .defaultValue(false) + .build()); + + private final Setting autoSearch = sgAutoSearch.add(new BoolSetting.Builder() + .name("auto-search") + .description("Teleports you to a new area to be scanned when the current area is already completed.") + .defaultValue(false) + .build()); + + private final Setting renderDistance = sgAutoSearch.add(new IntSetting.Builder() + .name("render-distance") + .description("The render distance in your settings, this is used to calculate the position to tp to.") + .defaultValue(16) + .min(5) + .sliderMax(32) + .visible(autoSearch::get) + .build() + ); + + private enum ProcessState { + IDLE, + PLACE_COMPARATOR, + PLACE_LEAVES, + CHECK_FOR_EGAP, + VERIFY_RESULT + } + + private ProcessState currentState = ProcessState.IDLE; + private boolean isProcessingChest = false; + private int ticksWithoutChest = 0; + private int comparatorDelayCounter = 0; + private BlockPos currentChestPos = null; + private BlockPos lastCheckedChestPos = null; + + public EgapFinder() { + super(NumbyHack.CATEGORY, "egap-finder", + "Finds Enchanted Golden Apples in chests and logs coordinates to " + OUTPUT_FILE); + } + + @Override + public void onActivate() { + resetState(); + } + + @EventHandler + private void onTick(TickEvent.Pre event) { + if (mc.world == null || mc.player == null) { + debug("Tick skipped: world or player is null"); + return; + } + + BlockPos foundChest = findNearestChest(); + + if (foundChest != null) { + if (currentChestPos == null || !currentChestPos.equals(foundChest)) { + debug("Found new chest at: " + formatPos(foundChest)); + } + currentChestPos = foundChest; + ticksWithoutChest = 0; + isProcessingChest = false; + } else { + ticksWithoutChest++; + } + + if (ticksWithoutChest >= NO_CHEST_TICK_THRESHOLD && ticksWithoutChest < CHUNK_SEARCHED_THRESHOLD) { + if (currentState != ProcessState.IDLE) { + debug("NO_CHEST_TICK_THRESHOLD reached (" + ticksWithoutChest + " ticks), resetting state"); + resetState(); + } + return; + } + + if (ticksWithoutChest >= CHUNK_SEARCHED_THRESHOLD && autoSearch.get()) { + debug("CHUNK_SEARCHED_THRESHOLD reached (" + ticksWithoutChest + " ticks), looking for new area"); + tpToNewSearchArea(); + resetState(); + ticksWithoutChest = 0; + } + + if (isProcessingChest) { + return; + } + + if (currentChestPos == null) { + return; + } + + processCurrentState(); + } + + private void tpToNewSearchArea() { + BlockPos positionToTeleportTo = this.calculateNewSearchArea(mc.player.getBlockPos()); + String command = "/tp %d %d %d"; + + ChatUtils.info(Formatting.GREEN + + String.format("Teleporting to new search area with the center at (%d, %d, %d)!", + positionToTeleportTo.getX(), positionToTeleportTo.getY(), positionToTeleportTo.getZ() + ) + ); + + ChatUtils.sendPlayerMsg(String.format(command, positionToTeleportTo.getX(), positionToTeleportTo.getY(), positionToTeleportTo.getZ())); + } + + private BlockPos calculateNewSearchArea(BlockPos currentSearchArea) { + int blocksInRange = 16 * renderDistance.get(); + return new BlockPos(currentSearchArea.getX() + (2 * blocksInRange), currentSearchArea.getY(), currentSearchArea.getZ()); + } + + private BlockPos findNearestChest() { + for (BlockEntity blockEntity : Utils.blockEntities()) { + if (blockEntity instanceof ChestBlockEntity && !blockEntity.isRemoved()) { + return blockEntity.getPos(); + } + } + + return null; + } + + private void processCurrentState() { + debug("Processing state: " + currentState); + + switch (currentState) { + case IDLE: + currentState = ProcessState.PLACE_COMPARATOR; + break; + + case PLACE_COMPARATOR: + placeComparator(); + currentState = ProcessState.PLACE_LEAVES; + break; + + case PLACE_LEAVES: + placeLeavesAndWaitForComparatorUpdate(); + break; + + case CHECK_FOR_EGAP: + checkForEgap(); + currentState = ProcessState.VERIFY_RESULT; + break; + + case VERIFY_RESULT: + verifyAndCleanup(); + currentState = ProcessState.IDLE; + break; + } + } + + private void placeComparator() { + BlockPos comparatorPos = currentChestPos.add(-1, 0, 0); + Block existingBlock = mc.world.getBlockState(comparatorPos).getBlock(); + + if (existingBlock != Blocks.COMPARATOR) { + String command = String.format("/setblock %d %d %d minecraft:comparator[facing=east]", + comparatorPos.getX(), comparatorPos.getY(), comparatorPos.getZ()); + ChatUtils.sendPlayerMsg(command); + } + } + + private void placeLeavesAndWaitForComparatorUpdate() { + BlockPos leavesPos = currentChestPos.add(-1, 1, 0); + Block existingBlock = mc.world.getBlockState(leavesPos).getBlock(); + + if (existingBlock != Blocks.ACACIA_LEAVES) { + String command = String.format("/setblock %d %d %d minecraft:acacia_leaves", + leavesPos.getX(), leavesPos.getY(), leavesPos.getZ()); + ChatUtils.sendPlayerMsg(command); + } + + comparatorDelayCounter++; + + // Wait for comparator to update before checking + if (comparatorDelayCounter >= COMPARATOR_DELAY_TICKS) { + currentState = ProcessState.CHECK_FOR_EGAP; + comparatorDelayCounter = 0; + } + } + + private void checkForEgap() { + Block block = mc.world.getBlockState(currentChestPos).getBlock(); + + if (block != Blocks.CHEST) { + return; + } + + String command = String.format( + "/execute if data block %d %d %d Items[{id:\"minecraft:enchanted_golden_apple\"}] " + + "as @p run setblock %d %d %d minecraft:diamond_block", + currentChestPos.getX(), currentChestPos.getY(), currentChestPos.getZ(), + currentChestPos.getX(), currentChestPos.getY(), currentChestPos.getZ() + ); + + ChatUtils.sendPlayerMsg(command); + + lastCheckedChestPos = currentChestPos; + } + + private void verifyAndCleanup() { + if (lastCheckedChestPos == null) { + return; + } + + Block resultBlock = mc.world.getBlockState(lastCheckedChestPos).getBlock(); + + if (resultBlock == Blocks.DIAMOND_BLOCK) { + handleEgapFound(lastCheckedChestPos); + } else { + String command = String.format("/setblock %d %d %d minecraft:air", + lastCheckedChestPos.getX(), lastCheckedChestPos.getY(), lastCheckedChestPos.getZ()); + ChatUtils.sendPlayerMsg(command); + } + } + + private void handleEgapFound(BlockPos pos) { + String coords = String.format("%d %d %d", pos.getX(), pos.getY(), pos.getZ()); + + if (playSound.get() && mc.player != null) { + mc.player.playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f); + } + + ChatUtils.info(Formatting.GREEN + "Found an egap (" + coords + ")!"); + + if (!writeCoordinatesToFile(coords)) { + ChatUtils.error("Failed to write coordinates to file!"); + } else { + } + } + + private boolean writeCoordinatesToFile(String coords) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(OUTPUT_FILE, true))) { + writer.write(coords); + writer.newLine(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + private void resetState() { + isProcessingChest = true; + currentState = ProcessState.IDLE; + comparatorDelayCounter = 0; + currentChestPos = null; + lastCheckedChestPos = null; + } + + private void debug(String message) { + if (DEBUG) { + System.out.println("[EgapFinder] " + message); + } + } + + private String formatPos(BlockPos pos) { + return String.format("(%d, %d, %d)", pos.getX(), pos.getY(), pos.getZ()); + } +} diff --git a/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java deleted file mode 100644 index daeddf5..0000000 --- a/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java +++ /dev/null @@ -1,183 +0,0 @@ -package cqb13.NumbyHack.modules.general; - -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; - -import cqb13.NumbyHack.NumbyHack; -import meteordevelopment.meteorclient.events.world.TickEvent; -import meteordevelopment.meteorclient.settings.BoolSetting; -import meteordevelopment.meteorclient.settings.Setting; -import meteordevelopment.meteorclient.settings.SettingGroup; -import meteordevelopment.meteorclient.systems.modules.Module; -import meteordevelopment.meteorclient.utils.Utils; -import meteordevelopment.meteorclient.utils.player.ChatUtils; -import meteordevelopment.orbit.EventHandler; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.block.entity.ChestBlockEntity; -import net.minecraft.sound.SoundEvents; -import net.minecraft.util.Formatting; -import net.minecraft.util.math.BlockPos; - -/** - * from Tanuki - */ -// https://gitlab.com/Walaryne/tanuki/-/blob/master/src/main/java/minegame159/meteorclient/modules/misc/EgapFinder.java - -public class TanukiEgapFinder extends Module { - private final SettingGroup sgDefault = settings.getDefaultGroup(); - - private final Setting coords = sgDefault.add(new BoolSetting.Builder() - .name("coords") - .description("Sends the coords in the message in case you're lazy to look at your .minecraft folder.") - .defaultValue(true) - .build()); - - private final Setting debug = sgDefault.add(new BoolSetting.Builder() - .name("debug") - .description( - "Useless. Just prints info about every chest it locates in your render distance, will spam chat a lot.") - .defaultValue(false) - .build()); - - private final Setting playSound = sgDefault.add(new BoolSetting.Builder() - .name("play-sound") - .description("Plays a sound when you find an egap.") - .defaultValue(false) - .build()); - - private boolean check; - private boolean lock; - private int stage = 0; - private int checkDelay; - private int comparatorHold = 0; - private BlockPos chest; - private BlockPos prevChest; - - public TanukiEgapFinder() { - super(NumbyHack.CATEGORY, "egap-finder", - "Finds Egaps in a SP world and creates a file called \"egap-coords.txt\"."); - } - - private static void writeToFile(String coords) { - try (FileWriter fw = new FileWriter("egap-coords.txt", true); - BufferedWriter bw = new BufferedWriter(fw); - PrintWriter out = new PrintWriter(bw)) { - out.println(coords); - } catch (IOException exception) { - exception.printStackTrace(); - } - } - - @Override - public void onActivate() { - stage = 0; - checkDelay = 0; - lock = true; - if (debug.get()) - ChatUtils.info("STARTING"); - } - - @Override - public void onDeactivate() { - if (debug.get()) - ChatUtils.info("STOPPING"); - } - - @EventHandler - private void onTick(TickEvent.Pre event) { - check = false; - for (BlockEntity blockEntity : Utils.blockEntities()) { - if (blockEntity instanceof ChestBlockEntity) { - if (blockEntity.isRemoved()) - continue; - chest = blockEntity.getPos(); - - check = true; - lock = false; - } - } - - if (!check) { - checkDelay++; - } else { - checkDelay = 0; - } - if (checkDelay >= 2) { - lock = true; - checkDelay = 0; - stage = 1; - } - - if (!lock) { - if (stage == 0) { - stage = 1; - } - switch (stage) { - case 1: { - int adjacent = chest.getX() - 1; - Block block = mc.world.getBlockState(chest.add(-1, 0, 0)).getBlock(); - if (block != Blocks.COMPARATOR) { - ; - ChatUtils.sendPlayerMsg("/setblock " + adjacent + " " + chest.getY() + " " + chest.getZ() - + " minecraft:comparator[facing=east]"); - } - stage++; - break; - } - case 2: { - int xAdjacent = chest.getX() - 1; - int yAdjacent = chest.getY() + 1; - Block block = mc.world.getBlockState(chest.add(-1, +1, 0)).getBlock(); - if (block != Blocks.ACACIA_LEAVES) { - ChatUtils.sendPlayerMsg( - "/setblock " + xAdjacent + " " + yAdjacent + " " + chest.getZ() - + " minecraft:acacia_leaves"); - } - comparatorHold++; - if (comparatorHold == 3) { - stage++; - comparatorHold = 0; - } - break; - } - case 3: { - Block block = mc.world.getBlockState(chest).getBlock(); - if (block == Blocks.CHEST) { - ChatUtils.sendPlayerMsg( - "/execute if data block " + chest.getX() + " " + chest.getY() + " " + chest.getZ() - + " Items[{id:\"minecraft:enchanted_golden_apple\"}] as @p run setblock " - + chest.getX() + " " - + chest.getY() + " " + chest.getZ() + " minecraft:diamond_block"); - } - prevChest = chest; - stage++; - break; - } - case 4: { - Block diamondPos = mc.world.getBlockState(prevChest).getBlock(); - if (diamondPos == Blocks.DIAMOND_BLOCK) { - if (playSound.get()) - mc.player.playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f); - ChatUtils.info((!coords.get() ? Formatting.GREEN + "Found an egap! Wrote coords to file." - : Formatting.GREEN + "Found an egap! Wrote coords to file. " + prevChest.getX() + " " - + prevChest.getY() - + " " + prevChest.getZ())); - writeToFile(prevChest.getX() + " " + prevChest.getY() + " " + prevChest.getZ()); - } else - ChatUtils.sendPlayerMsg( - "/setblock " + chest.getX() + " " + chest.getY() + " " + chest.getZ() - + " minecraft:air"); - stage++; - break; - } - } - if (stage == 5) { - stage = 1; - } - } - } -} From e00a9c220805e97e53f149e69c83d0cd5e3ad16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6hr?= <105299503+Mars190@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:50:45 +0100 Subject: [PATCH 2/6] Now using a SpiralTraversal --- src/main/java/cqb13/NumbyHack/NumbyHack.java | 2 +- ...{EgapFinder.java => TanukiEgapFinder.java} | 68 +++++++++++++++---- 2 files changed, 56 insertions(+), 14 deletions(-) rename src/main/java/cqb13/NumbyHack/modules/general/{EgapFinder.java => TanukiEgapFinder.java} (82%) diff --git a/src/main/java/cqb13/NumbyHack/NumbyHack.java b/src/main/java/cqb13/NumbyHack/NumbyHack.java index fc430fb..0318722 100644 --- a/src/main/java/cqb13/NumbyHack/NumbyHack.java +++ b/src/main/java/cqb13/NumbyHack/NumbyHack.java @@ -61,7 +61,7 @@ public void onInitialize() { modules.add(new SafeFire()); modules.add(new SafetyNet()); modules.add(new SpawnerEsp()); - modules.add(new EgapFinder()); + modules.add(new TanukiEgapFinder()); modules.add(new WurstGlide()); Log("Adding HUD modules..."); diff --git a/src/main/java/cqb13/NumbyHack/modules/general/EgapFinder.java b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java similarity index 82% rename from src/main/java/cqb13/NumbyHack/modules/general/EgapFinder.java rename to src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java index e19972c..3511472 100644 --- a/src/main/java/cqb13/NumbyHack/modules/general/EgapFinder.java +++ b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java @@ -3,6 +3,7 @@ import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; +import java.util.*; import cqb13.NumbyHack.NumbyHack; import meteordevelopment.meteorclient.events.world.TickEvent; @@ -23,9 +24,10 @@ import net.minecraft.util.math.BlockPos; /** - * Original from Tanuki: https://gitlab.com/Walaryne/tanuki + * Original from Tanuki: + * https://gitlab.com/Walaryne/tanuki/-/blob/master/src/main/java/minegame159/meteorclient/modules/misc/EgapFinder.java */ -public class EgapFinder extends Module { +public class TanukiEgapFinder extends Module { private static final String OUTPUT_FILE = "egap-coords.txt"; private static final int COMPARATOR_DELAY_TICKS = 3; private static final int NO_CHEST_TICK_THRESHOLD = 2; @@ -71,14 +73,17 @@ private enum ProcessState { private int comparatorDelayCounter = 0; private BlockPos currentChestPos = null; private BlockPos lastCheckedChestPos = null; + private SpiralTraversal spiralTraversal; - public EgapFinder() { + public TanukiEgapFinder() { super(NumbyHack.CATEGORY, "egap-finder", "Finds Enchanted Golden Apples in chests and logs coordinates to " + OUTPUT_FILE); } @Override public void onActivate() { + BlockPos playerPos = mc.player.getBlockPos(); + spiralTraversal = new SpiralTraversal(playerPos.getX(), playerPos.getZ()); resetState(); } @@ -129,21 +134,16 @@ private void onTick(TickEvent.Pre event) { } private void tpToNewSearchArea() { - BlockPos positionToTeleportTo = this.calculateNewSearchArea(mc.player.getBlockPos()); - String command = "/tp %d %d %d"; + XZPos positionToTeleportTo = spiralTraversal.next(); + String command = "/tp %d ~ %d"; ChatUtils.info(Formatting.GREEN + - String.format("Teleporting to new search area with the center at (%d, %d, %d)!", - positionToTeleportTo.getX(), positionToTeleportTo.getY(), positionToTeleportTo.getZ() + String.format("Teleporting to new search area with the center at (%d, ~, %d)!", + positionToTeleportTo.x(), positionToTeleportTo.z() ) ); - ChatUtils.sendPlayerMsg(String.format(command, positionToTeleportTo.getX(), positionToTeleportTo.getY(), positionToTeleportTo.getZ())); - } - - private BlockPos calculateNewSearchArea(BlockPos currentSearchArea) { - int blocksInRange = 16 * renderDistance.get(); - return new BlockPos(currentSearchArea.getX() + (2 * blocksInRange), currentSearchArea.getY(), currentSearchArea.getZ()); + ChatUtils.sendPlayerMsg(String.format(command, positionToTeleportTo.x(), positionToTeleportTo.z())); } private BlockPos findNearestChest() { @@ -293,4 +293,46 @@ private void debug(String message) { private String formatPos(BlockPos pos) { return String.format("(%d, %d, %d)", pos.getX(), pos.getY(), pos.getZ()); } + + private class SpiralTraversal { + private final Set visited = new HashSet<>(); + private final Set queued = new HashSet<>(); + private final Queue queue = new LinkedList<>(); + + public SpiralTraversal(int originX, int originZ) { + XZPos origin = new XZPos(originX, originZ); + + visited.add(origin); + updateQueue(origin); + } + + public XZPos next() { + XZPos next = queue.remove(); + + queued.remove(next); + visited.add(next); + updateQueue(next); + + return next; + } + + private void updateQueue(XZPos position) { + int blockOffset = 2 * 16 * renderDistance.get(); + Set neighbors = new HashSet<>(); + + neighbors.add(new XZPos(position.x + blockOffset, position.z)); + neighbors.add(new XZPos(position.x, position.z - blockOffset)); + neighbors.add(new XZPos(position.x - blockOffset, position.z)); + neighbors.add(new XZPos(position.x, position.z + blockOffset)); + + for (XZPos neighbor : neighbors) { + if (visited.contains(neighbor) || queued.contains(neighbor)) continue; + + queue.add(neighbor); + queued.add(neighbor); + } + } + } + + record XZPos(int x, int z) {} } From 255058c6417de18c3c5a7250b685833ca0f91866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6hr?= <105299503+Mars190@users.noreply.github.com> Date: Tue, 23 Dec 2025 01:01:56 +0100 Subject: [PATCH 3/6] Now using the chunk data event This makes the module a bit more flexible as it now also can be configured by the user. --- .../modules/general/TanukiEgapFinder.java | 76 ++++++++++++++----- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java index 3511472..094675b 100644 --- a/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java +++ b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java @@ -6,11 +6,9 @@ import java.util.*; import cqb13.NumbyHack.NumbyHack; +import meteordevelopment.meteorclient.events.world.ChunkDataEvent; import meteordevelopment.meteorclient.events.world.TickEvent; -import meteordevelopment.meteorclient.settings.BoolSetting; -import meteordevelopment.meteorclient.settings.IntSetting; -import meteordevelopment.meteorclient.settings.Setting; -import meteordevelopment.meteorclient.settings.SettingGroup; +import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.utils.Utils; import meteordevelopment.meteorclient.utils.player.ChatUtils; @@ -30,8 +28,6 @@ public class TanukiEgapFinder extends Module { private static final String OUTPUT_FILE = "egap-coords.txt"; private static final int COMPARATOR_DELAY_TICKS = 3; - private static final int NO_CHEST_TICK_THRESHOLD = 2; - private static final int CHUNK_SEARCHED_THRESHOLD = 10 * 20; private static final boolean DEBUG = true; private final SettingGroup sgDefault = settings.getDefaultGroup(); @@ -59,6 +55,39 @@ public class TanukiEgapFinder extends Module { .build() ); + private final Setting noChestTimeout = sgAutoSearch.add(new DoubleSetting.Builder() + .name("no-chest-timeout") + .description("Time in seconds where no chest is found and the area is deemed as looted.") + .defaultValue(2.5) + .min(1) + .sliderMax(10) + .visible(autoSearch::get) + .decimalPlaces(1) + .build() + ); + + private final Setting chunkStableTime = sgAutoSearch.add(new DoubleSetting.Builder() + .name("chunk-stable-time") + .description("Time in seconds no new chunks are loading before moving on.") + .defaultValue(2.5) + .min(1) + .sliderMax(10) + .visible(autoSearch::get) + .decimalPlaces(1) + .build() + ); + + private final Setting teleportCooldown = sgAutoSearch.add(new DoubleSetting.Builder() + .name("teleport-cooldown") + .description("Minimum time in seconds to wait after teleporting before moving again.") + .defaultValue(5) + .min(1) + .sliderMax(20) + .visible(autoSearch::get) + .decimalPlaces(1) + .build() + ); + private enum ProcessState { IDLE, PLACE_COMPARATOR, @@ -74,6 +103,8 @@ private enum ProcessState { private BlockPos currentChestPos = null; private BlockPos lastCheckedChestPos = null; private SpiralTraversal spiralTraversal; + private int ticksSinceLastChunkData = 0; + private int ticksSinceTeleport = 0; public TanukiEgapFinder() { super(NumbyHack.CATEGORY, "egap-finder", @@ -87,6 +118,11 @@ public void onActivate() { resetState(); } + @EventHandler + private void onChunkData(ChunkDataEvent event) { + ticksSinceLastChunkData = 0; + } + @EventHandler private void onTick(TickEvent.Pre event) { if (mc.world == null || mc.player == null) { @@ -94,6 +130,10 @@ private void onTick(TickEvent.Pre event) { return; } + + ticksSinceLastChunkData++; + ticksSinceTeleport++; + BlockPos foundChest = findNearestChest(); if (foundChest != null) { @@ -107,26 +147,21 @@ private void onTick(TickEvent.Pre event) { ticksWithoutChest++; } - if (ticksWithoutChest >= NO_CHEST_TICK_THRESHOLD && ticksWithoutChest < CHUNK_SEARCHED_THRESHOLD) { - if (currentState != ProcessState.IDLE) { - debug("NO_CHEST_TICK_THRESHOLD reached (" + ticksWithoutChest + " ticks), resetting state"); - resetState(); - } - return; - } - - if (ticksWithoutChest >= CHUNK_SEARCHED_THRESHOLD && autoSearch.get()) { - debug("CHUNK_SEARCHED_THRESHOLD reached (" + ticksWithoutChest + " ticks), looking for new area"); + if (autoSearch.get() + && ticksWithoutChest >= noChestTimeout.get() * 20 + && ticksSinceLastChunkData >= chunkStableTime.get() * 20 + && ticksSinceTeleport >= teleportCooldown.get() * 20) + { + debug("Chunks stable (" + ticksSinceLastChunkData + " ticks) and minimum wait satisfied, moving to new area"); tpToNewSearchArea(); resetState(); ticksWithoutChest = 0; - } - - if (isProcessingChest) { + ticksSinceLastChunkData = 0; + ticksSinceTeleport = 0; return; } - if (currentChestPos == null) { + if (isProcessingChest || currentChestPos == null) { return; } @@ -144,6 +179,7 @@ private void tpToNewSearchArea() { ); ChatUtils.sendPlayerMsg(String.format(command, positionToTeleportTo.x(), positionToTeleportTo.z())); + ticksSinceTeleport = 0; } private BlockPos findNearestChest() { From 060fc6806fc34946a406ed8e243eec05b226b50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20St=C3=B6hr?= <105299503+Mars190@users.noreply.github.com> Date: Tue, 23 Dec 2025 01:15:55 +0100 Subject: [PATCH 4/6] Now every world gets saved seperately by seed --- .../modules/general/TanukiEgapFinder.java | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java index 094675b..c7275bb 100644 --- a/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java +++ b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java @@ -26,7 +26,8 @@ * https://gitlab.com/Walaryne/tanuki/-/blob/master/src/main/java/minegame159/meteorclient/modules/misc/EgapFinder.java */ public class TanukiEgapFinder extends Module { - private static final String OUTPUT_FILE = "egap-coords.txt"; + private static final String OUTPUT_FILE_NAME = "egap-coords"; + private static final int COMPARATOR_DELAY_TICKS = 3; private static final boolean DEBUG = true; @@ -108,7 +109,7 @@ private enum ProcessState { public TanukiEgapFinder() { super(NumbyHack.CATEGORY, "egap-finder", - "Finds Enchanted Golden Apples in chests and logs coordinates to " + OUTPUT_FILE); + "Finds Enchanted Golden Apples in chests and logs coordinates to " + OUTPUT_FILE_NAME + "-yourworldseed.txt"); } @Override @@ -302,7 +303,7 @@ private void handleEgapFound(BlockPos pos) { } private boolean writeCoordinatesToFile(String coords) { - try (BufferedWriter writer = new BufferedWriter(new FileWriter(OUTPUT_FILE, true))) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(getOutputFileName(), true))) { writer.write(coords); writer.newLine(); return true; @@ -371,4 +372,26 @@ private void updateQueue(XZPos position) { } record XZPos(int x, int z) {} + + private String getOutputFileName() { + if (mc.world == null) { + return OUTPUT_FILE_NAME + ".txt"; + } + + long seed = getWorldSeed(); + + // If negative turn - into n + String seedStr = seed < 0 ? "n" + Math.abs(seed) : String.valueOf(seed); + return String.format("%s-%s.txt", OUTPUT_FILE_NAME, seedStr); + } + + private Long getWorldSeed() { + if (mc.getServer() != null) { + var worldProperties = mc.getServer().getSaveProperties(); + if (worldProperties != null) { + return worldProperties.getGeneratorOptions().getSeed(); + } + } + return null; + } } From fe7878dc179fc89ac7289aa6d6d2b7c649c4a686 Mon Sep 17 00:00:00 2001 From: cqb13 Date: Mon, 22 Dec 2025 19:43:18 -0500 Subject: [PATCH 5/6] code formatting --- .../modules/general/TanukiEgapFinder.java | 134 +++++++++--------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java index c7275bb..7397073 100644 --- a/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java +++ b/src/main/java/cqb13/NumbyHack/modules/general/TanukiEgapFinder.java @@ -3,12 +3,19 @@ import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; -import java.util.*; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Set; import cqb13.NumbyHack.NumbyHack; import meteordevelopment.meteorclient.events.world.ChunkDataEvent; import meteordevelopment.meteorclient.events.world.TickEvent; -import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.settings.BoolSetting; +import meteordevelopment.meteorclient.settings.DoubleSetting; +import meteordevelopment.meteorclient.settings.IntSetting; +import meteordevelopment.meteorclient.settings.Setting; +import meteordevelopment.meteorclient.settings.SettingGroup; import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.utils.Utils; import meteordevelopment.meteorclient.utils.player.ChatUtils; @@ -35,59 +42,55 @@ public class TanukiEgapFinder extends Module { private final SettingGroup sgAutoSearch = settings.createGroup("Auto-Search"); private final Setting playSound = sgDefault.add(new BoolSetting.Builder() - .name("play-sound") - .description("Plays a sound when you find an egap.") - .defaultValue(false) - .build()); + .name("play-sound") + .description("Plays a sound when you find an egap.") + .defaultValue(false) + .build()); private final Setting autoSearch = sgAutoSearch.add(new BoolSetting.Builder() - .name("auto-search") - .description("Teleports you to a new area to be scanned when the current area is already completed.") - .defaultValue(false) - .build()); + .name("auto-search") + .description("Teleports you to a new area to be scanned when the current area is already completed.") + .defaultValue(false) + .build()); private final Setting renderDistance = sgAutoSearch.add(new IntSetting.Builder() - .name("render-distance") - .description("The render distance in your settings, this is used to calculate the position to tp to.") - .defaultValue(16) - .min(5) - .sliderMax(32) - .visible(autoSearch::get) - .build() - ); + .name("render-distance") + .description("The render distance in your settings, this is used to calculate the position to tp to.") + .defaultValue(16) + .min(5) + .sliderMax(32) + .visible(autoSearch::get) + .build()); private final Setting noChestTimeout = sgAutoSearch.add(new DoubleSetting.Builder() - .name("no-chest-timeout") - .description("Time in seconds where no chest is found and the area is deemed as looted.") - .defaultValue(2.5) - .min(1) - .sliderMax(10) - .visible(autoSearch::get) - .decimalPlaces(1) - .build() - ); + .name("no-chest-timeout") + .description("Time in seconds where no chest is found and the area is deemed as looted.") + .defaultValue(2.5) + .min(1) + .sliderMax(10) + .visible(autoSearch::get) + .decimalPlaces(1) + .build()); private final Setting chunkStableTime = sgAutoSearch.add(new DoubleSetting.Builder() - .name("chunk-stable-time") - .description("Time in seconds no new chunks are loading before moving on.") - .defaultValue(2.5) - .min(1) - .sliderMax(10) - .visible(autoSearch::get) - .decimalPlaces(1) - .build() - ); + .name("chunk-stable-time") + .description("Time in seconds no new chunks are loading before moving on.") + .defaultValue(2.5) + .min(1) + .sliderMax(10) + .visible(autoSearch::get) + .decimalPlaces(1) + .build()); private final Setting teleportCooldown = sgAutoSearch.add(new DoubleSetting.Builder() - .name("teleport-cooldown") - .description("Minimum time in seconds to wait after teleporting before moving again.") - .defaultValue(5) - .min(1) - .sliderMax(20) - .visible(autoSearch::get) - .decimalPlaces(1) - .build() - ); + .name("teleport-cooldown") + .description("Minimum time in seconds to wait after teleporting before moving again.") + .defaultValue(5) + .min(1) + .sliderMax(20) + .visible(autoSearch::get) + .decimalPlaces(1) + .build()); private enum ProcessState { IDLE, @@ -109,7 +112,8 @@ private enum ProcessState { public TanukiEgapFinder() { super(NumbyHack.CATEGORY, "egap-finder", - "Finds Enchanted Golden Apples in chests and logs coordinates to " + OUTPUT_FILE_NAME + "-yourworldseed.txt"); + "Finds Enchanted Golden Apples in chests and logs coordinates to " + OUTPUT_FILE_NAME + + "-yourworldseed.txt"); } @Override @@ -131,7 +135,6 @@ private void onTick(TickEvent.Pre event) { return; } - ticksSinceLastChunkData++; ticksSinceTeleport++; @@ -149,11 +152,11 @@ private void onTick(TickEvent.Pre event) { } if (autoSearch.get() - && ticksWithoutChest >= noChestTimeout.get() * 20 - && ticksSinceLastChunkData >= chunkStableTime.get() * 20 - && ticksSinceTeleport >= teleportCooldown.get() * 20) - { - debug("Chunks stable (" + ticksSinceLastChunkData + " ticks) and minimum wait satisfied, moving to new area"); + && ticksWithoutChest >= noChestTimeout.get() * 20 + && ticksSinceLastChunkData >= chunkStableTime.get() * 20 + && ticksSinceTeleport >= teleportCooldown.get() * 20) { + debug("Chunks stable (" + ticksSinceLastChunkData + + " ticks) and minimum wait satisfied, moving to new area"); tpToNewSearchArea(); resetState(); ticksWithoutChest = 0; @@ -174,10 +177,8 @@ private void tpToNewSearchArea() { String command = "/tp %d ~ %d"; ChatUtils.info(Formatting.GREEN + - String.format("Teleporting to new search area with the center at (%d, ~, %d)!", - positionToTeleportTo.x(), positionToTeleportTo.z() - ) - ); + String.format("Teleporting to new search area with the center at (%d, ~, %d)!", + positionToTeleportTo.x(), positionToTeleportTo.z())); ChatUtils.sendPlayerMsg(String.format(command, positionToTeleportTo.x(), positionToTeleportTo.z())); ticksSinceTeleport = 0; @@ -228,7 +229,7 @@ private void placeComparator() { if (existingBlock != Blocks.COMPARATOR) { String command = String.format("/setblock %d %d %d minecraft:comparator[facing=east]", - comparatorPos.getX(), comparatorPos.getY(), comparatorPos.getZ()); + comparatorPos.getX(), comparatorPos.getY(), comparatorPos.getZ()); ChatUtils.sendPlayerMsg(command); } } @@ -239,7 +240,7 @@ private void placeLeavesAndWaitForComparatorUpdate() { if (existingBlock != Blocks.ACACIA_LEAVES) { String command = String.format("/setblock %d %d %d minecraft:acacia_leaves", - leavesPos.getX(), leavesPos.getY(), leavesPos.getZ()); + leavesPos.getX(), leavesPos.getY(), leavesPos.getZ()); ChatUtils.sendPlayerMsg(command); } @@ -260,11 +261,10 @@ private void checkForEgap() { } String command = String.format( - "/execute if data block %d %d %d Items[{id:\"minecraft:enchanted_golden_apple\"}] " + - "as @p run setblock %d %d %d minecraft:diamond_block", - currentChestPos.getX(), currentChestPos.getY(), currentChestPos.getZ(), - currentChestPos.getX(), currentChestPos.getY(), currentChestPos.getZ() - ); + "/execute if data block %d %d %d Items[{id:\"minecraft:enchanted_golden_apple\"}] " + + "as @p run setblock %d %d %d minecraft:diamond_block", + currentChestPos.getX(), currentChestPos.getY(), currentChestPos.getZ(), + currentChestPos.getX(), currentChestPos.getY(), currentChestPos.getZ()); ChatUtils.sendPlayerMsg(command); @@ -282,7 +282,7 @@ private void verifyAndCleanup() { handleEgapFound(lastCheckedChestPos); } else { String command = String.format("/setblock %d %d %d minecraft:air", - lastCheckedChestPos.getX(), lastCheckedChestPos.getY(), lastCheckedChestPos.getZ()); + lastCheckedChestPos.getX(), lastCheckedChestPos.getY(), lastCheckedChestPos.getZ()); ChatUtils.sendPlayerMsg(command); } } @@ -363,7 +363,8 @@ private void updateQueue(XZPos position) { neighbors.add(new XZPos(position.x, position.z + blockOffset)); for (XZPos neighbor : neighbors) { - if (visited.contains(neighbor) || queued.contains(neighbor)) continue; + if (visited.contains(neighbor) || queued.contains(neighbor)) + continue; queue.add(neighbor); queued.add(neighbor); @@ -371,7 +372,8 @@ private void updateQueue(XZPos position) { } } - record XZPos(int x, int z) {} + record XZPos(int x, int z) { + } private String getOutputFileName() { if (mc.world == null) { From d1cdcd85ccddc0d371500016916117e67745a9ef Mon Sep 17 00:00:00 2001 From: cqb13 Date: Mon, 22 Dec 2025 19:43:34 -0500 Subject: [PATCH 6/6] 2.6.7 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4f8221f..6011903 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,6 +8,6 @@ yarn_mappings=1.21.11+build.3 loader_version=0.18.2 # Mod Properties -mod_version=2.6.5 +mod_version=2.6.7 maven_group=cqb13.NumbyHack archives_base_name=Numby-Hack