668 lines
30 KiB
Java
668 lines
30 KiB
Java
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package baritone.pathing.path;
|
|
|
|
import baritone.Baritone;
|
|
import baritone.api.pathing.calc.IPath;
|
|
import baritone.api.pathing.movement.ActionCosts;
|
|
import baritone.api.pathing.movement.IMovement;
|
|
import baritone.api.pathing.movement.MovementStatus;
|
|
import baritone.api.pathing.path.IPathExecutor;
|
|
import baritone.api.utils.BetterBlockPos;
|
|
import baritone.api.utils.IPlayerContext;
|
|
import baritone.api.utils.RotationUtils;
|
|
import baritone.api.utils.VecUtils;
|
|
import baritone.api.utils.input.Input;
|
|
import baritone.behavior.PathingBehavior;
|
|
import baritone.pathing.calc.AbstractNodeCostSearch;
|
|
import baritone.pathing.movement.CalculationContext;
|
|
import baritone.pathing.movement.Movement;
|
|
import baritone.pathing.movement.MovementHelper;
|
|
import baritone.pathing.movement.movements.*;
|
|
import baritone.utils.BlockStateInterface;
|
|
import baritone.utils.Helper;
|
|
import net.minecraft.block.BlockLiquid;
|
|
import net.minecraft.init.Blocks;
|
|
import net.minecraft.util.Tuple;
|
|
import net.minecraft.util.math.BlockPos;
|
|
import net.minecraft.util.math.Vec3d;
|
|
import net.minecraft.util.math.Vec3i;
|
|
|
|
import java.util.*;
|
|
|
|
import static baritone.api.pathing.movement.MovementStatus.*;
|
|
|
|
/**
|
|
* Behavior to execute a precomputed path. Does not (yet) deal with path segmentation or stitching
|
|
* or cutting (jumping onto the next path if it starts with a backtrack of this path's ending)
|
|
*
|
|
* @author leijurv
|
|
*/
|
|
public class PathExecutor implements IPathExecutor, Helper {
|
|
|
|
private static final double MAX_MAX_DIST_FROM_PATH = 3;
|
|
private static final double MAX_DIST_FROM_PATH = 2;
|
|
|
|
/**
|
|
* Default value is equal to 10 seconds. It's find to decrease it, but it must be at least 5.5s (110 ticks).
|
|
* For more information, see issue #102.
|
|
*
|
|
* @see <a href="https://github.com/cabaletta/baritone/issues/102">Issue #102</a>
|
|
* @see <a href="https://i.imgur.com/5s5GLnI.png">Anime</a>
|
|
*/
|
|
private static final double MAX_TICKS_AWAY = 200;
|
|
|
|
private final IPath path;
|
|
private int pathPosition;
|
|
private int ticksAway;
|
|
private int ticksOnCurrent;
|
|
private Double currentMovementOriginalCostEstimate;
|
|
private Integer costEstimateIndex;
|
|
private boolean failed;
|
|
private boolean recalcBP = true;
|
|
private HashSet<BlockPos> toBreak = new HashSet<>();
|
|
private HashSet<BlockPos> toPlace = new HashSet<>();
|
|
private HashSet<BlockPos> toWalkInto = new HashSet<>();
|
|
|
|
private PathingBehavior behavior;
|
|
private IPlayerContext ctx;
|
|
|
|
private boolean sprintNextTick;
|
|
|
|
public PathExecutor(PathingBehavior behavior, IPath path) {
|
|
this.behavior = behavior;
|
|
this.ctx = behavior.ctx;
|
|
this.path = path;
|
|
this.pathPosition = 0;
|
|
}
|
|
|
|
/**
|
|
* Tick this executor
|
|
*
|
|
* @return True if a movement just finished (and the player is therefore in a "stable" state, like,
|
|
* not sneaking out over lava), false otherwise
|
|
*/
|
|
public boolean onTick() {
|
|
if (pathPosition == path.length() - 1) {
|
|
pathPosition++;
|
|
}
|
|
if (pathPosition >= path.length()) {
|
|
return true; // stop bugging me, I'm done
|
|
}
|
|
BetterBlockPos whereShouldIBe = path.positions().get(pathPosition);
|
|
BetterBlockPos whereAmI = ctx.playerFeet();
|
|
if (!whereShouldIBe.equals(whereAmI)) {
|
|
if (!Blocks.AIR.equals(BlockStateInterface.getBlock(ctx, whereAmI.down()))) {//do not skip if standing on air, because our position isn't stable to skip
|
|
for (int i = 0; i < pathPosition - 1 && i < path.length(); i++) {//this happens for example when you lag out and get teleported back a couple blocks
|
|
if (whereAmI.equals(path.positions().get(i))) {
|
|
logDebug("Skipping back " + (pathPosition - i) + " steps, to " + i);
|
|
int previousPos = pathPosition;
|
|
pathPosition = Math.max(i - 1, 0); // previous step might not actually be done
|
|
for (int j = pathPosition; j <= previousPos; j++) {
|
|
path.movements().get(j).reset();
|
|
}
|
|
onChangeInPathPosition();
|
|
onTick();
|
|
return false;
|
|
}
|
|
}
|
|
for (int i = pathPosition + 3; i < path.length(); i++) { //dont check pathPosition+1. the movement tells us when it's done (e.g. sneak placing)
|
|
// also don't check pathPosition+2 because reasons
|
|
if (whereAmI.equals(path.positions().get(i))) {
|
|
if (i - pathPosition > 2) {
|
|
logDebug("Skipping forward " + (i - pathPosition) + " steps, to " + i);
|
|
}
|
|
//System.out.println("Double skip sundae");
|
|
pathPosition = i - 1;
|
|
onChangeInPathPosition();
|
|
onTick();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Tuple<Double, BlockPos> status = closestPathPos(path);
|
|
if (possiblyOffPath(status, MAX_DIST_FROM_PATH)) {
|
|
ticksAway++;
|
|
System.out.println("FAR AWAY FROM PATH FOR " + ticksAway + " TICKS. Current distance: " + status.getFirst() + ". Threshold: " + MAX_DIST_FROM_PATH);
|
|
if (ticksAway > MAX_TICKS_AWAY) {
|
|
logDebug("Too far away from path for too long, cancelling path");
|
|
cancel();
|
|
return false;
|
|
}
|
|
} else {
|
|
ticksAway = 0;
|
|
}
|
|
if (possiblyOffPath(status, MAX_MAX_DIST_FROM_PATH)) { // ok, stop right away, we're way too far.
|
|
logDebug("too far from path");
|
|
cancel();
|
|
return false;
|
|
}
|
|
//this commented block is literally cursed.
|
|
/*Out.log(actions.get(pathPosition));
|
|
if (pathPosition < actions.size() - 1) {//if there are two ActionBridges in a row and they are at right angles, walk diagonally. This makes it so you walk at 45 degrees along a zigzag path instead of doing inefficient zigging and zagging
|
|
if ((actions.get(pathPosition) instanceof ActionBridge) && (actions.get(pathPosition + 1) instanceof ActionBridge)) {
|
|
ActionBridge curr = (ActionBridge) actions.get(pathPosition);
|
|
ActionBridge next = (ActionBridge) actions.get(pathPosition + 1);
|
|
if (curr.dx() != next.dx() || curr.dz() != next.dz()) {//two movement are not parallel, so this is a right angle
|
|
if (curr.amIGood() && next.amIGood()) {//nothing in the way
|
|
BlockPos cornerToCut1 = new BlockPos(next.to.getX() - next.from.getX() + curr.from.getX(), next.to.getY(), next.to.getZ() - next.from.getZ() + curr.from.getZ());
|
|
BlockPos cornerToCut2 = cornerToCut1.up();
|
|
//Block corner1 = Baritone.get(cornerToCut1).getBlock();
|
|
//Block corner2 = Baritone.get(cornerToCut2).getBlock();
|
|
//Out.gui("Cutting conner " + cornerToCut1 + " " + corner1, Out.Mode.Debug);
|
|
if (!Action.avoidWalkingInto(cornerToCut1) && !Action.avoidWalkingInto(cornerToCut2)) {
|
|
double x = (next.from.getX() + next.to.getX() + 1.0D) * 0.5D;
|
|
double z = (next.from.getZ() + next.to.getZ() + 1.0D) * 0.5D;
|
|
MovementManager.clearMovement();
|
|
if (!MovementManager.forward && curr.oneInTen != null && curr.oneInTen) {
|
|
MovementManager.clearMovement();
|
|
MovementManager.forward = LookManager.lookAtCoords(x, 0, z, false);
|
|
} else {
|
|
MovementManager.moveTowardsCoords(x, 0, z);
|
|
}
|
|
if (MovementManager.forward && !MovementManager.backward) {
|
|
thePlayer.setSprinting(true);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
//long start = System.nanoTime() / 1000000L;
|
|
BlockStateInterface bsi = new BlockStateInterface(ctx);
|
|
for (int i = pathPosition - 10; i < pathPosition + 10; i++) {
|
|
if (i < 0 || i >= path.movements().size()) {
|
|
continue;
|
|
}
|
|
Movement m = (Movement) path.movements().get(i);
|
|
List<BlockPos> prevBreak = m.toBreak(bsi);
|
|
List<BlockPos> prevPlace = m.toPlace(bsi);
|
|
List<BlockPos> prevWalkInto = m.toWalkInto(bsi);
|
|
m.resetBlockCache();
|
|
if (!prevBreak.equals(m.toBreak(bsi))) {
|
|
recalcBP = true;
|
|
}
|
|
if (!prevPlace.equals(m.toPlace(bsi))) {
|
|
recalcBP = true;
|
|
}
|
|
if (!prevWalkInto.equals(m.toWalkInto(bsi))) {
|
|
recalcBP = true;
|
|
}
|
|
}
|
|
if (recalcBP) {
|
|
HashSet<BlockPos> newBreak = new HashSet<>();
|
|
HashSet<BlockPos> newPlace = new HashSet<>();
|
|
HashSet<BlockPos> newWalkInto = new HashSet<>();
|
|
for (int i = pathPosition; i < path.movements().size(); i++) {
|
|
Movement movement = (Movement) path.movements().get(i);
|
|
newBreak.addAll(movement.toBreak(bsi));
|
|
newPlace.addAll(movement.toPlace(bsi));
|
|
newWalkInto.addAll(movement.toWalkInto(bsi));
|
|
}
|
|
toBreak = newBreak;
|
|
toPlace = newPlace;
|
|
toWalkInto = newWalkInto;
|
|
recalcBP = false;
|
|
}
|
|
/*long end = System.nanoTime() / 1000000L;
|
|
if (end - start > 0) {
|
|
System.out.println("Recalculating break and place took " + (end - start) + "ms");
|
|
}*/
|
|
IMovement movement = path.movements().get(pathPosition);
|
|
boolean canCancel = movement.safeToCancel();
|
|
if (costEstimateIndex == null || costEstimateIndex != pathPosition) {
|
|
costEstimateIndex = pathPosition;
|
|
// do this only once, when the movement starts, and deliberately get the cost as cached when this path was calculated, not the cost as it is right now
|
|
currentMovementOriginalCostEstimate = movement.getCost();
|
|
for (int i = 1; i < Baritone.settings().costVerificationLookahead.get() && pathPosition + i < path.length() - 1; i++) {
|
|
if (((Movement) path.movements().get(pathPosition + i)).calculateCost(behavior.secretInternalGetCalculationContext()) >= ActionCosts.COST_INF && canCancel) {
|
|
logDebug("Something has changed in the world and a future movement has become impossible. Cancelling.");
|
|
cancel();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
double currentCost = ((Movement) movement).recalculateCost(behavior.secretInternalGetCalculationContext());
|
|
if (currentCost >= ActionCosts.COST_INF && canCancel) {
|
|
logDebug("Something has changed in the world and this movement has become impossible. Cancelling.");
|
|
cancel();
|
|
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;
|
|
}
|
|
if (shouldPause()) {
|
|
logDebug("Pausing since current best path is a backtrack");
|
|
clearKeys();
|
|
return true;
|
|
}
|
|
MovementStatus movementStatus = movement.update();
|
|
if (movementStatus == UNREACHABLE || movementStatus == FAILED) {
|
|
logDebug("Movement returns status " + movementStatus);
|
|
cancel();
|
|
return true;
|
|
}
|
|
if (movementStatus == SUCCESS) {
|
|
//System.out.println("Movement done, next path");
|
|
pathPosition++;
|
|
onChangeInPathPosition();
|
|
onTick();
|
|
return true;
|
|
} else {
|
|
sprintNextTick = shouldSprintNextTick();
|
|
if (!sprintNextTick) {
|
|
ctx.player().setSprinting(false); // letting go of control doesn't make you stop sprinting actually
|
|
}
|
|
ticksOnCurrent++;
|
|
if (ticksOnCurrent > currentMovementOriginalCostEstimate + Baritone.settings().movementTimeoutTicks.get()) {
|
|
// only cancel if the total time has exceeded the initial estimate
|
|
// as you break the blocks required, the remaining cost goes down, to the point where
|
|
// ticksOnCurrent is greater than recalculateCost + 100
|
|
// this is why we cache cost at the beginning, and don't recalculate for this comparison every tick
|
|
logDebug("This movement has taken too long (" + ticksOnCurrent + " ticks, expected " + currentMovementOriginalCostEstimate + "). Cancelling.");
|
|
cancel();
|
|
return true;
|
|
}
|
|
}
|
|
return canCancel; // movement is in progress, but if it reports cancellable, PathingBehavior is good to cut onto the next path
|
|
}
|
|
|
|
private Tuple<Double, BlockPos> closestPathPos(IPath path) {
|
|
double best = -1;
|
|
BlockPos bestPos = null;
|
|
for (BlockPos pos : path.positions()) {
|
|
double dist = VecUtils.entityDistanceToCenter(ctx.player(), pos);
|
|
if (dist < best || best == -1) {
|
|
best = dist;
|
|
bestPos = pos;
|
|
}
|
|
}
|
|
return new Tuple<>(best, bestPos);
|
|
}
|
|
|
|
private boolean shouldPause() {
|
|
Optional<AbstractNodeCostSearch> current = behavior.getInProgress();
|
|
if (!current.isPresent()) {
|
|
return false;
|
|
}
|
|
if (!ctx.player().onGround) {
|
|
return false;
|
|
}
|
|
if (!MovementHelper.canWalkOn(ctx, ctx.playerFeet().down())) {
|
|
// we're in some kind of sketchy situation, maybe parkouring
|
|
return false;
|
|
}
|
|
if (!MovementHelper.canWalkThrough(ctx, ctx.playerFeet()) || !MovementHelper.canWalkThrough(ctx, ctx.playerFeet().up())) {
|
|
// suffocating?
|
|
return false;
|
|
}
|
|
if (!path.movements().get(pathPosition).safeToCancel()) {
|
|
return false;
|
|
}
|
|
Optional<IPath> currentBest = current.get().bestPathSoFar();
|
|
if (!currentBest.isPresent()) {
|
|
return false;
|
|
}
|
|
List<BetterBlockPos> positions = currentBest.get().positions();
|
|
if (positions.size() < 3) {
|
|
return false; // not long enough yet to justify pausing, its far from certain we'll actually take this route
|
|
}
|
|
// the first block of the next path will always overlap
|
|
// no need to pause our very last movement when it would have otherwise cleanly exited with MovementStatus SUCCESS
|
|
positions = positions.subList(1, positions.size());
|
|
return positions.contains(ctx.playerFeet());
|
|
}
|
|
|
|
private boolean possiblyOffPath(Tuple<Double, BlockPos> status, double leniency) {
|
|
double distanceFromPath = status.getFirst();
|
|
if (distanceFromPath > leniency) {
|
|
// when we're midair in the middle of a fall, we're very far from both the beginning and the end, but we aren't actually off path
|
|
if (path.movements().get(pathPosition) instanceof MovementFall) {
|
|
BlockPos fallDest = path.positions().get(pathPosition + 1); // .get(pathPosition) is the block we fell off of
|
|
return VecUtils.entityFlatDistanceToCenter(ctx.player(), fallDest) >= leniency; // ignore Y by using flat distance
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Regardless of current path position, snap to the current player feet if possible
|
|
*
|
|
* @return Whether or not it was possible to snap to the current player feet
|
|
*/
|
|
public boolean snipsnapifpossible() {
|
|
if (!ctx.player().onGround && !(ctx.world().getBlockState(ctx.playerFeet()).getBlock() instanceof BlockLiquid)) {
|
|
// if we're falling in the air, and not in water, don't splice
|
|
return false;
|
|
} else {
|
|
// we are either onGround or in liquid
|
|
if (ctx.player().motionY < -0.1) {
|
|
// if we are strictly moving downwards (not stationary)
|
|
// we could be falling through water, which could be unsafe to splice
|
|
return false; // so don't
|
|
}
|
|
}
|
|
int index = path.positions().indexOf(ctx.playerFeet());
|
|
if (index == -1) {
|
|
return false;
|
|
}
|
|
pathPosition = index; // jump directly to current position
|
|
clearKeys();
|
|
return true;
|
|
}
|
|
|
|
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) {
|
|
return false;
|
|
}
|
|
IMovement current = path.movements().get(pathPosition);
|
|
|
|
// traverse requests sprinting, so we need to do this check first
|
|
if (current instanceof MovementTraverse && pathPosition < path.length() - 3) {
|
|
IMovement next = path.movements().get(pathPosition + 1);
|
|
if (next instanceof MovementAscend && sprintableAscend(ctx, (MovementTraverse) current, (MovementAscend) next, path.movements().get(pathPosition + 2))) {
|
|
if (skipNow(ctx, current, next)) {
|
|
logDebug("Skipping traverse to straight ascend");
|
|
pathPosition++;
|
|
onChangeInPathPosition();
|
|
onTick();
|
|
behavior.baritone.getInputOverrideHandler().setInputForceState(Input.JUMP, true);
|
|
return true;
|
|
} else {
|
|
logDebug("Too far to the side to safely sprint ascend");
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the movement requested sprinting, then we're done
|
|
if (requested) {
|
|
return true;
|
|
}
|
|
|
|
// however, descend and ascend don't request sprinting, because they don't know the context of what movement comes after it
|
|
if (current instanceof MovementDescend) {
|
|
|
|
if (((MovementDescend) current).safeMode() && !((MovementDescend) current).skipToAscend()) {
|
|
logDebug("Sprinting would be unsafe");
|
|
return false;
|
|
}
|
|
|
|
if (pathPosition < path.length() - 2) {
|
|
IMovement next = path.movements().get(pathPosition + 1);
|
|
if (next instanceof MovementAscend && current.getDirection().up().equals(next.getDirection().down())) {
|
|
// a descend then an ascend in the same direction
|
|
pathPosition++;
|
|
onChangeInPathPosition();
|
|
onTick();
|
|
// okay to skip clearKeys and / or onChangeInPathPosition here since this isn't possible to repeat, since it's asymmetric
|
|
logDebug("Skipping descend to straight ascend");
|
|
return true;
|
|
}
|
|
if (canSprintFromDescendInto(ctx, current, next)) {
|
|
if (ctx.playerFeet().equals(current.getDest())) {
|
|
pathPosition++;
|
|
onChangeInPathPosition();
|
|
onTick();
|
|
}
|
|
return true;
|
|
}
|
|
//logDebug("Turning off sprinting " + movement + " " + next + " " + movement.getDirection() + " " + next.getDirection().down() + " " + next.getDirection().down().equals(movement.getDirection()));
|
|
}
|
|
}
|
|
if (current instanceof MovementAscend && pathPosition != 0) {
|
|
IMovement prev = path.movements().get(pathPosition - 1);
|
|
if (prev instanceof MovementDescend && prev.getDirection().up().equals(current.getDirection().down())) {
|
|
BlockPos center = current.getSrc().up();
|
|
if (ctx.player().posY >= center.getY()) { // playerFeet adds 0.1251 to account for soul sand
|
|
behavior.baritone.getInputOverrideHandler().setInputForceState(Input.JUMP, false);
|
|
return true;
|
|
}
|
|
}
|
|
if (pathPosition < path.length() - 2 && prev instanceof MovementTraverse && sprintableAscend(ctx, (MovementTraverse) prev, (MovementAscend) current, path.movements().get(pathPosition + 1))) {
|
|
return true;
|
|
}
|
|
}
|
|
if (current instanceof MovementFall) {
|
|
Tuple<Vec3d, BlockPos> data = overrideFall((MovementFall) current);
|
|
if (data != null) {
|
|
BlockPos fallDest = data.getSecond();
|
|
if (!path.positions().contains(fallDest)) {
|
|
throw new IllegalStateException();
|
|
}
|
|
if (ctx.playerFeet().equals(fallDest)) {
|
|
pathPosition = path.positions().indexOf(fallDest);
|
|
onChangeInPathPosition();
|
|
onTick();
|
|
return true;
|
|
}
|
|
clearKeys();
|
|
behavior.baritone.getLookBehavior().updateTarget(RotationUtils.calcRotationFromVec3d(ctx.playerHead(), data.getFirst(), ctx.playerRotations()), false);
|
|
behavior.baritone.getInputOverrideHandler().setInputForceState(Input.MOVE_FORWARD, true);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private Tuple<Vec3d, BlockPos> overrideFall(MovementFall movement) {
|
|
Vec3i dir = movement.getDirection();
|
|
if (dir.getY() < -3) {
|
|
return null;
|
|
}
|
|
if (!movement.toBreakCached.isEmpty()) {
|
|
return null; // it's breaking
|
|
}
|
|
Vec3i flatDir = new Vec3i(dir.getX(), 0, dir.getZ());
|
|
int i;
|
|
outer:
|
|
for (i = pathPosition + 1; i < path.length() - 1 && i < pathPosition + 3; i++) {
|
|
IMovement next = path.movements().get(i);
|
|
if (!(next instanceof MovementTraverse)) {
|
|
break;
|
|
}
|
|
if (!flatDir.equals(next.getDirection())) {
|
|
break;
|
|
}
|
|
for (int y = next.getDest().y; y <= movement.getSrc().y + 1; y++) {
|
|
BlockPos chk = new BlockPos(next.getDest().x, y, next.getDest().z);
|
|
if (!MovementHelper.fullyPassable(ctx.world().getBlockState(chk))) {
|
|
break outer;
|
|
}
|
|
}
|
|
if (!MovementHelper.canWalkOn(ctx, next.getDest().down())) {
|
|
break;
|
|
}
|
|
}
|
|
i--;
|
|
if (i == pathPosition) {
|
|
return null; // no valid extension exists
|
|
}
|
|
double len = i - pathPosition - 0.4;
|
|
return new Tuple<>(
|
|
new Vec3d(flatDir.getX() * len + movement.getDest().x + 0.5, movement.getDest().y, flatDir.getZ() * len + movement.getDest().z + 0.5),
|
|
movement.getDest().add(flatDir.getX() * (i - pathPosition), 0, flatDir.getZ() * (i - pathPosition)));
|
|
}
|
|
|
|
private static boolean skipNow(IPlayerContext ctx, IMovement current, IMovement next) {
|
|
double offTarget = Math.abs(current.getDirection().getX() * (current.getSrc().z + 0.5D - ctx.player().posZ)) + Math.abs(current.getDirection().getZ() * (current.getSrc().x + 0.5D - ctx.player().posX));
|
|
if (offTarget > 0.1) {
|
|
return false;
|
|
}
|
|
// we are centered
|
|
BlockPos headBonk = current.getSrc().subtract(current.getDirection()).up(2);
|
|
if (MovementHelper.fullyPassable(ctx.world().getBlockState(headBonk))) {
|
|
return true;
|
|
}
|
|
// wait 0.3
|
|
double flatDist = Math.abs(current.getDirection().getX() * (headBonk.getX() + 0.5D - ctx.player().posX)) + Math.abs(current.getDirection().getZ() * (headBonk.getZ() + 0.5 - ctx.player().posZ));
|
|
return flatDist > 0.8;
|
|
}
|
|
|
|
private static boolean sprintableAscend(IPlayerContext ctx, MovementTraverse current, MovementAscend next, IMovement nextnext) {
|
|
if (!Baritone.settings().sprintAscends.get()) {
|
|
return false;
|
|
}
|
|
if (!current.getDirection().equals(next.getDirection().down())) {
|
|
return false;
|
|
}
|
|
if (nextnext.getDirection().getX() != next.getDirection().getX() || nextnext.getDirection().getZ() != next.getDirection().getZ()) {
|
|
return false;
|
|
}
|
|
if (!MovementHelper.canWalkOn(ctx, current.getDest().down())) {
|
|
return false;
|
|
}
|
|
if (!MovementHelper.canWalkOn(ctx, next.getDest().down())) {
|
|
return false;
|
|
}
|
|
if (!next.toBreakCached.isEmpty()) {
|
|
return false; // it's breaking
|
|
}
|
|
for (int x = 0; x < 2; x++) {
|
|
for (int y = 0; y < 3; y++) {
|
|
BlockPos chk = current.getSrc().up(y);
|
|
if (x == 1) {
|
|
chk = chk.add(current.getDirection());
|
|
}
|
|
if (!MovementHelper.fullyPassable(ctx.world().getBlockState(chk))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (MovementHelper.avoidWalkingInto(ctx.world().getBlockState(current.getSrc().up(3)).getBlock())) {
|
|
return false;
|
|
}
|
|
if (MovementHelper.avoidWalkingInto(ctx.world().getBlockState(next.getDest().up(2)).getBlock())) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static boolean canSprintFromDescendInto(IPlayerContext ctx, IMovement current, IMovement next) {
|
|
if (next instanceof MovementDescend && next.getDirection().equals(current.getDirection())) {
|
|
return true;
|
|
}
|
|
if (next instanceof MovementTraverse && next.getDirection().down().equals(current.getDirection()) && MovementHelper.canWalkOn(ctx, next.getDest().down())) {
|
|
return true;
|
|
}
|
|
return next instanceof MovementDiagonal && Baritone.settings().allowOvershootDiagonalDescend.get();
|
|
}
|
|
|
|
private void onChangeInPathPosition() {
|
|
clearKeys();
|
|
ticksOnCurrent = 0;
|
|
}
|
|
|
|
private void clearKeys() {
|
|
// i'm just sick and tired of this snippet being everywhere lol
|
|
behavior.baritone.getInputOverrideHandler().clearAllKeys();
|
|
}
|
|
|
|
private void cancel() {
|
|
clearKeys();
|
|
behavior.baritone.getInputOverrideHandler().getBlockBreakHelper().stopBreakingBlock();
|
|
pathPosition = path.length() + 3;
|
|
failed = true;
|
|
}
|
|
|
|
@Override
|
|
public int getPosition() {
|
|
return pathPosition;
|
|
}
|
|
|
|
public PathExecutor trySplice(PathExecutor next) {
|
|
if (next == null) {
|
|
return cutIfTooLong();
|
|
}
|
|
return SplicedPath.trySplice(path, next.path, false).map(path -> {
|
|
if (!path.getDest().equals(next.getPath().getDest())) {
|
|
throw new IllegalStateException();
|
|
}
|
|
PathExecutor ret = new PathExecutor(behavior, path);
|
|
ret.pathPosition = pathPosition;
|
|
ret.currentMovementOriginalCostEstimate = currentMovementOriginalCostEstimate;
|
|
ret.costEstimateIndex = costEstimateIndex;
|
|
ret.ticksOnCurrent = ticksOnCurrent;
|
|
return ret;
|
|
}).orElse(cutIfTooLong());
|
|
}
|
|
|
|
private PathExecutor cutIfTooLong() {
|
|
if (pathPosition > Baritone.settings().maxPathHistoryLength.get()) {
|
|
int cutoffAmt = Baritone.settings().pathHistoryCutoffAmount.get();
|
|
CutoffPath newPath = new CutoffPath(path, cutoffAmt, path.length() - 1);
|
|
if (!newPath.getDest().equals(path.getDest())) {
|
|
throw new IllegalStateException();
|
|
}
|
|
logDebug("Discarding earliest segment movements, length cut from " + path.length() + " to " + newPath.length());
|
|
PathExecutor ret = new PathExecutor(behavior, newPath);
|
|
ret.pathPosition = pathPosition - cutoffAmt;
|
|
ret.currentMovementOriginalCostEstimate = currentMovementOriginalCostEstimate;
|
|
if (costEstimateIndex != null) {
|
|
ret.costEstimateIndex = costEstimateIndex - cutoffAmt;
|
|
}
|
|
ret.ticksOnCurrent = ticksOnCurrent;
|
|
return ret;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public IPath getPath() {
|
|
return path;
|
|
}
|
|
|
|
public boolean failed() {
|
|
return failed;
|
|
}
|
|
|
|
public boolean finished() {
|
|
return pathPosition >= path.length();
|
|
}
|
|
|
|
public Set<BlockPos> toBreak() {
|
|
return Collections.unmodifiableSet(toBreak);
|
|
}
|
|
|
|
public Set<BlockPos> toPlace() {
|
|
return Collections.unmodifiableSet(toPlace);
|
|
}
|
|
|
|
public Set<BlockPos> toWalkInto() {
|
|
return Collections.unmodifiableSet(toWalkInto);
|
|
}
|
|
|
|
public boolean isSprinting() {
|
|
return sprintNextTick;
|
|
}
|
|
}
|