Files
help/PathExecutor.java
2026-05-31 20:12:28 +00:00

329 lines
13 KiB
Java

package xyz.nodrop.farmingtools.client.pathfinding;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult;
import java.util.List;
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 ─────────────────────────────────────────────────────────────────
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 float cameraYaw = 0f;
private float cameraPitch = 0f;
private boolean cameraInitialized = false;
private List<BlockPos> path;
private int waypointIndex = 0;
private boolean active = false;
// Read by MixinKeyboardInput each tick
private volatile PathInput currentInput = PathInput.NONE;
// ── Public API ─────────────────────────────────────────────────────────
public synchronized void start(List<BlockPos> newPath) {
this.path = newPath;
this.waypointIndex = 0;
this.active = !newPath.isEmpty();
this.currentInput = PathInput.NONE;
}
public synchronized void stop(MinecraftClient client) {
active = false;
currentInput = PathInput.NONE;
yawVelocity = 0f; // ← dodaj
pitchVelocity = 0f; // ← dodaj
smoothLookPoint = null; // ← dodaj
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; }
public synchronized BlockPos getGoal() {
return (path != null && !path.isEmpty()) ? path.get(path.size() - 1) : null;
}
public synchronized List<BlockPos> getPath() { return path; }
public PathInput getCurrentInput() {
return active ? currentInput : null;
}
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().allowFlying = true;
}
advanceWaypoints(client.player);
if (waypointIndex >= path.size()) {
stop(client);
return;
}
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
client.player.setSprinting(shouldSprint);
}
private void rotateCamera(ClientPlayerEntity player, BlockPos target, MinecraftClient client) {
if (!cameraInitialized) {
cameraYaw = player.getYaw();
cameraPitch = player.getPitch();
cameraInitialized = true;
}
BlockPos lookTarget = chooseLookTarget(player, client);
Vec3d rawLookPoint = Vec3d.ofCenter(lookTarget).add(0, 0.5, 0);
if (smoothLookPoint == null) {
smoothLookPoint = rawLookPoint;
} else {
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;
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));
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;
cameraYaw += yawVelocity;
cameraPitch += pitchVelocity;
cameraPitch = Math.max(-90f, Math.min(90f, cameraPitch));
nextYaw = cameraYaw;
nextPitch = 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;
}
private BlockPos chooseLookTarget(ClientPlayerEntity player, MinecraftClient client) {
Vec3d eyePos = player.getEyePos();
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);
if (eyePos.distanceTo(candidateVec) < MIN_LOOKAHEAD_DIST) continue;
BlockHitResult hit = client.world.raycast(new RaycastContext(
eyePos,
candidateVec,
RaycastContext.ShapeType.COLLIDER,
RaycastContext.FluidHandling.NONE,
player
));
if (hit.getType() == HitResult.Type.MISS) {
return candidate;
}
}
return path.get(waypointIndex);
}
// ── Internal ───────────────────────────────────────────────────────────
private void advanceWaypoints(ClientPlayerEntity player) {
while (waypointIndex < path.size()) {
Vec3d center = Vec3d.ofCenter(path.get(waypointIndex));
Vec3d pPos = player.getEntityPos();
double dist;
double reach;
if (flying) {
dist = pPos.distanceTo(center); // pełny 3D
reach = 2.5; // większy reach w locie
} else {
dist = horizontalDist(pPos, center); // poziomy 2D
reach = Math.min(1.8, 0.55 + dist * 0.15);
}
if (dist < 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);
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 rad = Math.toRadians(diff);
float forward = (float) Math.cos(rad);
float sideways = (float) -Math.sin(rad);
if (Math.abs(forward) < 0.05f) forward = 0;
if (Math.abs(sideways) < 0.05f) sideways = 0;
// ── Oblicz mnożnik prędkości ──────────────────────────────────────────
float speedMult = 1.0f;
// 1. Hamowanie przed celem (ostatnie N bloków)
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 / 6.0);
}
} 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);
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
double distToTurn = flying
? playerPos.distanceTo(targetPos)
: horizontalDist(playerPos, targetPos);
if (distToTurn < 5.0) {
speedMult *= turnMult;
}
}
}
// 3. Hamowanie przy zmianie wysokości w locie
float upward = 0f;
boolean jumping = false;
boolean sneaking = false;
if (flying) {
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;
}
} 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 sprint = !isLastWaypoint
&& Math.abs(diff) < SPRINT_ANGLE
&& forward > 0.5f
&& speedMult > 0.7f; // nie sprintuj gdy hamujesz
return new PathInput(forward, sideways, upward, jumping, sprint, sneaking);
}
// ── Utilities ──────────────────────────────────────────────────────────
private static double horizontalDist(Vec3d a, Vec3d b) {
double dx = a.x - b.x;
double dz = a.z - b.z;
return Math.sqrt(dx * dx + dz * dz);
}
private static double angleDiff(double target, double current) {
double diff = target - current;
while (diff > 180) diff -= 360;
while (diff < -180) diff += 360;
return diff;
}
public boolean isFlying() {
return flying;
}
public void setFlying(boolean flying) {
this.flying = flying;
}
}