diff --git a/README.md b/README.md index 7782651ac..1826eb208 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Baritone is the pathfinding system used in [Impact](https://impactdevelopment.gi This project is an updated version of [MineBot](https://github.com/leijurv/MineBot/), the original version of the bot for Minecraft 1.8, rebuilt for 1.12.2. Baritone focuses on reliability and particularly performance (it's over [30x faster](https://github.com/cabaletta/baritone/pull/180#issuecomment-423822928) than MineBot at calculating paths). +Have committed at least once a day for the last 6 months =D 🦀 + Here are some links to help to get started: - [Features](FEATURES.md) diff --git a/SETUP.md b/SETUP.md index 6400990d4..a479227be 100644 --- a/SETUP.md +++ b/SETUP.md @@ -65,7 +65,7 @@ $ gradlew build ![Image](https://i.imgur.com/PE6r9iN.png) -- Right click on **build** and press **Run** +- Double click on **build** to run it ## Artifacts diff --git a/buildSrc/src/main/java/baritone/gradle/task/ProguardTask.java b/buildSrc/src/main/java/baritone/gradle/task/ProguardTask.java index 351ded3a9..4d10e7f54 100644 --- a/buildSrc/src/main/java/baritone/gradle/task/ProguardTask.java +++ b/buildSrc/src/main/java/baritone/gradle/task/ProguardTask.java @@ -298,8 +298,8 @@ public class ProguardTask extends BaritoneGradleTask { .start(); // We can't do output inherit process I/O with gradle for some reason and have it work, so we have to do this - this.printOutputLog(p.getInputStream()); - this.printOutputLog(p.getErrorStream()); + this.printOutputLog(p.getInputStream(), System.out); + this.printOutputLog(p.getErrorStream(), System.err); // Halt the current thread until the process is complete, if the exit code isn't 0, throw an exception int exitCode = p.waitFor(); @@ -308,12 +308,12 @@ public class ProguardTask extends BaritoneGradleTask { } } - private void printOutputLog(InputStream stream) { + private void printOutputLog(InputStream stream, PrintStream outerr) { new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { String line; while ((line = reader.readLine()) != null) { - System.out.println(line); + outerr.println(line); } } catch (Exception e) { e.printStackTrace(); diff --git a/src/api/java/baritone/api/behavior/IPathingBehavior.java b/src/api/java/baritone/api/behavior/IPathingBehavior.java index d28195ae9..64be71d50 100644 --- a/src/api/java/baritone/api/behavior/IPathingBehavior.java +++ b/src/api/java/baritone/api/behavior/IPathingBehavior.java @@ -38,11 +38,24 @@ public interface IPathingBehavior extends IBehavior { * @return The estimated remaining ticks in the current segment. */ default Optional ticksRemainingInSegment() { + return ticksRemainingInSegment(true); + } + + /** + * Returns the estimated remaining ticks in the current pathing + * segment. Given that the return type is an optional, {@link Optional#empty()} + * will be returned in the case that there is no current segment being pathed. + * + * @param includeCurrentMovement whether or not to include the entirety of the cost of the currently executing movement in the total + * @return The estimated remaining ticks in the current segment. + */ + default Optional ticksRemainingInSegment(boolean includeCurrentMovement) { IPathExecutor current = getCurrent(); if (current == null) { return Optional.empty(); } - return Optional.of(current.getPath().ticksRemainingFrom(current.getPosition())); + int start = includeCurrentMovement ? current.getPosition() : current.getPosition() + 1; + return Optional.of(current.getPath().ticksRemainingFrom(start)); } /** diff --git a/src/launch/java/baritone/launch/mixins/MixinEntityPlayerSP.java b/src/launch/java/baritone/launch/mixins/MixinEntityPlayerSP.java index bbf920113..87cc65624 100644 --- a/src/launch/java/baritone/launch/mixins/MixinEntityPlayerSP.java +++ b/src/launch/java/baritone/launch/mixins/MixinEntityPlayerSP.java @@ -18,6 +18,7 @@ package baritone.launch.mixins; import baritone.api.BaritoneAPI; +import baritone.api.IBaritone; import baritone.api.behavior.IPathingBehavior; import baritone.api.event.events.ChatEvent; import baritone.api.event.events.PlayerUpdateEvent; @@ -100,8 +101,16 @@ public class MixinEntityPlayerSP { ) private boolean isKeyDown(KeyBinding keyBinding) { SprintStateEvent event = new SprintStateEvent(); - BaritoneAPI.getProvider().getBaritoneForPlayer((EntityPlayerSP) (Object) this).getGameEventHandler().onPlayerSprintState(event); - return event.getState() == null ? keyBinding.isKeyDown() : event.getState(); + IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForPlayer((EntityPlayerSP) (Object) this); + baritone.getGameEventHandler().onPlayerSprintState(event); + if (event.getState() != null) { + return event.getState(); + } + if (baritone != BaritoneAPI.getProvider().getPrimaryBaritone()) { + // hitting control shouldn't make all bots sprint + return false; + } + return keyBinding.isKeyDown(); } @Inject( diff --git a/src/main/java/baritone/behavior/PathingBehavior.java b/src/main/java/baritone/behavior/PathingBehavior.java index ea7520825..8339625d0 100644 --- a/src/main/java/baritone/behavior/PathingBehavior.java +++ b/src/main/java/baritone/behavior/PathingBehavior.java @@ -193,8 +193,10 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior, // and this path doesn't get us all the way there return; } - if (ticksRemainingInSegment().get() < Baritone.settings().planningTickLookAhead.get()) { + if (ticksRemainingInSegment(false).get() < Baritone.settings().planningTickLookAhead.get()) { // and this path has 7.5 seconds or less left + // don't include the current movement so a very long last movement (e.g. descend) doesn't trip it up + // if we actually included current, it wouldn't start planning ahead until the last movement was done, if the last movement took more than 7.5 seconds on its own logDebug("Path almost over. Planning ahead..."); queuePathEvent(PathEvent.NEXT_SEGMENT_CALC_STARTED); findPathInNewThread(current.getPath().getDest(), false); diff --git a/src/main/java/baritone/cache/ChunkPacker.java b/src/main/java/baritone/cache/ChunkPacker.java index 60ac63315..e2903ceaf 100644 --- a/src/main/java/baritone/cache/ChunkPacker.java +++ b/src/main/java/baritone/cache/ChunkPacker.java @@ -124,12 +124,13 @@ public final class ChunkPacker { private static PathingBlockType getPathingBlockType(IBlockState state) { Block block = state.getBlock(); - if (block == Blocks.WATER && !MovementHelper.isFlowing(state)) { + if ((block == Blocks.WATER || block == Blocks.FLOWING_WATER) && !MovementHelper.isFlowing(state)) { // only water source blocks are plausibly usable, flowing water should be avoid + // FLOWING_WATER is a waterfall, it doesn't really matter and caching it as AVOID just makes it look wrong return PathingBlockType.WATER; } - if (MovementHelper.avoidWalkingInto(block) || block == Blocks.FLOWING_WATER || MovementHelper.isBottomSlab(state)) { + if (MovementHelper.avoidWalkingInto(block) || MovementHelper.isBottomSlab(state)) { return PathingBlockType.AVOID; } // We used to do an AABB check here diff --git a/src/main/java/baritone/pathing/calc/AStarPathFinder.java b/src/main/java/baritone/pathing/calc/AStarPathFinder.java index b32fc99ff..fda41cfdc 100644 --- a/src/main/java/baritone/pathing/calc/AStarPathFinder.java +++ b/src/main/java/baritone/pathing/calc/AStarPathFinder.java @@ -25,7 +25,6 @@ import baritone.api.utils.BetterBlockPos; import baritone.pathing.calc.openset.BinaryHeapOpenSet; import baritone.pathing.movement.CalculationContext; import baritone.pathing.movement.Moves; -import baritone.utils.Helper; import baritone.utils.pathing.BetterWorldBorder; import baritone.utils.pathing.Favoring; import baritone.utils.pathing.MutableMoveResult; @@ -37,7 +36,7 @@ import java.util.Optional; * * @author leijurv */ -public final class AStarPathFinder extends AbstractNodeCostSearch implements Helper { +public final class AStarPathFinder extends AbstractNodeCostSearch { private final Favoring favoring; private final CalculationContext calcContext; @@ -55,14 +54,12 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel startNode.combinedCost = startNode.estimatedCostToGoal; BinaryHeapOpenSet openSet = new BinaryHeapOpenSet(); openSet.insert(startNode); - bestSoFar = new PathNode[COEFFICIENTS.length];//keep track of the best node by the metric of (estimatedCostToGoal + cost / COEFFICIENTS[i]) - double[] bestHeuristicSoFar = new double[COEFFICIENTS.length]; + double[] bestHeuristicSoFar = new double[COEFFICIENTS.length];//keep track of the best node by the metric of (estimatedCostToGoal + cost / COEFFICIENTS[i]) for (int i = 0; i < bestHeuristicSoFar.length; i++) { bestHeuristicSoFar[i] = startNode.estimatedCostToGoal; bestSoFar[i] = startNode; } MutableMoveResult res = new MutableMoveResult(); - Favoring favored = favoring; BetterWorldBorder worldBorder = new BetterWorldBorder(calcContext.world.getWorldBorder()); long startTime = System.currentTimeMillis(); boolean slowPath = Baritone.settings().slowPath.get(); @@ -75,10 +72,10 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel int numNodes = 0; int numMovementsConsidered = 0; int numEmptyChunk = 0; - boolean favoring = !favored.isEmpty(); + boolean isFavoring = !favoring.isEmpty(); int timeCheckInterval = 1 << 6; int pathingMaxChunkBorderFetch = Baritone.settings().pathingMaxChunkBorderFetch.get(); // grab all settings beforehand so that changing settings during pathing doesn't cause a crash or unpredictable behavior - boolean minimumImprovementRepropagation = Baritone.settings().minimumImprovementRepropagation.get(); + double minimumImprovement = Baritone.settings().minimumImprovementRepropagation.get() ? MIN_IMPROVEMENT : 0; while (!openSet.isEmpty() && numEmptyChunk < pathingMaxChunkBorderFetch && !cancelRequested) { if ((numNodes & (timeCheckInterval - 1)) == 0) { // only call this once every 64 nodes (about half a millisecond) long now = System.currentTimeMillis(); // since nanoTime is slow on windows (takes many microseconds) @@ -136,21 +133,13 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel throw new IllegalStateException(moves + " " + res.y + " " + (currentNode.y + moves.yOffset)); } long hashCode = BetterBlockPos.longHash(res.x, res.y, res.z); - if (favoring) { + if (isFavoring) { // see issue #18 - actionCost *= favored.calculate(hashCode); + actionCost *= favoring.calculate(hashCode); } PathNode neighbor = getNodeAtPosition(res.x, res.y, res.z, hashCode); double tentativeCost = currentNode.cost + actionCost; - if (tentativeCost < neighbor.cost) { - double improvementBy = neighbor.cost - tentativeCost; - // there are floating point errors caused by random combinations of traverse and diagonal over a flat area - // that means that sometimes there's a cost improvement of like 10 ^ -16 - // it's not worth the time to update the costs, decrease-key the heap, potentially repropagate, etc - if (improvementBy < 0.01 && minimumImprovementRepropagation) { - // who cares about a hundredth of a tick? that's half a millisecond for crying out loud! - continue; - } + if (neighbor.cost - tentativeCost > minimumImprovement) { neighbor.previous = currentNode; neighbor.cost = tentativeCost; neighbor.combinedCost = tentativeCost + neighbor.estimatedCostToGoal; @@ -159,15 +148,12 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel } else { openSet.insert(neighbor);//dont double count, dont insert into open set if it's already there } - for (int i = 0; i < bestSoFar.length; i++) { + for (int i = 0; i < COEFFICIENTS.length; i++) { double heuristic = neighbor.estimatedCostToGoal + neighbor.cost / COEFFICIENTS[i]; - if (heuristic < bestHeuristicSoFar[i]) { - if (bestHeuristicSoFar[i] - heuristic < 0.01 && minimumImprovementRepropagation) { - continue; - } + if (bestHeuristicSoFar[i] - heuristic > minimumImprovement) { bestHeuristicSoFar[i] = heuristic; bestSoFar[i] = neighbor; - if (getDistFromStartSq(neighbor) > MIN_DIST_PATH * MIN_DIST_PATH) { + if (failing && getDistFromStartSq(neighbor) > MIN_DIST_PATH * MIN_DIST_PATH) { failing = false; } } @@ -182,28 +168,10 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel System.out.println("Open set size: " + openSet.size()); System.out.println("PathNode map size: " + mapSize()); System.out.println((int) (numNodes * 1.0 / ((System.currentTimeMillis() - startTime) / 1000F)) + " nodes per second"); - double bestDist = 0; - for (int i = 0; i < bestSoFar.length; i++) { - if (bestSoFar[i] == null) { - continue; - } - double dist = getDistFromStartSq(bestSoFar[i]); - if (dist > bestDist) { - bestDist = dist; - } - if (dist > MIN_DIST_PATH * MIN_DIST_PATH) { // square the comparison since distFromStartSq is squared - logDebug("Took " + (System.currentTimeMillis() - startTime) + "ms, A* cost coefficient " + COEFFICIENTS[i] + ", " + numMovementsConsidered + " movements considered"); - if (COEFFICIENTS[i] >= 3) { - System.out.println("Warning: cost coefficient is greater than three! Probably means that"); - System.out.println("the path I found is pretty terrible (like sneak-bridging for dozens of blocks)"); - System.out.println("But I'm going to do it anyway, because yolo"); - } - System.out.println("Path goes for " + Math.sqrt(dist) + " blocks"); - return Optional.of(new Path(startNode, bestSoFar[i], numNodes, goal, calcContext)); - } + Optional result = bestSoFar(true, numNodes); + if (result.isPresent()) { + logDebug("Took " + (System.currentTimeMillis() - startTime) + "ms, " + numMovementsConsidered + " movements considered"); } - logDebug("Even with a cost coefficient of " + COEFFICIENTS[COEFFICIENTS.length - 1] + ", I couldn't get more than " + Math.sqrt(bestDist) + " blocks"); - logDebug("No path found =("); - return Optional.empty(); + return result; } } diff --git a/src/main/java/baritone/pathing/calc/AbstractNodeCostSearch.java b/src/main/java/baritone/pathing/calc/AbstractNodeCostSearch.java index 625e8e592..a9974e8df 100644 --- a/src/main/java/baritone/pathing/calc/AbstractNodeCostSearch.java +++ b/src/main/java/baritone/pathing/calc/AbstractNodeCostSearch.java @@ -34,7 +34,7 @@ import java.util.Optional; * * @author leijurv */ -public abstract class AbstractNodeCostSearch implements IPathFinder { +public abstract class AbstractNodeCostSearch implements IPathFinder, Helper { protected final int startX; protected final int startY; @@ -53,7 +53,7 @@ public abstract class AbstractNodeCostSearch implements IPathFinder { protected PathNode mostRecentConsidered; - protected PathNode[] bestSoFar; + protected final PathNode[] bestSoFar = new PathNode[COEFFICIENTS.length]; private volatile boolean isFinished; @@ -63,13 +63,23 @@ public abstract class AbstractNodeCostSearch implements IPathFinder { * 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 + * @see here */ - protected static final double[] COEFFICIENTS = {1.5, 2, 2.5, 3, 4, 5, 10}; // big TODO tune + protected static final double[] COEFFICIENTS = {1.5, 2, 2.5, 3, 4, 5, 10}; + /** * 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; + protected static final double MIN_DIST_PATH = 5; + + /** + * there are floating point errors caused by random combinations of traverse and diagonal over a flat area + * that means that sometimes there's a cost improvement of like 10 ^ -16 + * it's not worth the time to update the costs, decrease-key the heap, potentially repropagate, etc + *

+ * who cares about a hundredth of a tick? that's half a millisecond for crying out loud! + */ + protected static final double MIN_IMPROVEMENT = 0.01; AbstractNodeCostSearch(int startX, int startY, int startZ, Goal goal, CalculationContext context) { this.startX = startX; @@ -170,25 +180,43 @@ public abstract class AbstractNodeCostSearch implements IPathFinder { return Optional.ofNullable(mostRecentConsidered).map(node -> new Path(startNode, node, 0, goal, context)); } - protected int mapSize() { - return map.size(); + @Override + public Optional bestPathSoFar() { + return bestSoFar(false, 0); } - @Override - public Optional bestPathSoFar() { // TODO cleanup code duplication between here and AStarPathFinder + protected Optional bestSoFar(boolean logInfo, int numNodes) { if (startNode == null || bestSoFar == null) { return Optional.empty(); } - for (int i = 0; i < bestSoFar.length; i++) { + double bestDist = 0; + for (int i = 0; i < COEFFICIENTS.length; i++) { if (bestSoFar[i] == null) { continue; } - if (getDistFromStartSq(bestSoFar[i]) > MIN_DIST_PATH * MIN_DIST_PATH) { // square the comparison since distFromStartSq is squared - return Optional.of(new Path(startNode, bestSoFar[i], 0, goal, context)); + double dist = getDistFromStartSq(bestSoFar[i]); + if (dist > bestDist) { + bestDist = dist; + } + if (dist > MIN_DIST_PATH * MIN_DIST_PATH) { // square the comparison since distFromStartSq is squared + if (logInfo) { + if (COEFFICIENTS[i] >= 3) { + System.out.println("Warning: cost coefficient is greater than three! Probably means that"); + System.out.println("the path I found is pretty terrible (like sneak-bridging for dozens of blocks)"); + System.out.println("But I'm going to do it anyway, because yolo"); + } + System.out.println("Path goes for " + Math.sqrt(dist) + " blocks"); + logDebug("A* cost coefficient " + COEFFICIENTS[i]); + } + return Optional.of(new Path(startNode, bestSoFar[i], numNodes, goal, context)); } } // 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 + if (logInfo) { + logDebug("Even with a cost coefficient of " + COEFFICIENTS[COEFFICIENTS.length - 1] + ", I couldn't get more than " + Math.sqrt(bestDist) + " blocks"); + logDebug("No path found =("); + } return Optional.empty(); } @@ -205,4 +233,8 @@ public abstract class AbstractNodeCostSearch implements IPathFinder { public BetterBlockPos getStart() { return new BetterBlockPos(startX, startY, startZ); } + + protected int mapSize() { + return map.size(); + } } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java b/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java index a47fc2634..0abaccf7b 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java @@ -114,7 +114,7 @@ public class MovementDiagonal extends Movement { return; } IBlockState pb3 = context.get(destX, y + 1, z); - if (optionA == 0 && ((MovementHelper.avoidWalkingInto(pb2.getBlock()) && pb2.getBlock() != Blocks.WATER) || (MovementHelper.avoidWalkingInto(pb3.getBlock()) && pb3.getBlock() != Blocks.WATER))) { + if (optionA == 0 && ((MovementHelper.avoidWalkingInto(pb2.getBlock()) && pb2.getBlock() != Blocks.WATER) || MovementHelper.avoidWalkingInto(pb3.getBlock()))) { // at this point we're done calculating optionA, so we can check if it's actually possible to edge around in that direction return; } @@ -123,7 +123,7 @@ public class MovementDiagonal extends Movement { // and finally, if the cost is nonzero for both ways to approach this diagonal, it's not possible return; } - if (optionB == 0 && ((MovementHelper.avoidWalkingInto(pb0.getBlock()) && pb0.getBlock() != Blocks.WATER) || (MovementHelper.avoidWalkingInto(pb1.getBlock()) && pb1.getBlock() != Blocks.WATER))) { + if (optionB == 0 && ((MovementHelper.avoidWalkingInto(pb0.getBlock()) && pb0.getBlock() != Blocks.WATER) || MovementHelper.avoidWalkingInto(pb1.getBlock()))) { // and now that option B is fully calculated, see if we can edge around that way return; } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java index 38fd510ac..74acac48b 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java @@ -187,6 +187,11 @@ public class MovementParkour extends Movement { logDebug("Pausing parkour since hand is active"); return state; } + if (ctx.playerFeet().y < src.y) { + // we have fallen + logDebug("sorry"); + return state.setStatus(MovementStatus.UNREACHABLE); + } if (dist >= 4) { state.setInput(Input.SPRINT, true); } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java index 4f9b6b518..74e4b6f39 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java @@ -281,6 +281,8 @@ public class MovementTraverse extends Movement { } return state; } + default: + break; } if (whereAmI.equals(dest)) { // If we are in the block that we are trying to get to, we are sneaking over air and we need to place a block beneath us against the one we just walked off of diff --git a/src/main/java/baritone/pathing/path/PathExecutor.java b/src/main/java/baritone/pathing/path/PathExecutor.java index a2c0e2ff2..1b59f8e32 100644 --- a/src/main/java/baritone/pathing/path/PathExecutor.java +++ b/src/main/java/baritone/pathing/path/PathExecutor.java @@ -249,6 +249,8 @@ public class PathExecutor implements IPathExecutor, Helper { return true; } if (!movement.calculatedWhileLoaded() && currentCost - currentMovementOriginalCostEstimate > Baritone.settings().maxCostIncrease.get() && canCancel) { + // don't do this if the movement was calculated while loaded + // that means that this isn't a cache error, it's just part of the path interfering with a later part logDebug("Original cost " + currentMovementOriginalCostEstimate + " current cost " + currentCost + ". Cancelling."); cancel(); return true; @@ -377,21 +379,21 @@ public class PathExecutor implements IPathExecutor, Helper { } private boolean shouldSprintNextTick() { + boolean requested = behavior.baritone.getInputOverrideHandler().isInputForcedDown(Input.SPRINT); + + // we'll take it from here, no need for minecraft to see we're holding down control and sprint for us + behavior.baritone.getInputOverrideHandler().setInputForceState(Input.SPRINT, false); + // first and foremost, if allowSprint is off, or if we don't have enough hunger, don't try and sprint if (!new CalculationContext(behavior.baritone).canSprint) { - behavior.baritone.getInputOverrideHandler().setInputForceState(Input.SPRINT, false); return false; } // if the movement requested sprinting, then we're done - if (behavior.baritone.getInputOverrideHandler().isInputForcedDown(Input.SPRINT)) { - behavior.baritone.getInputOverrideHandler().setInputForceState(Input.SPRINT, false); + if (requested) { return true; } - // we'll take it from here, no need for minecraft to see we're holding down control and sprint for us - behavior.baritone.getInputOverrideHandler().setInputForceState(Input.SPRINT, false); - // however, descend doesn't request sprinting, beceause it doesn't know the context of what movement comes after it IMovement current = path.movements().get(pathPosition); if (current instanceof MovementDescend) {