Files
help/PathfindingController.java
2026-06-01 12:19:01 +00:00

147 lines
5.9 KiB
Java

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,
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";
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.reset(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); }
}
}