/* * This file is part of Baritone. * * Baritone is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Baritone is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Baritone. If not, see . */ package baritone.pathing.calc; import baritone.Baritone; import baritone.api.pathing.goals.Goal; import baritone.pathing.path.IPath; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import java.util.Optional; /** * Any pathfinding algorithm that keeps track of nodes recursively by their cost (e.g. A*, dijkstra) * * @author leijurv */ public abstract class AbstractNodeCostSearch implements IPathFinder { /** * The currently running search task */ private static AbstractNodeCostSearch currentlyRunning = null; protected final int startX; protected final int startY; protected final int startZ; protected final Goal goal; /** * @see Issue #107 */ private final Long2ObjectOpenHashMap map; protected PathNode startNode; protected PathNode mostRecentConsidered; protected PathNode[] bestSoFar; private volatile boolean isFinished; protected boolean cancelRequested; /** * This is really complicated and hard to explain. I wrote a comment in the old version of MineBot but it was so * long it was easier as a Google Doc (because I could insert charts). * * @see */ protected static final double[] COEFFICIENTS = {1.5, 2, 2.5, 3, 4, 5, 10}; // big TODO tune /** * If a path goes less than 5 blocks and doesn't make it to its goal, it's not worth considering. */ protected final static double MIN_DIST_PATH = 5; AbstractNodeCostSearch(int startX, int startY, int startZ, Goal goal) { this.startX = startX; this.startY = startY; this.startZ = startZ; this.goal = goal; this.map = new Long2ObjectOpenHashMap<>(Baritone.settings().pathingMapDefaultSize.value, Baritone.settings().pathingMapLoadFactor.get()); } public void cancel() { cancelRequested = true; } public synchronized Optional calculate(long timeout) { if (isFinished) { throw new IllegalStateException("Path Finder is currently in use, and cannot be reused!"); } this.cancelRequested = false; try { Optional path = calculate0(timeout); path.ifPresent(IPath::postprocess); isFinished = true; return path; } finally { // this is run regardless of what exception may or may not be raised by calculate0 currentlyRunning = null; isFinished = true; } } /** * Don't set currentlyRunning to this until everything is all ready to go, and we're about to enter the main loop. * For example, bestSoFar is null so bestPathSoFar (which gets bestSoFar[0]) could NPE if we set currentlyRunning before calculate0 */ protected void loopBegin() { currentlyRunning = this; } protected abstract Optional calculate0(long timeout); /** * Determines the distance squared from the specified node to the start * node. Intended for use in distance comparison, rather than anything that * considers the real distance value, hence the "sq". * * @param n A node * @return The distance, squared */ protected double getDistFromStartSq(PathNode n) { int xDiff = n.x - startX; int yDiff = n.y - startY; int zDiff = n.z - startZ; return xDiff * xDiff + yDiff * yDiff + zDiff * zDiff; } /** * Attempts to search the block position hashCode long to {@link PathNode} map * for the node mapped to the specified pos. If no node is found, * a new node is created. * * @return The associated node * @see Issue #107 */ protected PathNode getNodeAtPosition(int x, int y, int z, long hashCode) { PathNode node = map.get(hashCode); if (node == null) { node = new PathNode(x, y, z, goal); map.put(hashCode, node); } return node; } public static long posHash(int x, int y, int z) { /* * This is the hashcode implementation of Vec3i (the superclass of the class which I shall not name) * * public int hashCode() { * return (this.getY() + this.getZ() * 31) * 31 + this.getX(); * } * * That is terrible and has tons of collisions and makes the HashMap terribly inefficient. * * That's why we grab out the X, Y, Z and calculate our own hashcode */ long hash = 3241; hash = 3457689L * hash + x; hash = 8734625L * hash + y; hash = 2873465L * hash + z; return hash; } public static void forceCancel() { currentlyRunning = null; } public PathNode mostRecentNodeConsidered() { return mostRecentConsidered; } public PathNode bestNodeSoFar() { return bestSoFar[0]; } public PathNode startNode() { return startNode; } @Override public Optional pathToMostRecentNodeConsidered() { try { return Optional.ofNullable(mostRecentConsidered).map(node -> new Path(startNode, node, 0, goal)); } catch (IllegalStateException ex) { System.out.println("Unable to construct path to render"); return Optional.empty(); } } protected int mapSize() { return map.size(); } @Override public Optional bestPathSoFar() { if (startNode == null || bestSoFar[0] == null) { return Optional.empty(); } for (int i = 0; i < bestSoFar.length; i++) { if (bestSoFar[i] == null) { continue; } if (getDistFromStartSq(bestSoFar[i]) > MIN_DIST_PATH * MIN_DIST_PATH) { // square the comparison since distFromStartSq is squared try { return Optional.of(new Path(startNode, bestSoFar[i], 0, goal)); } catch (IllegalStateException ex) { System.out.println("Unable to construct path to render"); return Optional.empty(); } } } // instead of returning bestSoFar[0], be less misleading // if it actually won't find any path, don't make them think it will by rendering a dark blue that will never actually happen return Optional.empty(); } @Override public final boolean isFinished() { return isFinished; } @Override public final Goal getGoal() { return goal; } public static Optional getCurrentlyRunning() { return Optional.ofNullable(currentlyRunning); } }