Upload files to "/"
This commit is contained in:
282
AStarPathfinder.java
Normal file
282
AStarPathfinder.java
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
package xyz.nodrop.farmingtools.client.pathfinding;
|
||||||
|
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.world.ClientWorld;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.block.*;
|
||||||
|
import net.minecraft.registry.tag.BlockTags;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class AStarPathfinder {
|
||||||
|
|
||||||
|
private static final int MAX_NODES = 100_000;
|
||||||
|
private static final int MAX_FALL = 3;
|
||||||
|
|
||||||
|
private static final int[][] DIRS = {
|
||||||
|
{1,0}, {-1,0}, {0,1}, {0,-1},
|
||||||
|
{1,1}, {1,-1}, {-1,1}, {-1,-1}
|
||||||
|
};
|
||||||
|
private static final double SQRT2 = 1.41421356;
|
||||||
|
|
||||||
|
public static List<BlockPos> find(ClientWorld world, BlockPos start, BlockPos goal, boolean flying) {
|
||||||
|
PriorityQueue<PathNode> open = new PriorityQueue<>();
|
||||||
|
Map<BlockPos, Double> gScore = new HashMap<>();
|
||||||
|
|
||||||
|
open.add(new PathNode(start, null, 0, heuristic(start, goal)));
|
||||||
|
gScore.put(start, 0.0);
|
||||||
|
|
||||||
|
int expanded = 0;
|
||||||
|
while (!open.isEmpty() && expanded < MAX_NODES) {
|
||||||
|
PathNode current = open.poll();
|
||||||
|
expanded++;
|
||||||
|
|
||||||
|
if (current.pos.equals(goal)) {
|
||||||
|
List<BlockPos> raw = reconstruct(current);
|
||||||
|
return smooth(world, raw, flying);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PathNode nb : neighbours(world, current, goal, flying)) {
|
||||||
|
double existing = gScore.getOrDefault(nb.pos, Double.MAX_VALUE);
|
||||||
|
if (nb.g < existing) {
|
||||||
|
gScore.put(nb.pos, nb.g);
|
||||||
|
open.add(nb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<BlockPos> smooth(ClientWorld world, List<BlockPos> raw, boolean flying) {
|
||||||
|
if (raw.size() <= 2) return raw;
|
||||||
|
|
||||||
|
if (flying) return smoothFly(world, raw);
|
||||||
|
else return smoothWalk(world, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Smoothing dla chodzenia (stary kod, tylko przemianowany) ──────────────
|
||||||
|
private static List<BlockPos> smoothWalk(ClientWorld world, List<BlockPos> raw) {
|
||||||
|
List<BlockPos> smoothed = new ArrayList<>();
|
||||||
|
smoothed.add(raw.get(0));
|
||||||
|
|
||||||
|
int anchor = 0;
|
||||||
|
int i = 2;
|
||||||
|
|
||||||
|
while (i < raw.size()) {
|
||||||
|
BlockPos from = raw.get(anchor);
|
||||||
|
BlockPos to = raw.get(i);
|
||||||
|
boolean yChanged = raw.get(i).getY() != raw.get(i - 1).getY();
|
||||||
|
|
||||||
|
if (yChanged || !hasLineOfWalk(world, from, to)) {
|
||||||
|
smoothed.add(raw.get(i - 1));
|
||||||
|
anchor = i - 1;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
smoothed.add(raw.get(raw.size() - 1));
|
||||||
|
return smoothed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Smoothing dla lotu — ray cast 3D ─────────────────────────────────────
|
||||||
|
private static List<BlockPos> smoothFly(ClientWorld world, List<BlockPos> raw) {
|
||||||
|
List<BlockPos> smoothed = new ArrayList<>();
|
||||||
|
smoothed.add(raw.get(0));
|
||||||
|
|
||||||
|
int anchor = 0;
|
||||||
|
int i = 2;
|
||||||
|
|
||||||
|
while (i < raw.size()) {
|
||||||
|
BlockPos from = raw.get(anchor);
|
||||||
|
BlockPos to = raw.get(i);
|
||||||
|
|
||||||
|
if (!hasLineOfFlight(world, from, to)) {
|
||||||
|
smoothed.add(raw.get(i - 1));
|
||||||
|
anchor = i - 1;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
smoothed.add(raw.get(raw.size() - 1));
|
||||||
|
return smoothed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Ray cast 3D dla lotu (Bresenham 3D) ──────────────────────────────────
|
||||||
|
private static boolean hasLineOfFlight(ClientWorld world, BlockPos from, BlockPos to) {
|
||||||
|
int x0 = from.getX(), y0 = from.getY(), z0 = from.getZ();
|
||||||
|
int x1 = to.getX(), y1 = to.getY(), z1 = to.getZ();
|
||||||
|
|
||||||
|
int dx = Math.abs(x1 - x0), dy = Math.abs(y1 - y0), dz = Math.abs(z1 - z0);
|
||||||
|
int sx = x0 < x1 ? 1 : -1;
|
||||||
|
int sy = y0 < y1 ? 1 : -1;
|
||||||
|
int sz = z0 < z1 ? 1 : -1;
|
||||||
|
|
||||||
|
int cx = x0, cy = y0, cz = z0;
|
||||||
|
|
||||||
|
// Błędy dla Bresenhama 3D
|
||||||
|
int errXY = dx - dy;
|
||||||
|
int errXZ = dx - dz;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 < dx) { errXZ += dx; cz += sz; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasLineOfWalk(ClientWorld world, BlockPos from, BlockPos to) {
|
||||||
|
if (from.getY() != to.getY()) return false;
|
||||||
|
|
||||||
|
int x0 = from.getX(), z0 = from.getZ();
|
||||||
|
int x1 = to.getX(), z1 = to.getZ();
|
||||||
|
int y = from.getY();
|
||||||
|
|
||||||
|
int dx = Math.abs(x1 - x0), dz = Math.abs(z1 - z0);
|
||||||
|
int sx = x0 < x1 ? 1 : -1, sz = z0 < z1 ? 1 : -1;
|
||||||
|
int err = dx - dz;
|
||||||
|
|
||||||
|
int cx = x0, cz = z0;
|
||||||
|
while (true) {
|
||||||
|
if (cx == x1 && cz == z1) break;
|
||||||
|
|
||||||
|
BlockPos pos = new BlockPos(cx, y, cz);
|
||||||
|
if (!canPass(world, pos) || !isSolid(world, pos.down())) return false;
|
||||||
|
|
||||||
|
int e2 = 2 * err;
|
||||||
|
if (e2 > -dz) { err -= dz; cx += sx; }
|
||||||
|
if (e2 < dx) { err += dx; cz += sz; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PathNode> neighbours(ClientWorld world, PathNode current, BlockPos goal, boolean flying) {
|
||||||
|
List<PathNode> result = new ArrayList<>();
|
||||||
|
BlockPos pos = current.pos;
|
||||||
|
|
||||||
|
if (flying) {
|
||||||
|
// ── Tryb lotu — ruch we wszystkich 26 kierunkach ──────────────────
|
||||||
|
for (int dx = -1; dx <= 1; dx++) {
|
||||||
|
for (int dy = -1; dy <= 1; dy++) {
|
||||||
|
for (int dz = -1; dz <= 1; dz++) {
|
||||||
|
if (dx == 0 && dy == 0 && dz == 0) continue;
|
||||||
|
BlockPos nb = pos.add(dx, dy, dz);
|
||||||
|
if (isPassable(world, nb) && isPassable(world, nb.up())) {
|
||||||
|
double cost = Math.sqrt(dx*dx + dy*dy + dz*dz);
|
||||||
|
add(result, nb, current, goal, cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int[] d : DIRS) {
|
||||||
|
int dx = d[0], dz = d[1];
|
||||||
|
boolean diagonal = (dx != 0 && dz != 0);
|
||||||
|
double baseCost = diagonal ? SQRT2 : 1.0;
|
||||||
|
|
||||||
|
int nx = pos.getX() + dx;
|
||||||
|
int nz = pos.getZ() + dz;
|
||||||
|
|
||||||
|
if (diagonal) {
|
||||||
|
if (!canPass(world, new BlockPos(pos.getX() + dx, pos.getY(), pos.getZ()))) continue;
|
||||||
|
if (!canPass(world, new BlockPos(pos.getX(), pos.getY(), pos.getZ() + dz))) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockPos flat = new BlockPos(nx, pos.getY(), nz);
|
||||||
|
if (canStand(world, flat)) add(result, flat, current, goal, baseCost);
|
||||||
|
|
||||||
|
BlockPos up = new BlockPos(nx, pos.getY() + 1, nz);
|
||||||
|
if (canStand(world, up) && isPassable(world, pos.up()))
|
||||||
|
add(result, up, current, goal, baseCost + 0.5);
|
||||||
|
|
||||||
|
for (int drop = 1; drop <= MAX_FALL; drop++) {
|
||||||
|
BlockPos fell = new BlockPos(nx, pos.getY() - drop, nz);
|
||||||
|
if (canStand(world, fell)) {
|
||||||
|
add(result, fell, current, goal, baseCost + drop * 0.5);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!isPassable(world, new BlockPos(nx, pos.getY() - drop, nz))) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int drop = 1; drop <= MAX_FALL; drop++) {
|
||||||
|
BlockPos fell = pos.down(drop);
|
||||||
|
if (canStand(world, fell)) {
|
||||||
|
add(result, fell, current, goal, drop * 0.5);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!isPassable(world, fell)) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void add(List<PathNode> list, BlockPos pos,
|
||||||
|
PathNode parent, BlockPos goal, double cost) {
|
||||||
|
list.add(new PathNode(pos, parent, parent.g + cost, heuristic(pos, goal)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean canStand(ClientWorld world, BlockPos feet) {
|
||||||
|
return isSolid(world, feet.down())
|
||||||
|
&& isPassable(world, feet)
|
||||||
|
&& isPassable(world, feet.up());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canPass(ClientWorld world, BlockPos pos) {
|
||||||
|
return isPassable(world, pos) && isPassable(world, pos.up());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Set<Block> BLOCKED_BLOCKS = Set.of(
|
||||||
|
Blocks.COBWEB,
|
||||||
|
Blocks.SWEET_BERRY_BUSH,
|
||||||
|
Blocks.BAMBOO,
|
||||||
|
Blocks.BAMBOO_SAPLING,
|
||||||
|
Blocks.CACTUS,
|
||||||
|
Blocks.POWDER_SNOW
|
||||||
|
);
|
||||||
|
|
||||||
|
private static boolean isPassable(ClientWorld world, BlockPos pos) {
|
||||||
|
BlockState state = world.getBlockState(pos);
|
||||||
|
Block block = state.getBlock();
|
||||||
|
|
||||||
|
// Block blocks from tags
|
||||||
|
if (state.isIn(BlockTags.LEAVES)) return false;
|
||||||
|
if (state.isIn(BlockTags.FENCES)) return false;
|
||||||
|
if (state.isIn(BlockTags.WALLS)) return false;
|
||||||
|
|
||||||
|
// Block blocks from list
|
||||||
|
if (BLOCKED_BLOCKS.contains(block)) return false;
|
||||||
|
|
||||||
|
return !state.isSolidBlock(world, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSolid(ClientWorld world, BlockPos pos) {
|
||||||
|
BlockState state = world.getBlockState(pos);
|
||||||
|
return state.isSolidBlock(world, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double heuristic(BlockPos a, BlockPos b) {
|
||||||
|
double dx = a.getX() - b.getX();
|
||||||
|
double dy = a.getY() - b.getY();
|
||||||
|
double dz = a.getZ() - b.getZ();
|
||||||
|
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<BlockPos> reconstruct(PathNode node) {
|
||||||
|
LinkedList<BlockPos> path = new LinkedList<>();
|
||||||
|
for (PathNode n = node; n != null; n = n.parent) path.addFirst(n.pos);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -230,20 +230,20 @@ public class PathExecutor {
|
|||||||
// ── Oblicz mnożnik prędkości ──────────────────────────────────────────
|
// ── Oblicz mnożnik prędkości ──────────────────────────────────────────
|
||||||
float speedMult = 1.0f;
|
float speedMult = 1.0f;
|
||||||
|
|
||||||
// 1. Hamowanie przed celem (ostatnie N bloków)
|
// 1. Hamowanie przed celem (ostatnie N bloków)z
|
||||||
boolean isLastWaypoint = (waypointIndex == path.size() - 1);
|
boolean isLastWaypoint = (waypointIndex == path.size() - 1);
|
||||||
if (flying) {
|
// if (flying) {
|
||||||
double distToTarget = playerPos.distanceTo(targetPos);
|
//double distToTarget = playerPos.distanceTo(targetPos);
|
||||||
if (isLastWaypoint) {
|
// if (isLastWaypoint) {
|
||||||
// Hamuj płynnie na ostatnich 6 blokach
|
// Hamuj płynnie na ostatnich 6 blokach
|
||||||
speedMult *= (float) Math.min(1.0, distToTarget / 6.0);
|
//speedMult *= (float) Math.min(1.0, distToTarget / 0.1);
|
||||||
}
|
// }
|
||||||
} else {
|
//} else {
|
||||||
double distToTarget = horizontalDist(playerPos, targetPos);
|
//double distToTarget = horizontalDist(playerPos, targetPos);
|
||||||
if (isLastWaypoint) {
|
//if (isLastWaypoint) {
|
||||||
speedMult *= (float) Math.min(1.0, distToTarget / 4.0);
|
//speedMult *= (float) Math.min(1.0, distToTarget / 4.0);
|
||||||
}
|
//}
|
||||||
}
|
//}
|
||||||
|
|
||||||
// 2. Hamowanie przed zakrętem — patrz na kąt do NASTĘPNEGO waypointu
|
// 2. Hamowanie przed zakrętem — patrz na kąt do NASTĘPNEGO waypointu
|
||||||
if (waypointIndex + 1 < path.size()) {
|
if (waypointIndex + 1 < path.size()) {
|
||||||
|
|||||||
12
PathInput.java
Normal file
12
PathInput.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package xyz.nodrop.farmingtools.client.pathfinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snapshot of movement input for one tick.
|
||||||
|
* Passed from PathExecutor → MixinKeyboardInput.
|
||||||
|
*
|
||||||
|
* movementForward: 1.0 = forward, -1.0 = back
|
||||||
|
* movementSideways: 1.0 = left, -1.0 = right (MC convention)
|
||||||
|
*/
|
||||||
|
public record PathInput(float forward, float sideways, float upward, boolean jumping, boolean sprint, boolean sneaking) {
|
||||||
|
public static final PathInput NONE = new PathInput(0, 0, 0, false, false, false);
|
||||||
|
}
|
||||||
35
PathNode.java
Normal file
35
PathNode.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package xyz.nodrop.farmingtools.client.pathfinding;
|
||||||
|
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single node in the A* search graph.
|
||||||
|
* Comparable by f = g + h so it can be used directly in a PriorityQueue.
|
||||||
|
*/
|
||||||
|
public class PathNode implements Comparable<PathNode> {
|
||||||
|
|
||||||
|
public final BlockPos pos;
|
||||||
|
public final PathNode parent;
|
||||||
|
|
||||||
|
/** Cost from start to this node. */
|
||||||
|
public final double g;
|
||||||
|
|
||||||
|
/** Heuristic cost estimate from this node to goal. */
|
||||||
|
public final double h;
|
||||||
|
|
||||||
|
public PathNode(BlockPos pos, PathNode parent, double g, double h) {
|
||||||
|
this.pos = pos;
|
||||||
|
this.parent = parent;
|
||||||
|
this.g = g;
|
||||||
|
this.h = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double f() {
|
||||||
|
return g + h;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(PathNode other) {
|
||||||
|
return Double.compare(this.f(), other.f());
|
||||||
|
}
|
||||||
|
}
|
||||||
135
PathfindingController.java
Normal file
135
PathfindingController.java
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package xyz.nodrop.farmingtools.client.pathfinding;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.network.ClientPlayerEntity;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import xyz.nodrop.farmingtools.client.pathfinding.PathInput;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Central controller for pathfinding.
|
||||||
|
*
|
||||||
|
* ┌─────────────────────────────────────────────────────┐
|
||||||
|
* │ External script / command calls: │
|
||||||
|
* │ PathfindingController.INSTANCE.goTo(x, y, z) │
|
||||||
|
* │ PathfindingController.INSTANCE.stop() │
|
||||||
|
* │ PathfindingController.INSTANCE.isRunning() │
|
||||||
|
* └─────────────────────────────────────────────────────┘
|
||||||
|
*
|
||||||
|
* The A* search runs on a background thread so the client never freezes.
|
||||||
|
* Once the path is ready it is handed off to PathExecutor which ticks
|
||||||
|
* on the main client thread.
|
||||||
|
*/
|
||||||
|
public class PathfindingController {
|
||||||
|
|
||||||
|
public static final PathfindingController INSTANCE = new PathfindingController();
|
||||||
|
|
||||||
|
private final ExecutorService threadPool = Executors.newSingleThreadExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "farmingtools-pathfinder");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
private final PathExecutor executor = new PathExecutor();
|
||||||
|
|
||||||
|
/** Last status message — shown on HUD. */
|
||||||
|
private volatile String status = "Idle";
|
||||||
|
|
||||||
|
/** Optional callback fired when path is found / fails / finishes. */
|
||||||
|
private volatile Consumer<PathResult> onResult = null;
|
||||||
|
|
||||||
|
private PathfindingController() {}
|
||||||
|
|
||||||
|
// ── Registration ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public void register() {
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
|
executor.tick(client);
|
||||||
|
|
||||||
|
// Auto-clear status when done
|
||||||
|
if (!executor.isActive() && status.equals("Walking...")) {
|
||||||
|
status = "Arrived";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public API (call from any thread) ─────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start pathfinding to the given block position.
|
||||||
|
* Computation happens on a background thread; walking starts automatically.
|
||||||
|
*
|
||||||
|
* @param onResult optional callback called with the result (may be null)
|
||||||
|
*/
|
||||||
|
public void goTo(int x, int y, int z, boolean flying, Consumer<PathResult> onResult) {
|
||||||
|
this.onResult = onResult;
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
ClientPlayerEntity player = client.player;
|
||||||
|
|
||||||
|
if (player == null || client.world == null) {
|
||||||
|
status = "Not in world";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockPos start = player.getBlockPos();
|
||||||
|
BlockPos goal = new BlockPos(x, y, z);
|
||||||
|
|
||||||
|
status = "Searching...";
|
||||||
|
executor.stop(client);
|
||||||
|
executor.setFlying(flying);
|
||||||
|
|
||||||
|
threadPool.submit(() -> {
|
||||||
|
List<BlockPos> path = AStarPathfinder.find(client.world, start, goal, flying);
|
||||||
|
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
status = "No path found";
|
||||||
|
if (onResult != null) onResult.accept(PathResult.failure(goal));
|
||||||
|
} else {
|
||||||
|
status = "Walking...";
|
||||||
|
executor.start(path);
|
||||||
|
if (onResult != null) onResult.accept(PathResult.success(goal, path.size()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience overload without callback. */
|
||||||
|
public void goTo(int x, int y, int z) {
|
||||||
|
goTo(x, y, z, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience overload accepting BlockPos. */
|
||||||
|
public void goTo(BlockPos goal) {
|
||||||
|
goTo(goal.getX(), goal.getY(), goal.getZ(), false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stop walking immediately. */
|
||||||
|
public void stop() {
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
executor.stop(client);
|
||||||
|
status = "Stopped";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() { return executor.isActive(); }
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public PathInput getCurrentInput() { return executor.getCurrentInput(); }
|
||||||
|
public int getProgress() { return executor.getWaypointIndex(); }
|
||||||
|
public int getPathLength() { return executor.getPathLength(); }
|
||||||
|
public BlockPos getGoal() { return executor.getGoal(); }
|
||||||
|
public List<BlockPos> getCurrentPath() { return executor.getPath(); }
|
||||||
|
public int getCurrentWaypointIndex() { return executor.getWaypointIndex(); }
|
||||||
|
// W PathfindingController:
|
||||||
|
public PathExecutor getExecutor() { return executor; }
|
||||||
|
|
||||||
|
// ── Result record ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public record PathResult(boolean success, BlockPos goal, int length) {
|
||||||
|
static PathResult success(BlockPos g, int len) { return new PathResult(true, g, len); }
|
||||||
|
static PathResult failure(BlockPos g) { return new PathResult(false, g, 0); }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user