Skip to content

Commit 3128d5d

Browse files
feat(auto-fish): add offhand fishing rod support (#6653)
1 parent 2e45360 commit 3128d5d

1 file changed

Lines changed: 126 additions & 56 deletions

File tree

  • src/main/java/meteordevelopment/meteorclient/systems/modules/player

‎src/main/java/meteordevelopment/meteorclient/systems/modules/player/AutoFish.java‎

Lines changed: 126 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@
1717
import meteordevelopment.meteorclient.utils.player.InvUtils;
1818
import meteordevelopment.meteorclient.utils.world.TickRate;
1919
import meteordevelopment.orbit.EventHandler;
20+
import net.minecraft.world.InteractionHand;
2021
import net.minecraft.world.entity.projectile.FishingHook;
2122
import net.minecraft.world.item.FishingRodItem;
2223
import net.minecraft.world.item.ItemStack;
2324
import net.minecraft.world.item.enchantment.Enchantments;
25+
import org.jspecify.annotations.NullMarked;
26+
import org.jspecify.annotations.Nullable;
2427

28+
@NullMarked
2529
public class AutoFish extends Module {
2630
private final SettingGroup sgGeneral = settings.getDefaultGroup();
2731

@@ -82,107 +86,173 @@ public class AutoFish extends Module {
8286
.build()
8387
);
8488

85-
public AutoFish() {
86-
super(Categories.Player, "auto-fish", "Automatically fishes for you.");
89+
private enum State {
90+
IDLE,
91+
WAITING_FOR_BITE,
92+
WAITING_TO_REEL
8793
}
8894

95+
private State state = State.IDLE;
8996
private double castDelayLeft = 0.0;
9097
private double catchDelayLeft = 0.0;
91-
private boolean wasHooked = false;
98+
99+
public AutoFish() {
100+
super(Categories.Player, "auto-fish", "Automatically fishes for you.");
101+
}
92102

93103
@Override
94104
public void onActivate() {
105+
state = State.IDLE;
95106
castDelayLeft = 0.0;
96107
catchDelayLeft = 0.0;
108+
}
97109

98-
wasHooked = false;
110+
@Override
111+
public void onDeactivate() {
112+
state = State.IDLE;
99113
}
100114

101115
@EventHandler
102116
private void onTick(TickEvent.Pre event) {
103-
int bestRodSlot = findBestRod();
104-
105-
if (autoSwitch.get() && bestRodSlot != -1 && mc.player.getInventory().getSelectedSlot() != bestRodSlot) {
106-
InvUtils.swap(bestRodSlot, false);
107-
}
108-
109-
if (!(mc.player.getMainHandItem().getItem() instanceof FishingRodItem)) return;
110-
111-
tryCast();
112-
tryCatch();
117+
if (mc.player.fishing != null) handleFishing();
118+
else handleIdle();
113119
}
114120

115-
private void tryCast() {
116-
if (mc.player.fishing != null) return;
121+
private void handleIdle() {
122+
if (state != State.IDLE) state = State.IDLE;
117123

124+
RodCandidate candidate = findBestRodCandidate();
125+
if (candidate == null) return;
126+
if (!prepareRod(candidate)) return;
118127
if (!autoCast.get()) return;
119128

120129
if (castDelayLeft > 0) {
121130
castDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0;
122131
return;
123132
}
124133

125-
useRod();
134+
cast();
126135
}
127136

128-
private void tryCatch() {
129-
if (mc.player.fishing == null) return;
130-
if (mc.player.fishing.getHookedIn() != null) {
131-
useRod();
132-
return;
137+
private void handleFishing() {
138+
switch (state) {
139+
case IDLE -> state = State.WAITING_FOR_BITE;
140+
case WAITING_FOR_BITE -> {
141+
if (mc.player.fishing.getHookedIn() != null) {
142+
reel();
143+
return;
144+
}
145+
146+
if (mc.player.fishing.currentState != FishingHook.FishHookState.BOBBING) return;
147+
if (((FishingHookAccessor) mc.player.fishing).meteor$hasCaughtFish()) {
148+
catchDelayLeft = randomizeDelay(catchDelay.get(), catchDelayVariance.get());
149+
state = State.WAITING_TO_REEL;
150+
}
151+
}
152+
case WAITING_TO_REEL -> {
153+
if (mc.player.fishing.getHookedIn() != null) {
154+
reel();
155+
return;
156+
}
157+
158+
if (catchDelayLeft > 0) {
159+
catchDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0;
160+
return;
161+
}
162+
163+
reel();
164+
}
133165
}
166+
}
134167

135-
if (mc.player.fishing.currentState != FishingHook.FishHookState.BOBBING) return;
168+
private void cast() {
169+
if (useRod()) state = State.WAITING_FOR_BITE;
170+
}
136171

137-
if (!wasHooked) {
138-
if (((FishingHookAccessor) mc.player.fishing).meteor$hasCaughtFish()) {
139-
catchDelayLeft = randomizeDelay(catchDelay.get(), catchDelayVariance.get());
140-
wasHooked = true;
141-
}
172+
private void reel() {
173+
if (useRod()) state = State.IDLE;
174+
}
142175

143-
return;
144-
}
176+
private boolean useRod() {
177+
RodCandidate candidate = findBestRodCandidate();
178+
if (candidate == null) return false;
179+
if (!prepareRod(candidate)) return false;
145180

146-
if (catchDelayLeft > 0) {
147-
catchDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0;
148-
return;
149-
}
181+
mc.gameMode.useItem(mc.player, candidate.hand());
182+
castDelayLeft = randomizeDelay(castDelay.get(), castDelayVariance.get());
183+
return true;
184+
}
150185

151-
useRod();
186+
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
187+
private boolean prepareRod(RodCandidate candidate) {
188+
if (candidate.hand() == InteractionHand.OFF_HAND) return true;
189+
if (candidate.hotbarSlot() == mc.player.getInventory().getSelectedSlot()) return true;
190+
if (!autoSwitch.get()) return false;
191+
192+
InvUtils.swap(candidate.hotbarSlot(), false);
193+
return candidate.hotbarSlot() == mc.player.getInventory().getSelectedSlot();
152194
}
153195

154-
private void useRod() {
155-
Utils.rightClick();
156-
wasHooked = false;
157-
castDelayLeft = randomizeDelay(castDelay.get(), castDelayVariance.get());
196+
private boolean isUsableRod(ItemStack stack) {
197+
if (!(stack.getItem() instanceof FishingRodItem)) return false;
198+
return !antiBreak.get() || stack.getDamageValue() != stack.getMaxDamage() - 1;
158199
}
159200

160-
private int findBestRod() {
161-
int bestSlot = -1;
162-
int bestScore = -1;
201+
private int scoreRod(ItemStack stack) {
202+
int score = 0;
203+
204+
score += Utils.getEnchantmentLevel(stack, Enchantments.LUCK_OF_THE_SEA);
205+
score += Utils.getEnchantmentLevel(stack, Enchantments.LURE);
206+
score += Utils.getEnchantmentLevel(stack, Enchantments.MENDING);
207+
score += Utils.getEnchantmentLevel(stack, Enchantments.UNBREAKING);
208+
209+
return score;
210+
}
211+
212+
/// Finds the best rod candidate in the player's inventory and offhand.
213+
///
214+
/// Tie-break order:
215+
/// 1. Selected hotbar slot
216+
/// 2. Remaining hotbar slots in stable 0..8 order
217+
/// 3. Offhand
218+
///
219+
/// Candidates only replace the current best when they have a strictly higher score,
220+
/// preserving the ordering above for equal scores.
221+
private @Nullable RodCandidate findBestRodCandidate() {
222+
int selectedSlot = mc.player.getInventory().getSelectedSlot();
223+
RodCandidate best = null;
224+
225+
ItemStack mainHandStack = mc.player.getMainHandItem();
226+
if (isUsableRod(mainHandStack)) {
227+
best = new RodCandidate(InteractionHand.MAIN_HAND, selectedSlot, scoreRod(mainHandStack));
228+
}
163229

164230
for (int i = 0; i < 9; i++) {
165-
ItemStack stack = mc.player.getInventory().getItem(i);
166-
if (!(stack.getItem() instanceof FishingRodItem)) continue;
167-
if (antiBreak.get() && stack.getDamageValue() == stack.getMaxDamage() - 1) continue;
231+
if (i == selectedSlot) continue;
168232

169-
int score = 0;
233+
ItemStack stack = mc.player.getInventory().getItem(i);
234+
if (!isUsableRod(stack)) continue;
170235

171-
score += Utils.getEnchantmentLevel(stack, Enchantments.LUCK_OF_THE_SEA);
172-
score += Utils.getEnchantmentLevel(stack, Enchantments.LURE);
173-
score += Utils.getEnchantmentLevel(stack, Enchantments.MENDING);
174-
score += Utils.getEnchantmentLevel(stack, Enchantments.UNBREAKING);
236+
int score = scoreRod(stack);
175237

176-
if (score > bestScore) {
177-
bestScore = score;
178-
bestSlot = i;
238+
if (best == null || score > best.score()) {
239+
best = new RodCandidate(InteractionHand.MAIN_HAND, i, score);
179240
}
241+
}
180242

181-
// Found a maxed out rod
182-
if (score == 10) break;
243+
ItemStack offhandStack = mc.player.getOffhandItem();
244+
if (isUsableRod(offhandStack)) {
245+
int score = scoreRod(offhandStack);
246+
247+
if (best == null || score > best.score()) {
248+
best = new RodCandidate(InteractionHand.OFF_HAND, -1, score);
249+
}
183250
}
184251

185-
return bestSlot;
252+
return best;
253+
}
254+
255+
private record RodCandidate(InteractionHand hand, int hotbarSlot, int score) {
186256
}
187257

188258
private double randomizeDelay(int delay, int variance) {

0 commit comments

Comments
 (0)