From 8b7fcf3a78b246880e1f85d296f547387e13d9b6 Mon Sep 17 00:00:00 2001 From: mmichlol Date: Mon, 1 Jun 2026 12:19:01 +0000 Subject: [PATCH] Upload files to "/" --- AStarPathfinder.java | 15 +- PathExecutor.java | 485 ++++++++++++++++++++++++++----------- PathfindingController.java | 16 +- 3 files changed, 362 insertions(+), 154 deletions(-) diff --git a/AStarPathfinder.java b/AStarPathfinder.java index e503df6..86c8882 100644 --- a/AStarPathfinder.java +++ b/AStarPathfinder.java @@ -19,7 +19,8 @@ public class AStarPathfinder { }; private static final double SQRT2 = 1.41421356; - public static List find(ClientWorld world, BlockPos start, BlockPos goal, boolean flying) { + + public static List find(ClientWorld world, BlockPos start, BlockPos goal, boolean flying, java.util.Set blacklistedNodes) { PriorityQueue open = new PriorityQueue<>(); Map gScore = new HashMap<>(); @@ -36,7 +37,7 @@ public class AStarPathfinder { return smooth(world, raw, flying); } - for (PathNode nb : neighbours(world, current, goal, flying)) { + for (PathNode nb : neighbours(world, current, goal, flying, blacklistedNodes)) { double existing = gScore.getOrDefault(nb.pos, Double.MAX_VALUE); if (nb.g < existing) { gScore.put(nb.pos, nb.g); @@ -120,16 +121,15 @@ public class AStarPathfinder { int steps = Math.max(dx, Math.max(dy, dz)); for (int i = 0; i < steps; i++) { - BlockPos pos = new BlockPos(cx, cy, cz); - // Sprawdź czy gracz (2 bloki wysokości) przejdzie - if (!isPassable(world, pos) || !isPassable(world, pos.up())) return false; + if (!isPassable(world, new BlockPos(cx, cy, cz))) return false; + if (!isPassable(world, new BlockPos(cx, cy + 1, cz))) return false; int e2XY = 2 * errXY; int e2XZ = 2 * errXZ; if (e2XY > -dy) { errXY -= dy; cx += sx; } if (e2XY < dx) { errXY += dx; cy += sy; } - if (e2XZ > -dz) { errXZ -= dz; } + if (e2XZ > -dz) { errXZ -= dz; cx += sx; } if (e2XZ < dx) { errXZ += dx; cz += sz; } } return true; @@ -160,9 +160,10 @@ public class AStarPathfinder { return true; } - private static List neighbours(ClientWorld world, PathNode current, BlockPos goal, boolean flying) { + private static List neighbours(ClientWorld world, PathNode current, BlockPos goal, boolean flying, java.util.Set blacklist) { List result = new ArrayList<>(); BlockPos pos = current.pos; + if (blacklist.contains(pos)) return result; if (flying) { // ── Tryb lotu — ruch we wszystkich 26 kierunkach ────────────────── diff --git a/PathExecutor.java b/PathExecutor.java index 9d6c168..f1fab98 100644 --- a/PathExecutor.java +++ b/PathExecutor.java @@ -8,79 +8,151 @@ import net.minecraft.world.RaycastContext; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.HitResult; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; public class PathExecutor { - private static final double SPRINT_ANGLE = 35.0; // max yaw diff to allow sprint - // ── Pola ────────────────────────────────────────────────────────────────── - private float yawVelocity = 0f; - private float pitchVelocity = 0f; - private Vec3d smoothLookPoint = null; - private boolean flying = false; + // ══════════════════════════════════════════════════════════════════════════ + // STAŁE + // ══════════════════════════════════════════════════════════════════════════ - // ── Stałe ───────────────────────────────────────────────────────────────── - private static final float SPRING_STIFFNESS = 0.18f; - private static final float SPRING_DAMPING = 0.72f; - private static final float MAX_VELOCITY = 18.0f; - private static final float PITCH_LOOK_AHEAD = 6.0f; - private static final float LOOK_POINT_LERP = 0.08f; - private volatile float prevYaw = 0f; - private volatile float prevPitch = 0f; - private volatile float nextYaw = 0f; - private volatile float nextPitch = 0f; + private static final double SPRINT_ANGLE = 35.0; // max kąt yaw do sprintu + private static final float SPRING_STIFFNESS = 0.18f; + private static final float SPRING_DAMPING = 0.72f; + private static final float MAX_VELOCITY = 18.0f; + private static final float PITCH_LOOK_AHEAD = 6.0f; + private static final float LOOK_POINT_LERP = 0.08f; - private float cameraYaw = 0f; - private float cameraPitch = 0f; + // Lookahead + private static final int HOLD_TICKS = 10; + private static final float NEAR_THRESHOLD = 3.5f; + + // Stuck detection + private static final int STUCK_TICKS = 26; // ~1.3s przy 20 TPS + private static final float STUCK_MIN_DIST = 0.3f; + + // ══════════════════════════════════════════════════════════════════════════ + // INTERPOLACJA KAMERY (atomic frame dla render threadu) + // ══════════════════════════════════════════════════════════════════════════ + + private record YawPitchFrame(float prevYaw, float prevPitch, + float nextYaw, float nextPitch) {} + + private volatile YawPitchFrame frame = new YawPitchFrame(0, 0, 0, 0); + + // ══════════════════════════════════════════════════════════════════════════ + // STAN KAMERY + // ══════════════════════════════════════════════════════════════════════════ + + private float cameraYaw = 0f; + private float cameraPitch = 0f; private boolean cameraInitialized = false; + private float currentMoveYaw = 0f; // yaw do aktualnego waypointu (nie lookahead) + + private float yawVelocity = 0f; + private float pitchVelocity = 0f; + private Vec3d smoothLookPoint = null; + + // Lookahead state + private int lookTargetIndex = 0; + private int lookHoldTicks = 0; + + // ══════════════════════════════════════════════════════════════════════════ + // STAN PATHFINDINGU + // ══════════════════════════════════════════════════════════════════════════ private List path; - private int waypointIndex = 0; - private boolean active = false; + private int waypointIndex = 0; + private boolean active = false; + private boolean flying = false; - // Read by MixinKeyboardInput each tick private volatile PathInput currentInput = PathInput.NONE; - // ── Public API ───────────────────────────────────────────────────────── + // Waypoint offsety (randomizacja pozycji w bloku) + private final Random rng = new Random(); + private final Map waypointOffsets = new HashMap<>(); + + // ══════════════════════════════════════════════════════════════════════════ + // STUCK DETECTION + // ══════════════════════════════════════════════════════════════════════════ + + private Vec3d lastStuckCheckPos = null; + private int stuckTickCounter = 0; + private final Set blacklistedNodes = new HashSet<>(); + + // ══════════════════════════════════════════════════════════════════════════ + // PUBLIC API + // ══════════════════════════════════════════════════════════════════════════ public synchronized void start(List newPath) { - this.path = newPath; + this.path = newPath; this.waypointIndex = 0; - this.active = !newPath.isEmpty(); - this.currentInput = PathInput.NONE; + this.active = !newPath.isEmpty(); + this.currentInput = PathInput.NONE; } + /** Zatrzymaj pathfinding — blacklista zostaje (potrzebna przy retry po stucku). */ public synchronized void stop(MinecraftClient client) { - active = false; - currentInput = PathInput.NONE; - yawVelocity = 0f; // ← dodaj - pitchVelocity = 0f; // ← dodaj - smoothLookPoint = null; // ← dodaj + active = false; + currentInput = PathInput.NONE; + yawVelocity = 0f; + pitchVelocity = 0f; + smoothLookPoint = null; + cameraInitialized = false; + currentMoveYaw = 0f; + lookTargetIndex = 0; + lookHoldTicks = 0; + waypointIndex = 0; + waypointOffsets.clear(); + lastStuckCheckPos = null; + stuckTickCounter = 0; if (client.player != null) client.player.setSprinting(false); } - public synchronized boolean isActive() { return active; } - public synchronized int getWaypointIndex(){ return waypointIndex; } - public synchronized int getPathLength() { return path != null ? path.size() : 0; } + /** Pełny reset — wywołuj tylko przy ręcznym zatrzymaniu przez gracza. */ + public synchronized void reset(MinecraftClient client) { + stop(client); + blacklistedNodes.clear(); + } + + public synchronized void clearBlacklist() { + blacklistedNodes.clear(); + } + + public Set getBlacklist() { return blacklistedNodes; } + + public synchronized boolean isActive() { return active; } + public synchronized int getWaypointIndex() { return waypointIndex; } + public synchronized int getPathLength() { return path != null ? path.size() : 0; } + public synchronized List getPath() { return path; } public synchronized BlockPos getGoal() { return (path != null && !path.isEmpty()) ? path.get(path.size() - 1) : null; } - public synchronized List getPath() { return path; } - public PathInput getCurrentInput() { return active ? currentInput : null; } + public boolean isFlying() { return flying; } + public void setFlying(boolean f) { this.flying = f; } + + // ══════════════════════════════════════════════════════════════════════════ + // TICK GŁÓWNY + // ══════════════════════════════════════════════════════════════════════════ + public synchronized void tick(MinecraftClient client) { if (!active || path == null || client.player == null) { currentInput = PathInput.NONE; return; } - // ── Włącz/wyłącz latanie ────────────────────────────────────────────── if (flying) { - client.player.getAbilities().flying = true; + client.player.getAbilities().flying = true; client.player.getAbilities().allowFlying = true; } @@ -91,16 +163,22 @@ public class PathExecutor { return; } + checkStuck(client.player, client); + BlockPos target = path.get(waypointIndex); rotateCamera(client.player, target, client); currentInput = computeInput(client.player, target); boolean shouldSprint = currentInput.sprint() && !currentInput.jumping() - && (flying || client.player.isOnGround()); // ← flying nie wymaga gruntu + && (flying || client.player.isOnGround()); client.player.setSprinting(shouldSprint); } + // ══════════════════════════════════════════════════════════════════════════ + // KAMERA — rotacja i interpolacja + // ══════════════════════════════════════════════════════════════════════════ + private void rotateCamera(ClientPlayerEntity player, BlockPos target, MinecraftClient client) { if (!cameraInitialized) { cameraYaw = player.getYaw(); @@ -108,6 +186,7 @@ public class PathExecutor { cameraInitialized = true; } + // ── Look target (lookahead) ─────────────────────────────────────────── BlockPos lookTarget = chooseLookTarget(player, client); Vec3d rawLookPoint = Vec3d.ofCenter(lookTarget).add(0, 0.5, 0); @@ -117,109 +196,202 @@ public class PathExecutor { smoothLookPoint = smoothLookPoint.lerp(rawLookPoint, LOOK_POINT_LERP); } - Vec3d playerPos = new Vec3d(player.getX(), player.getEyeY(), player.getZ()); - double dx = smoothLookPoint.x - playerPos.x; - double dz = smoothLookPoint.z - playerPos.z; - double dy = smoothLookPoint.y - playerPos.y; + // ── Oblicz target yaw/pitch z smoothLookPoint ───────────────────────── + Vec3d playerEye = new Vec3d(player.getX(), player.getEyeY(), player.getZ()); + double dx = smoothLookPoint.x - playerEye.x; + double dz = smoothLookPoint.z - playerEye.z; + double dy = smoothLookPoint.y - playerEye.y; float targetYaw = (float) Math.toDegrees(Math.atan2(-dx, dz)); float targetPitch = (float) -Math.toDegrees( Math.atan2(dy, Math.sqrt(dx * dx + dz * dz))); targetPitch = Math.max(-90f, Math.min(90f, targetPitch - PITCH_LOOK_AHEAD)); - float yawError = (float) angleDiff(targetYaw, cameraYaw); - float yawAccel = SPRING_STIFFNESS * yawError - SPRING_DAMPING * yawVelocity; - yawVelocity += yawAccel; - yawVelocity = Math.max(-MAX_VELOCITY, Math.min(MAX_VELOCITY, yawVelocity)); + // ── Spring damper — yaw ─────────────────────────────────────────────── + float yawError = (float) angleDiff(targetYaw, cameraYaw); + float yawAccel = SPRING_STIFFNESS * yawError - SPRING_DAMPING * yawVelocity; + yawVelocity += yawAccel; + yawVelocity = Math.max(-MAX_VELOCITY, Math.min(MAX_VELOCITY, yawVelocity)); - float pitchError = targetPitch - cameraPitch; - float pitchAccel = SPRING_STIFFNESS * pitchError - SPRING_DAMPING * pitchVelocity; - pitchVelocity += pitchAccel; - pitchVelocity = Math.max(-MAX_VELOCITY, Math.min(MAX_VELOCITY, pitchVelocity)); + // ── Spring damper — pitch ───────────────────────────────────────────── + float pitchError = targetPitch - cameraPitch; + float pitchAccel = SPRING_STIFFNESS * pitchError - SPRING_DAMPING * pitchVelocity; + pitchVelocity += pitchAccel; + pitchVelocity = Math.max(-MAX_VELOCITY, Math.min(MAX_VELOCITY, pitchVelocity)); - prevYaw = cameraYaw; - prevPitch = cameraPitch; + float oldYaw = cameraYaw; + float oldPitch = cameraPitch; cameraYaw += yawVelocity; cameraPitch += pitchVelocity; cameraPitch = Math.max(-90f, Math.min(90f, cameraPitch)); - nextYaw = cameraYaw; - nextPitch = cameraPitch; + // Atomowy zapis dla render threadu + frame = new YawPitchFrame(oldYaw, oldPitch, cameraYaw, cameraPitch); player.setYaw(cameraYaw); player.setPitch(cameraPitch); - } - public float getInterpolatedYaw(float tickDelta) { - float diff = (float) angleDiff(nextYaw, prevYaw); - return prevYaw + diff * tickDelta; - } - public float getInterpolatedPitch(float tickDelta) { - return prevPitch + (nextPitch - prevPitch) * tickDelta; + + // ── moveYaw — osobny kąt dla nóg (do aktualnego waypointu, nie lookahead) ── + Vec3d playerFeet = player.getEntityPos(); + Vec3d wpTarget = getWaypointTarget(path.get(waypointIndex)); + double mdx = wpTarget.x - playerFeet.x; + double mdz = wpTarget.z - playerFeet.z; + float rawMoveYaw = (float) Math.toDegrees(Math.atan2(-mdx, mdz)); + float moveDiff = (float) angleDiff(rawMoveYaw, currentMoveYaw); + currentMoveYaw += moveDiff * 0.3f; // lerp 30% — wygładza drobne wahania } + public float getInterpolatedYaw(float tickDelta) { + YawPitchFrame f = frame; + float diff = (float) angleDiff(f.nextYaw(), f.prevYaw()); + return f.prevYaw() + diff * tickDelta; + } + + public float getInterpolatedPitch(float tickDelta) { + YawPitchFrame f = frame; + return f.prevPitch() + (f.nextPitch() - f.prevPitch()) * tickDelta; + } + + // ══════════════════════════════════════════════════════════════════════════ + // LOOKAHEAD — wybór punktu do patrzenia + // ══════════════════════════════════════════════════════════════════════════ private BlockPos chooseLookTarget(ClientPlayerEntity player, MinecraftClient client) { - Vec3d eyePos = player.getEyePos(); + Vec3d eyePos = player.getEyePos(); + Vec3d playerPos = player.getEntityPos(); + int MAX_LOOKAHEAD = 4; + int bestIdx = waypointIndex; - int LOOKAHEAD_STEPS = 3; - float MIN_LOOKAHEAD_DIST = 4.0f; - for (int i = LOOKAHEAD_STEPS; i >= 1; i--) { - int idx = Math.min(waypointIndex + i, path.size() - 1); - BlockPos candidate = path.get(idx); - Vec3d candidateVec = Vec3d.ofCenter(candidate).add(0, 0.5, 0); + for (int i = 1; i <= MAX_LOOKAHEAD; i++) { + int idx = waypointIndex + i; + if (idx >= path.size()) break; - if (eyePos.distanceTo(candidateVec) < MIN_LOOKAHEAD_DIST) continue; + BlockPos candidate = path.get(idx); + Vec3d candidateVec = Vec3d.ofCenter(candidate).add(0, 0.5, 0); + double distToCandidate = eyePos.distanceTo(candidateVec); + + // ── Sprawdź czy między aktualnym a kandydatem jest zmiana wysokości ── + // Jeśli którykolwiek waypoint po drodze ma inną Y — zatrzymaj lookahead + boolean heightChangeOnPath = false; + for (int j = waypointIndex; j < idx; j++) { + int yA = path.get(j).getY(); + int yB = path.get(j + 1).getY(); + if (Math.abs(yA - yB) >= 1) { + heightChangeOnPath = true; + break; + } + } + + // Jeśli na drodze do kandydata jest zmiana wysokości — + // patrz tylko na pierwszy waypoint tej zmiany, nie dalej + if (heightChangeOnPath) { + // Znajdź pierwszy waypoint ze zmianą Y i patrz na niego + for (int j = waypointIndex; j < idx; j++) { + if (Math.abs(path.get(j).getY() - path.get(j + 1).getY()) >= 1) { + bestIdx = j + 1; // patrz na wyższy/niższy waypoint + break; + } + } + break; // nie idź dalej w lookahead + } + + // Brak zmiany wysokości — normalna logika + if (distToCandidate < 2.5) continue; + + double angleToCandidate = angleToTarget(player, candidateVec); + if (Math.abs(angleToCandidate) > 70.0) break; BlockHitResult hit = client.world.raycast(new RaycastContext( - eyePos, - candidateVec, - RaycastContext.ShapeType.COLLIDER, + eyePos, candidateVec, + RaycastContext.ShapeType.OUTLINE, RaycastContext.FluidHandling.NONE, player )); if (hit.getType() == HitResult.Type.MISS) { - return candidate; + bestIdx = idx; + } else { + break; } } - return path.get(waypointIndex); + return path.get(bestIdx); } - // ── Internal ─────────────────────────────────────────────────────────── + /** Kąt między aktualnym kierunkiem ruchu gracza a punktem docelowym. */ + private double angleToTarget(ClientPlayerEntity player, Vec3d target) { + Vec3d pos = player.getEntityPos(); + double dx = target.x - pos.x; + double dz = target.z - pos.z; + double targetYaw = Math.toDegrees(Math.atan2(-dx, dz)); + return angleDiff(targetYaw, currentMoveYaw); + } + + // ══════════════════════════════════════════════════════════════════════════ + // RUCH — waypoints i input + // ══════════════════════════════════════════════════════════════════════════ private void advanceWaypoints(ClientPlayerEntity player) { while (waypointIndex < path.size()) { - Vec3d center = Vec3d.ofCenter(path.get(waypointIndex)); - Vec3d pPos = player.getEntityPos(); - - double dist; - double reach; + BlockPos wpBlock = path.get(waypointIndex); + Vec3d center = getWaypointTarget(wpBlock); + Vec3d pPos = player.getEntityPos(); if (flying) { - dist = pPos.distanceTo(center); // pełny 3D - reach = 2.5; // większy reach w locie + double dist = pPos.distanceTo(center); + if (dist < 2.5) waypointIndex++; + else break; } else { - dist = horizontalDist(pPos, center); // poziomy 2D - reach = Math.min(1.8, 0.55 + dist * 0.15); - } + double horizDist = horizontalDist(pPos, center); + double vertDiff = pPos.y - center.y; // dodatnie = gracz wyżej niż cel - if (dist < reach) waypointIndex++; - else break; + // ── Cel jest WYŻEJ niż gracz (skok w górę) ─────────────────────── + // Nie zaliczaj dopóki gracz faktycznie nie wejdzie na ten poziom + if (wpBlock.getY() > Math.floor(pPos.y) + 0.1) { + // Gracz musi być na tym samym Y i poziomo blisko + if (horizDist < 1.0 && Math.abs(vertDiff) < 0.6) { + waypointIndex++; + } else { + break; + } + continue; + } + + // ── Cel jest NIŻEJ niż gracz (schodzenie/spadanie) ─────────────── + if (vertDiff > 0.8 && horizDist < 1.2) { + waypointIndex++; + continue; + } + + // ── Ten sam poziom — normalne zaliczanie ───────────────────────── + double reach = Math.min(1.5, 0.5 + horizDist * 0.1); + if (horizDist < reach) waypointIndex++; + else break; + } } } private PathInput computeInput(ClientPlayerEntity player, BlockPos target) { Vec3d playerPos = new Vec3d(player.getX(), player.getY(), player.getZ()); - Vec3d targetPos = Vec3d.ofCenter(target); + Vec3d targetPos = getWaypointTarget(target); + + // Jeśli cel jest niżej i gracz jest poziomo nad nim — patrz na następny + // waypoint żeby nie wchodzić w ścianę przy schodzeniu + double horizDist = horizontalDist(playerPos, targetPos); + double vertDiff = playerPos.y - targetPos.y; + if (vertDiff > 0.8 && horizDist < 1.5 && waypointIndex + 1 < path.size()) { + targetPos = getWaypointTarget(path.get(waypointIndex + 1)); + } double dx = targetPos.x - playerPos.x; double dz = targetPos.z - playerPos.z; - double targetYaw = Math.toDegrees(Math.atan2(-dx, dz)); - double diff = angleDiff(targetYaw, player.getYaw()); + double moveYaw = Math.toDegrees(Math.atan2(-dx, dz)); + // Użyj currentMoveYaw (kąt do waypointu) zamiast player.getYaw() + // (który jest ustawiony przez lookahead kamery) + double diff = angleDiff(moveYaw, currentMoveYaw); double rad = Math.toRadians(diff); float forward = (float) Math.cos(rad); float sideways = (float) -Math.sin(rad); @@ -227,51 +399,28 @@ public class PathExecutor { if (Math.abs(forward) < 0.05f) forward = 0; if (Math.abs(sideways) < 0.05f) sideways = 0; - // ── Oblicz mnożnik prędkości ────────────────────────────────────────── + // ── Hamowanie przed zakrętem ────────────────────────────────────────── float speedMult = 1.0f; - // 1. Hamowanie przed celem (ostatnie N bloków)z - boolean isLastWaypoint = (waypointIndex == path.size() - 1); - // if (flying) { - //double distToTarget = playerPos.distanceTo(targetPos); - // if (isLastWaypoint) { - // Hamuj płynnie na ostatnich 6 blokach - //speedMult *= (float) Math.min(1.0, distToTarget / 0.1); - // } - //} else { - //double distToTarget = horizontalDist(playerPos, targetPos); - //if (isLastWaypoint) { - //speedMult *= (float) Math.min(1.0, distToTarget / 4.0); - //} - //} - - // 2. Hamowanie przed zakrętem — patrz na kąt do NASTĘPNEGO waypointu if (waypointIndex + 1 < path.size()) { - BlockPos nextTarget = path.get(waypointIndex + 1); - Vec3d nextPos = Vec3d.ofCenter(nextTarget); + BlockPos nextTarget = path.get(waypointIndex + 1); + Vec3d nextPos = Vec3d.ofCenter(nextTarget); + double ndx = nextPos.x - targetPos.x; + double ndz = nextPos.z - targetPos.z; + double nextYaw = Math.toDegrees(Math.atan2(-ndx, ndz)); + double turnAngle = Math.abs(angleDiff(nextYaw, moveYaw)); - double ndx = nextPos.x - targetPos.x; - double ndz = nextPos.z - targetPos.z; - double nextYaw = Math.toDegrees(Math.atan2(-ndx, ndz)); - double turnAngle = Math.abs(angleDiff(nextYaw, targetYaw)); - - // Im większy zakręt tym mocniej hamuj (powyżej 30° zaczyna hamować) if (turnAngle > 30.0) { - float turnMult = (float) Math.max(0.3, 1.0 - (turnAngle - 30.0) / 120.0); - - // Hamuj tylko gdy jesteś blisko zakrętu + float turnMult = (float) Math.max(0.3, 1.0 - (turnAngle - 30.0) / 120.0); double distToTurn = flying ? playerPos.distanceTo(targetPos) : horizontalDist(playerPos, targetPos); - - if (distToTurn < 5.0) { - speedMult *= turnMult; - } + if (distToTurn < 5.0) speedMult *= turnMult; } } - // 3. Hamowanie przy zmianie wysokości w locie - float upward = 0f; + // ── Lot — sterowanie pionowe i hamowanie przy dużej zmianie Y ──────── + float upward = 0f; boolean jumping = false; boolean sneaking = false; @@ -279,31 +428,85 @@ public class PathExecutor { double dy = targetPos.y - playerPos.y; if (dy > 0.4) upward = 1.0f; else if (dy < -0.4) upward = -1.0f; - - // Jeśli duża zmiana wysokości — zwolnij ruch poziomy - if (Math.abs(dy) > 3.0) { - speedMult *= 0.5f; - } + if (Math.abs(dy) > 3.0) speedMult *= 0.5f; } else { jumping = target.getY() > Math.floor(playerPos.y) + 0.5; } - // Clamp speedMult żeby nie był ujemny speedMult = Math.max(0.0f, Math.min(1.0f, speedMult)); - // Zastosuj mnożnik do forward/sideways - forward *= speedMult; - sideways *= speedMult; - + boolean isLastWaypoint = (waypointIndex == path.size() - 1); boolean sprint = !isLastWaypoint && Math.abs(diff) < SPRINT_ANGLE && forward > 0.5f - && speedMult > 0.7f; // nie sprintuj gdy hamujesz + && speedMult > 0.7f; return new PathInput(forward, sideways, upward, jumping, sprint, sneaking); } - // ── Utilities ────────────────────────────────────────────────────────── + // ══════════════════════════════════════════════════════════════════════════ + // STUCK DETECTION + // ══════════════════════════════════════════════════════════════════════════ + + private void checkStuck(ClientPlayerEntity player, MinecraftClient client) { + Vec3d pos = player.getEntityPos(); + + if (lastStuckCheckPos == null) { + lastStuckCheckPos = pos; + return; + } + + double moved = horizontalDist(pos, lastStuckCheckPos); + + if (moved < STUCK_MIN_DIST) { + stuckTickCounter++; + if (stuckTickCounter >= STUCK_TICKS) { + stuckTickCounter = 0; + lastStuckCheckPos = pos; + handleStuck(client); + } + } else { + stuckTickCounter = 0; + lastStuckCheckPos = pos; + } + } + + private void handleStuck(MinecraftClient client) { + if (path == null || client.player == null) return; + + BlockPos goal = path.get(path.size() - 1); + BlockPos stuckAt = client.player.getBlockPos(); + + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + BlockPos candidate = stuckAt.add(dx, 0, dz); + if (candidate.isWithinDistance(goal, 3.0)) continue; + blacklistedNodes.add(candidate); + blacklistedNodes.add(candidate.up()); + blacklistedNodes.add(candidate.down()); + } + } + + stop(client); + + PathfindingController.INSTANCE.goTo( + goal.getX(), goal.getY(), goal.getZ(), + flying, null + ); + } + + // ══════════════════════════════════════════════════════════════════════════ + // POMOCNICZE + // ══════════════════════════════════════════════════════════════════════════ + + /** Losowy offset w bloku — zapobiega chodzeniu przez idealny środek. */ + private Vec3d getWaypointTarget(BlockPos pos) { + return waypointOffsets.computeIfAbsent(pos, p -> { + double ox = (rng.nextDouble() - 0.5) * 0.6; + double oz = (rng.nextDouble() - 0.5) * 0.6; + return new Vec3d(p.getX() + 0.5 + ox, p.getY(), p.getZ() + 0.5 + oz); + }); + } private static double horizontalDist(Vec3d a, Vec3d b) { double dx = a.x - b.x; @@ -317,12 +520,4 @@ public class PathExecutor { while (diff < -180) diff += 360; return diff; } - - public boolean isFlying() { - return flying; - } - - public void setFlying(boolean flying) { - this.flying = flying; - } -} +} \ No newline at end of file diff --git a/PathfindingController.java b/PathfindingController.java index 570f962..4af3464 100644 --- a/PathfindingController.java +++ b/PathfindingController.java @@ -85,7 +85,19 @@ public class PathfindingController { executor.setFlying(flying); threadPool.submit(() -> { - List path = AStarPathfinder.find(client.world, start, goal, flying); + List path = AStarPathfinder.find( + client.world, start, goal, flying, + executor.getBlacklist() + ); + + // Jeśli nie znalazł z blacklistą — spróbuj bez niej + if (path.isEmpty() && !executor.getBlacklist().isEmpty()) { + executor.clearBlacklist(); + path = AStarPathfinder.find( + client.world, start, goal, flying, + executor.getBlacklist() + ); + } if (path.isEmpty()) { status = "No path found"; @@ -111,7 +123,7 @@ public class PathfindingController { /** Stop walking immediately. */ public void stop() { MinecraftClient client = MinecraftClient.getInstance(); - executor.stop(client); + executor.reset(client); status = "Stopped"; }