Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
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;
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();

Expand Down Expand Up @@ -82,107 +86,173 @@ 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 double castDelayLeft = 0.0;
private double catchDelayLeft = 0.0;
private boolean wasHooked = false;

public AutoFish() {
super(Categories.Player, "auto-fish", "Automatically fishes for you.");
}

@Override
public void onActivate() {
state = State.IDLE;
castDelayLeft = 0.0;
catchDelayLeft = 0.0;
}

wasHooked = false;
@Override
public void onDeactivate() {
state = State.IDLE;
}

@EventHandler
private void onTick(TickEvent.Pre event) {
int bestRodSlot = findBestRod();

if (autoSwitch.get() && bestRodSlot != -1 && mc.player.getInventory().getSelectedSlot() != bestRodSlot) {
InvUtils.swap(bestRodSlot, false);
}

if (!(mc.player.getMainHandItem().getItem() instanceof FishingRodItem)) return;

tryCast();
tryCatch();
if (mc.player.fishing != null) handleFishing();
else handleIdle();
}

private void tryCast() {
if (mc.player.fishing != null) return;
private void handleIdle() {
if (state != State.IDLE) state = State.IDLE;

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;
}

useRod();
cast();
}

private void tryCatch() {
if (mc.player.fishing == null) return;
if (mc.player.fishing.getHookedIn() != null) {
useRod();
return;
private void handleFishing() {
switch (state) {
case IDLE -> state = State.WAITING_FOR_BITE;
case WAITING_FOR_BITE -> {
if (mc.player.fishing.getHookedIn() != null) {
reel();
return;
}

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;
}
}
case WAITING_TO_REEL -> {
if (mc.player.fishing.getHookedIn() != null) {
reel();
return;
}

if (catchDelayLeft > 0) {
catchDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0;
return;
}

reel();
}
}
}

if (mc.player.fishing.currentState != FishingHook.FishHookState.BOBBING) return;
private void cast() {
if (useRod()) state = State.WAITING_FOR_BITE;
}

if (!wasHooked) {
if (((FishingHookAccessor) mc.player.fishing).meteor$hasCaughtFish()) {
catchDelayLeft = randomizeDelay(catchDelay.get(), catchDelayVariance.get());
wasHooked = true;
}
private void reel() {
if (useRod()) state = State.IDLE;
}

return;
}
private boolean useRod() {
RodCandidate candidate = findBestRodCandidate();
if (candidate == null) return false;
if (!prepareRod(candidate)) return false;

if (catchDelayLeft > 0) {
catchDelayLeft -= TickRate.INSTANCE.getTickRate() / 20.0;
return;
}
mc.gameMode.useItem(mc.player, candidate.hand());
castDelayLeft = randomizeDelay(castDelay.get(), castDelayVariance.get());
return true;
}

useRod();
@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;

InvUtils.swap(candidate.hotbarSlot(), false);
return candidate.hotbarSlot() == mc.player.getInventory().getSelectedSlot();
}

private void useRod() {
Utils.rightClick();
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;
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 @Nullable 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++) {
ItemStack stack = mc.player.getInventory().getItem(i);
if (!(stack.getItem() instanceof FishingRodItem)) continue;
if (antiBreak.get() && stack.getDamageValue() == stack.getMaxDamage() - 1) continue;
if (i == selectedSlot) continue;

int score = 0;
ItemStack stack = mc.player.getInventory().getItem(i);
if (!isUsableRod(stack)) continue;

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);
}
}

// Found a maxed out rod
if (score == 10) break;
ItemStack offhandStack = mc.player.getOffhandItem();
if (isUsableRod(offhandStack)) {
int score = scoreRod(offhandStack);

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) {
Expand Down
Loading