From 0712b595c2d31c6893e8ada5524990046223b373 Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:38:22 +0200 Subject: [PATCH 1/5] feat(auto-fish): add offhand fishing rod support --- .../systems/modules/player/AutoFish.java | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java index 81dc56f8dc9..c3acb59882e 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java @@ -17,6 +17,7 @@ import meteordevelopment.meteorclient.utils.player.InvUtils; import meteordevelopment.meteorclient.utils.world.TickRate; import meteordevelopment.orbit.EventHandler; +import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.projectile.FishingHook; import net.minecraft.world.item.FishingRodItem; import net.minecraft.world.item.ItemStack; @@ -89,6 +90,7 @@ public AutoFish() { private double castDelayLeft = 0.0; private double catchDelayLeft = 0.0; private boolean wasHooked = false; + private InteractionHand fishingHand = null; @Override public void onActivate() { @@ -96,20 +98,44 @@ public void onActivate() { catchDelayLeft = 0.0; wasHooked = false; + fishingHand = null; } @EventHandler private void onTick(TickEvent.Pre event) { - int bestRodSlot = findBestRod(); + if (mc.player.fishing != null) { + if (fishingHand == null) fishingHand = getRodHand(); + if (fishingHand != null) tryCatch(); + return; + } + + if (isUsableRod(mc.player.getOffhandItem())) { + fishingHand = InteractionHand.OFF_HAND; + } else { + int bestRodSlot = findBestRod(); + + if (autoSwitch.get() && bestRodSlot != -1 && mc.player.getInventory().getSelectedSlot() != bestRodSlot) { + InvUtils.swap(bestRodSlot, false); + } - if (autoSwitch.get() && bestRodSlot != -1 && mc.player.getInventory().getSelectedSlot() != bestRodSlot) { - InvUtils.swap(bestRodSlot, false); + fishingHand = isUsableRod(mc.player.getMainHandItem()) ? InteractionHand.MAIN_HAND : null; } - if (!(mc.player.getMainHandItem().getItem() instanceof FishingRodItem)) return; + if (fishingHand == null) return; tryCast(); - tryCatch(); + } + + private InteractionHand getRodHand() { + if (isUsableRod(mc.player.getOffhandItem())) return InteractionHand.OFF_HAND; + if (isUsableRod(mc.player.getMainHandItem())) return InteractionHand.MAIN_HAND; + return null; + } + + private boolean isUsableRod(ItemStack stack) { + if (!(stack.getItem() instanceof FishingRodItem)) return false; + if (antiBreak.get() && stack.getDamageValue() == stack.getMaxDamage() - 1) return false; + return true; } private void tryCast() { @@ -122,11 +148,13 @@ private void tryCast() { return; } + if (fishingHand == null) return; useRod(); } private void tryCatch() { if (mc.player.fishing == null) return; + if (fishingHand == null) return; if (mc.player.fishing.getHookedIn() != null) { useRod(); return; @@ -152,7 +180,7 @@ private void tryCatch() { } private void useRod() { - Utils.rightClick(); + mc.gameMode.useItem(mc.player, fishingHand); wasHooked = false; castDelayLeft = randomizeDelay(castDelay.get(), castDelayVariance.get()); } From 2487ffbfde03345cd20fc893feb0d355f227f7a1 Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:09:51 +0200 Subject: [PATCH 2/5] refactor(auto-fish): use state machine for fishing lifecycle --- .../systems/modules/player/AutoFish.java | 122 ++++++++++-------- 1 file changed, 70 insertions(+), 52 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java index c3acb59882e..76f784b3a3b 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java @@ -83,30 +83,47 @@ public class AutoFish extends Module { .build() ); - public AutoFish() { - super(Categories.Player, "auto-fish", "Automatically fishes for you."); + private enum State { + IDLE, + WAITING_FOR_BITE, + WAITING_TO_REEL } + private State state = State.IDLE; + private InteractionHand fishingHand = null; + private double castDelayLeft = 0.0; private double catchDelayLeft = 0.0; - private boolean wasHooked = false; - private InteractionHand fishingHand = null; + + public AutoFish() { + super(Categories.Player, "auto-fish", "Automatically fishes for you."); + } @Override public void onActivate() { + state = State.IDLE; + fishingHand = null; + castDelayLeft = 0.0; catchDelayLeft = 0.0; + } - wasHooked = false; + @Override + public void onDeactivate() { + state = State.IDLE; fishingHand = null; } @EventHandler private void onTick(TickEvent.Pre event) { - if (mc.player.fishing != null) { - if (fishingHand == null) fishingHand = getRodHand(); - if (fishingHand != null) tryCatch(); - return; + if (mc.player.fishing != null) handleFishing(); + else handleIdle(); + } + + private void handleIdle() { + if (state != State.IDLE) { + state = State.IDLE; + fishingHand = null; } if (isUsableRod(mc.player.getOffhandItem())) { @@ -118,81 +135,82 @@ private void onTick(TickEvent.Pre event) { InvUtils.swap(bestRodSlot, false); } - fishingHand = isUsableRod(mc.player.getMainHandItem()) ? InteractionHand.MAIN_HAND : null; + if (isUsableRod(mc.player.getMainHandItem())) { + fishingHand = InteractionHand.MAIN_HAND; + } } if (fishingHand == null) return; - - tryCast(); - } - - private InteractionHand getRodHand() { - if (isUsableRod(mc.player.getOffhandItem())) return InteractionHand.OFF_HAND; - if (isUsableRod(mc.player.getMainHandItem())) return InteractionHand.MAIN_HAND; - return null; - } - - private boolean isUsableRod(ItemStack stack) { - if (!(stack.getItem() instanceof FishingRodItem)) return false; - if (antiBreak.get() && stack.getDamageValue() == stack.getMaxDamage() - 1) return false; - return true; - } - - private void tryCast() { - if (mc.player.fishing != null) return; - if (!autoCast.get()) return; - if (castDelayLeft > 0) { castDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0; return; } - if (fishingHand == null) return; - useRod(); + cast(); } - private void tryCatch() { - if (mc.player.fishing == null) return; + private void handleFishing() { if (fishingHand == null) return; - if (mc.player.fishing.getHookedIn() != null) { - useRod(); - return; - } - if (mc.player.fishing.currentState != FishingHook.FishHookState.BOBBING) return; + switch (state) { + case IDLE -> state = State.WAITING_FOR_BITE; + case WAITING_FOR_BITE -> { + if (mc.player.fishing.getHookedIn() != null) { + reel(); + return; + } - if (!wasHooked) { - if (((FishingHookAccessor) mc.player.fishing).meteor$hasCaughtFish()) { - catchDelayLeft = randomizeDelay(catchDelay.get(), catchDelayVariance.get()); - wasHooked = true; - } + if (mc.player.fishing.currentState != FishingHook.FishHookState.BOBBING) return; - return; + if (((FishingHookAccessor) mc.player.fishing).meteor$hasCaughtFish()) { + catchDelayLeft = randomizeDelay(catchDelay.get(), catchDelayVariance.get()); + state = State.WAITING_TO_REEL; + } + } + case WAITING_TO_REEL -> { + if (mc.player.fishing.getHookedIn() != null) { + reel(); + return; + } + + if (catchDelayLeft > 0) { + catchDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0; + return; + } + + reel(); + } } + } - if (catchDelayLeft > 0) { - catchDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0; - return; - } + private void cast() { + useRod(); + state = State.WAITING_FOR_BITE; + } + private void reel() { useRod(); + state = State.IDLE; } private void useRod() { mc.gameMode.useItem(mc.player, fishingHand); - wasHooked = false; castDelayLeft = randomizeDelay(castDelay.get(), castDelayVariance.get()); } + private boolean isUsableRod(ItemStack stack) { + if (!(stack.getItem() instanceof FishingRodItem)) return false; + return !antiBreak.get() || stack.getDamageValue() != stack.getMaxDamage() - 1; + } + private int findBestRod() { int bestSlot = -1; int bestScore = -1; for (int i = 0; i < 9; i++) { ItemStack stack = mc.player.getInventory().getItem(i); - if (!(stack.getItem() instanceof FishingRodItem)) continue; - if (antiBreak.get() && stack.getDamageValue() == stack.getMaxDamage() - 1) continue; + if (!isUsableRod(stack)) continue; int score = 0; From 9529f32b3ed7f36d7e548e5d3b9dfa1f5627dff5 Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Wed, 9 Sep 2026 12:18:13 +0200 Subject: [PATCH 3/5] fix(auto-fish): resolve fishing rod hand dynamically --- .../systems/modules/player/AutoFish.java | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java index 76f784b3a3b..423dc510370 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java @@ -90,8 +90,6 @@ private enum State { } private State state = State.IDLE; - private InteractionHand fishingHand = null; - private double castDelayLeft = 0.0; private double catchDelayLeft = 0.0; @@ -102,8 +100,6 @@ public AutoFish() { @Override public void onActivate() { state = State.IDLE; - fishingHand = null; - castDelayLeft = 0.0; catchDelayLeft = 0.0; } @@ -111,7 +107,6 @@ public void onActivate() { @Override public void onDeactivate() { state = State.IDLE; - fishingHand = null; } @EventHandler @@ -121,23 +116,18 @@ private void onTick(TickEvent.Pre event) { } private void handleIdle() { - if (state != State.IDLE) { - state = State.IDLE; - fishingHand = null; - } + if (state != State.IDLE) state = State.IDLE; - if (isUsableRod(mc.player.getOffhandItem())) { - fishingHand = InteractionHand.OFF_HAND; - } else { + InteractionHand fishingHand = getRodHand(); + + if (fishingHand == null) { int bestRodSlot = findBestRod(); if (autoSwitch.get() && bestRodSlot != -1 && mc.player.getInventory().getSelectedSlot() != bestRodSlot) { InvUtils.swap(bestRodSlot, false); } - if (isUsableRod(mc.player.getMainHandItem())) { - fishingHand = InteractionHand.MAIN_HAND; - } + fishingHand = getRodHand(); } if (fishingHand == null) return; @@ -151,8 +141,6 @@ private void handleIdle() { } private void handleFishing() { - if (fishingHand == null) return; - switch (state) { case IDLE -> state = State.WAITING_FOR_BITE; case WAITING_FOR_BITE -> { @@ -185,18 +173,27 @@ private void handleFishing() { } private void cast() { - useRod(); - state = State.WAITING_FOR_BITE; + if (useRod()) state = State.WAITING_FOR_BITE; } private void reel() { - useRod(); - state = State.IDLE; + if (useRod()) state = State.IDLE; } - private void useRod() { + private boolean useRod() { + InteractionHand fishingHand = getRodHand(); + if (fishingHand == null) return false; + mc.gameMode.useItem(mc.player, fishingHand); castDelayLeft = randomizeDelay(castDelay.get(), castDelayVariance.get()); + return true; + } + + private InteractionHand getRodHand() { + if (isUsableRod(mc.player.getOffhandItem())) return InteractionHand.OFF_HAND; + if (isUsableRod(mc.player.getMainHandItem())) return InteractionHand.MAIN_HAND; + + return null; } private boolean isUsableRod(ItemStack stack) { From 113c880fe8b44be45dc6dffb244735908dda101b Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:25:17 +0200 Subject: [PATCH 4/5] fix(auto-fish): select best available fishing rod Select the highest-scoring usable rod from the hotbar and offhand when casting or reeling. Preserve selected-hotbar-first tie-breaking and respect auto-switch and anti-break behavior. --- .../systems/modules/player/AutoFish.java | 96 ++++++++++++------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java index 423dc510370..db9f027e17f 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java @@ -118,20 +118,11 @@ private void onTick(TickEvent.Pre event) { private void handleIdle() { if (state != State.IDLE) state = State.IDLE; - InteractionHand fishingHand = getRodHand(); - - if (fishingHand == null) { - int bestRodSlot = findBestRod(); - - if (autoSwitch.get() && bestRodSlot != -1 && mc.player.getInventory().getSelectedSlot() != bestRodSlot) { - InvUtils.swap(bestRodSlot, false); - } - - fishingHand = getRodHand(); - } - - if (fishingHand == null) return; + RodCandidate candidate = findBestRodCandidate(); + if (candidate == null) return; + if (!prepareRod(candidate)) return; if (!autoCast.get()) return; + if (castDelayLeft > 0) { castDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0; return; @@ -150,7 +141,6 @@ private void handleFishing() { } if (mc.player.fishing.currentState != FishingHook.FishHookState.BOBBING) return; - if (((FishingHookAccessor) mc.player.fishing).meteor$hasCaughtFish()) { catchDelayLeft = randomizeDelay(catchDelay.get(), catchDelayVariance.get()); state = State.WAITING_TO_REEL; @@ -181,19 +171,23 @@ private void reel() { } private boolean useRod() { - InteractionHand fishingHand = getRodHand(); - if (fishingHand == null) return false; + RodCandidate candidate = findBestRodCandidate(); + if (candidate == null) return false; + if (!prepareRod(candidate)) return false; - mc.gameMode.useItem(mc.player, fishingHand); + mc.gameMode.useItem(mc.player, candidate.hand()); castDelayLeft = randomizeDelay(castDelay.get(), castDelayVariance.get()); return true; } - private InteractionHand getRodHand() { - if (isUsableRod(mc.player.getOffhandItem())) return InteractionHand.OFF_HAND; - if (isUsableRod(mc.player.getMainHandItem())) return InteractionHand.MAIN_HAND; + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean prepareRod(RodCandidate candidate) { + if (candidate.hand() == InteractionHand.OFF_HAND) return true; + if (candidate.hotbarSlot() == mc.player.getInventory().getSelectedSlot()) return true; + if (!autoSwitch.get()) return false; - return null; + InvUtils.swap(candidate.hotbarSlot(), false); + return candidate.hotbarSlot() == mc.player.getInventory().getSelectedSlot(); } private boolean isUsableRod(ItemStack stack) { @@ -201,31 +195,61 @@ private boolean isUsableRod(ItemStack stack) { return !antiBreak.get() || stack.getDamageValue() != stack.getMaxDamage() - 1; } - private int findBestRod() { - int bestSlot = -1; - int bestScore = -1; + private int scoreRod(ItemStack stack) { + int score = 0; + + score += Utils.getEnchantmentLevel(stack, Enchantments.LUCK_OF_THE_SEA); + score += Utils.getEnchantmentLevel(stack, Enchantments.LURE); + score += Utils.getEnchantmentLevel(stack, Enchantments.MENDING); + score += Utils.getEnchantmentLevel(stack, Enchantments.UNBREAKING); + + return score; + } + + /// Finds the best rod candidate in the player's inventory and offhand. + /// + /// Tie-break order: + /// 1. Selected hotbar slot + /// 2. Remaining hotbar slots in stable 0..8 order + /// 3. Offhand + /// + /// Candidates only replace the current best when they have a strictly higher score, + /// preserving the ordering above for equal scores. + private RodCandidate findBestRodCandidate() { + int selectedSlot = mc.player.getInventory().getSelectedSlot(); + RodCandidate best = null; + + ItemStack mainHandStack = mc.player.getMainHandItem(); + if (isUsableRod(mainHandStack)) { + best = new RodCandidate(InteractionHand.MAIN_HAND, selectedSlot, scoreRod(mainHandStack)); + } for (int i = 0; i < 9; i++) { + if (i == selectedSlot) continue; + ItemStack stack = mc.player.getInventory().getItem(i); if (!isUsableRod(stack)) continue; - int score = 0; - - score += Utils.getEnchantmentLevel(stack, Enchantments.LUCK_OF_THE_SEA); - score += Utils.getEnchantmentLevel(stack, Enchantments.LURE); - score += Utils.getEnchantmentLevel(stack, Enchantments.MENDING); - score += Utils.getEnchantmentLevel(stack, Enchantments.UNBREAKING); + int score = scoreRod(stack); - if (score > bestScore) { - bestScore = score; - bestSlot = i; + if (best == null || score > best.score()) { + best = new RodCandidate(InteractionHand.MAIN_HAND, i, score); } + } + + ItemStack offhandStack = mc.player.getOffhandItem(); + if (isUsableRod(offhandStack)) { + int score = scoreRod(offhandStack); - // Found a maxed out rod - if (score == 10) break; + if (best == null || score > best.score()) { + best = new RodCandidate(InteractionHand.OFF_HAND, -1, score); + } } - return bestSlot; + return best; + } + + private record RodCandidate(InteractionHand hand, int hotbarSlot, int score) { } private double randomizeDelay(int delay, int variance) { From 9f07248e9da8a0d6d3e7cb443da0e0e089b40cb6 Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Thu, 10 Sep 2026 00:04:22 +0200 Subject: [PATCH 5/5] chore(auto-fish): satisfy jspecify nullability --- .../meteorclient/systems/modules/player/AutoFish.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java index db9f027e17f..020adfeebf3 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java @@ -22,7 +22,10 @@ import net.minecraft.world.item.FishingRodItem; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.Enchantments; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class AutoFish extends Module { private final SettingGroup sgGeneral = settings.getDefaultGroup(); @@ -215,7 +218,7 @@ private int scoreRod(ItemStack stack) { /// /// Candidates only replace the current best when they have a strictly higher score, /// preserving the ordering above for equal scores. - private RodCandidate findBestRodCandidate() { + private @Nullable RodCandidate findBestRodCandidate() { int selectedSlot = mc.player.getInventory().getSelectedSlot(); RodCandidate best = null;