baritone/src/main/java/baritone/behavior/PathingBehavior.java

469 lines
17 KiB
Java
Raw Normal View History

2018-08-08 03:16:53 +00:00
/*
* 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
2018-08-08 03:16:53 +00:00
* 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,
2018-08-08 03:16:53 +00:00
* 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.
2018-08-08 03:16:53 +00:00
*
* You should have received a copy of the GNU Lesser General Public License
2018-08-08 03:16:53 +00:00
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
*/
package baritone.behavior;
2018-08-22 20:15:56 +00:00
import baritone.Baritone;
import baritone.api.behavior.IPathingBehavior;
2018-08-28 00:37:21 +00:00
import baritone.api.event.events.PathEvent;
import baritone.api.event.events.PlayerUpdateEvent;
import baritone.api.event.events.RenderEvent;
import baritone.api.event.events.TickEvent;
2018-10-09 23:40:04 +00:00
import baritone.api.pathing.calc.IPath;
import baritone.api.pathing.calc.IPathFinder;
2018-09-24 16:57:06 +00:00
import baritone.api.pathing.goals.Goal;
import baritone.api.pathing.goals.GoalXZ;
2018-10-09 01:37:52 +00:00
import baritone.api.utils.BetterBlockPos;
import baritone.api.utils.interfaces.IGoalRenderPos;
2018-08-22 20:15:56 +00:00
import baritone.pathing.calc.AStarPathFinder;
import baritone.pathing.calc.AbstractNodeCostSearch;
2018-10-09 23:40:04 +00:00
import baritone.pathing.calc.CutoffPath;
import baritone.pathing.movement.MovementHelper;
2018-08-22 20:15:56 +00:00
import baritone.pathing.path.PathExecutor;
import baritone.utils.BlockBreakHelper;
2018-09-12 22:53:29 +00:00
import baritone.utils.Helper;
2018-08-22 20:15:56 +00:00
import baritone.utils.PathRenderer;
2018-08-05 22:38:11 +00:00
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.chunk.EmptyChunk;
2018-08-05 22:38:11 +00:00
2018-10-11 00:05:51 +00:00
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
2018-09-23 15:54:26 +00:00
import java.util.stream.Collectors;
2018-08-05 03:25:05 +00:00
public final class PathingBehavior extends Behavior implements IPathingBehavior, Helper {
2018-08-05 05:25:08 +00:00
2018-08-05 03:28:32 +00:00
public static final PathingBehavior INSTANCE = new PathingBehavior();
2018-08-05 03:25:05 +00:00
private PathExecutor current;
2018-08-13 18:49:36 +00:00
private PathExecutor next;
2018-08-05 03:25:05 +00:00
2018-08-05 22:38:11 +00:00
private Goal goal;
2018-08-13 19:35:44 +00:00
private volatile boolean isPathCalcInProgress;
private final Object pathCalcLock = new Object();
private final Object pathPlanLock = new Object();
private boolean lastAutoJump;
private final LinkedBlockingQueue<PathEvent> toDispatch = new LinkedBlockingQueue<>();
2018-09-17 03:16:05 +00:00
private PathingBehavior() {}
private void queuePathEvent(PathEvent event) {
toDispatch.add(event);
}
private void dispatchEvents() {
ArrayList<PathEvent> curr = new ArrayList<>();
toDispatch.drainTo(curr);
for (PathEvent event : curr) {
Baritone.INSTANCE.getGameEventHandler().onPathEvent(event);
}
2018-08-23 22:39:02 +00:00
}
2018-08-05 03:28:32 +00:00
@Override
public void onTick(TickEvent event) {
dispatchEvents();
2018-08-13 20:13:42 +00:00
if (event.getType() == TickEvent.Type.OUT) {
cancel();
2018-08-13 20:13:42 +00:00
return;
}
2018-09-12 22:25:01 +00:00
mc.playerController.setPlayerCapabilities(mc.player);
tickPath();
dispatchEvents();
}
private void tickPath() {
2018-08-13 20:13:42 +00:00
if (current == null) {
2018-08-05 03:28:32 +00:00
return;
}
boolean safe = current.onTick();
2018-08-13 19:35:44 +00:00
synchronized (pathPlanLock) {
if (current.failed() || current.finished()) {
current = null;
2018-08-16 23:51:43 +00:00
if (goal == null || goal.isInGoal(playerFeet())) {
2018-09-11 20:45:43 +00:00
logDebug("All done. At " + goal);
queuePathEvent(PathEvent.AT_GOAL);
2018-08-13 19:56:08 +00:00
next = null;
return;
}
2018-08-13 19:35:44 +00:00
if (next != null && !next.getPath().positions().contains(playerFeet())) {
// if the current path failed, we may not actually be on the next one, so make sure
2018-09-11 20:45:43 +00:00
logDebug("Discarding next path as it does not contain current position");
2018-08-13 19:35:44 +00:00
// for example if we had a nicely planned ahead path that starts where current ends
// that's all fine and good
// but if we fail in the middle of current
// we're nowhere close to our planned ahead path
// so need to discard it sadly.
queuePathEvent(PathEvent.DISCARD_NEXT);
2018-08-13 19:35:44 +00:00
next = null;
}
if (next != null) {
2018-09-11 20:45:43 +00:00
logDebug("Continuing on to planned next path");
queuePathEvent(PathEvent.CONTINUING_ONTO_PLANNED_NEXT);
2018-08-13 19:35:44 +00:00
current = next;
next = null;
current.onTick();
2018-08-13 19:35:44 +00:00
return;
}
// at this point, current just ended, but we aren't in the goal and have no plan for the future
synchronized (pathCalcLock) {
if (isPathCalcInProgress) {
queuePathEvent(PathEvent.PATH_FINISHED_NEXT_STILL_CALCULATING);
2018-08-13 19:35:44 +00:00
// if we aren't calculating right now
return;
}
queuePathEvent(PathEvent.CALC_STARTED);
2018-08-18 19:21:21 +00:00
findPathInNewThread(pathStart(), true, Optional.empty());
2018-08-13 19:35:44 +00:00
}
return;
}
// at this point, we know current is in progress
2018-10-27 21:41:25 +00:00
if (safe && next != null && next.snipsnapifpossible()) {
// a movement just ended; jump directly onto the next path
logDebug("Splicing into planned next path early...");
queuePathEvent(PathEvent.SPLICING_ONTO_NEXT_EARLY);
current = next;
next = null;
current.onTick();
return;
2018-08-13 19:35:44 +00:00
}
synchronized (pathCalcLock) {
if (isPathCalcInProgress) {
// if we aren't calculating right now
return;
}
if (next != null) {
// and we have no plan for what to do next
return;
}
2018-08-16 23:51:43 +00:00
if (goal == null || goal.isInGoal(current.getPath().getDest())) {
2018-08-13 19:35:44 +00:00
// and this path dosen't get us all the way there
return;
}
2018-08-16 00:29:45 +00:00
if (ticksRemainingInSegment().get() < Baritone.settings().planningTickLookAhead.get()) {
2018-08-13 19:35:44 +00:00
// and this path has 5 seconds or less left
2018-09-11 20:45:43 +00:00
logDebug("Path almost over. Planning ahead...");
queuePathEvent(PathEvent.NEXT_SEGMENT_CALC_STARTED);
2018-08-16 22:10:15 +00:00
findPathInNewThread(current.getPath().getDest(), false, Optional.of(current.getPath()));
2018-08-13 19:35:44 +00:00
}
}
2018-08-05 03:28:32 +00:00
}
}
@Override
public void onPlayerUpdate(PlayerUpdateEvent event) {
if (current != null) {
switch (event.getState()) {
case PRE:
lastAutoJump = mc.gameSettings.autoJump;
mc.gameSettings.autoJump = false;
break;
case POST:
mc.gameSettings.autoJump = lastAutoJump;
break;
2018-09-17 02:50:07 +00:00
default:
break;
}
}
}
@Override
2018-08-16 00:29:45 +00:00
public Optional<Double> ticksRemainingInSegment() {
if (current == null) {
return Optional.empty();
}
return Optional.of(current.getPath().ticksRemainingFrom(current.getPosition()));
}
@Override
2018-08-14 03:57:29 +00:00
public void setGoal(Goal goal) {
this.goal = goal;
2018-08-07 14:47:37 +00:00
}
2018-08-05 22:38:11 +00:00
2018-10-26 04:21:42 +00:00
public boolean setGoalAndPath(Goal goal) {
setGoal(goal);
return path();
}
@Override
2018-08-25 22:09:47 +00:00
public Goal getGoal() {
return goal;
}
2018-10-09 01:37:52 +00:00
@Override
2018-08-14 22:32:16 +00:00
public PathExecutor getCurrent() {
return current;
}
2018-10-09 01:37:52 +00:00
@Override
public PathExecutor getNext() {
return next;
}
2018-08-14 22:32:16 +00:00
2018-10-09 01:37:52 +00:00
@Override
2018-10-09 02:09:54 +00:00
public Optional<IPathFinder> getPathFinder() {
2018-10-09 01:37:52 +00:00
return Optional.ofNullable(AbstractNodeCostSearch.currentlyRunning());
2018-08-05 22:38:11 +00:00
}
@Override
public boolean isPathing() {
return this.current != null;
}
@Override
2018-09-12 22:25:01 +00:00
public void cancel() {
queuePathEvent(PathEvent.CANCELED);
2018-08-14 03:57:29 +00:00
current = null;
next = null;
Baritone.INSTANCE.getInputOverrideHandler().clearAllKeys();
2018-08-28 18:17:11 +00:00
AbstractNodeCostSearch.getCurrentlyRunning().ifPresent(AbstractNodeCostSearch::cancel);
BlockBreakHelper.stopBreakingBlock();
2018-09-12 01:26:10 +00:00
}
2018-09-24 16:57:06 +00:00
public void forceCancel() { // NOT exposed on public api
isPathCalcInProgress = false;
}
2018-08-28 18:43:28 +00:00
/**
* Start calculating a path if we aren't already
*
* @return true if this call started path calculation, false if it was already calculating or executing a path
*/
@Override
2018-08-28 18:43:28 +00:00
public boolean path() {
2018-08-31 21:58:23 +00:00
if (goal == null) {
return false;
}
if (goal.isInGoal(playerFeet())) {
return false;
}
2018-08-16 22:10:15 +00:00
synchronized (pathPlanLock) {
if (current != null) {
2018-08-28 18:43:28 +00:00
return false;
2018-08-14 03:57:29 +00:00
}
2018-08-16 22:10:15 +00:00
synchronized (pathCalcLock) {
if (isPathCalcInProgress) {
2018-08-28 18:43:28 +00:00
return false;
2018-08-16 22:10:15 +00:00
}
queuePathEvent(PathEvent.CALC_STARTED);
2018-08-18 19:21:21 +00:00
findPathInNewThread(pathStart(), true, Optional.empty());
2018-08-28 18:43:28 +00:00
return true;
2018-08-16 22:10:15 +00:00
}
2018-08-14 03:57:29 +00:00
}
}
/**
2018-10-11 00:05:51 +00:00
* See issue #209
*
* @return The starting {@link BlockPos} for a new path
*/
2018-10-11 00:05:51 +00:00
public BlockPos pathStart() {
2018-09-22 15:47:02 +00:00
BetterBlockPos feet = playerFeet();
2018-10-11 00:05:51 +00:00
if (!MovementHelper.canWalkOn(feet.down())) {
if (player().onGround) {
double playerX = player().posX;
double playerZ = player().posZ;
ArrayList<BetterBlockPos> closest = new ArrayList<>();
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
closest.add(new BetterBlockPos(feet.x + dx, feet.y, feet.z + dz));
}
}
closest.sort(Comparator.comparingDouble(pos -> ((pos.x + 0.5D) - playerX) * ((pos.x + 0.5D) - playerX) + ((pos.z + 0.5D) - playerZ) * ((pos.z + 0.5D) - playerZ)));
for (int i = 0; i < 4; i++) {
BetterBlockPos possibleSupport = closest.get(i);
double xDist = Math.abs((possibleSupport.x + 0.5D) - playerX);
double zDist = Math.abs((possibleSupport.z + 0.5D) - playerZ);
if (xDist > 0.8 && zDist > 0.8) {
// can't possibly be sneaking off of this one, we're too far away
continue;
}
if (MovementHelper.canWalkOn(possibleSupport.down()) && MovementHelper.canWalkThrough(possibleSupport) && MovementHelper.canWalkThrough(possibleSupport.up())) {
// this is plausible
logDebug("Faking path start assuming player is standing off the edge of a block");
return possibleSupport;
}
}
} else {
// !onGround
// we're in the middle of a jump
if (MovementHelper.canWalkOn(feet.down().down())) {
logDebug("Faking path start assuming player is midair and falling");
return feet.down();
}
}
2018-08-18 19:21:21 +00:00
}
return feet;
}
2018-08-05 22:38:11 +00:00
/**
* In a new thread, pathfind to target blockpos
*
* @param start
* @param talkAboutIt
*/
2018-08-16 22:10:15 +00:00
private void findPathInNewThread(final BlockPos start, final boolean talkAboutIt, final Optional<IPath> previous) {
2018-08-13 19:35:44 +00:00
synchronized (pathCalcLock) {
if (isPathCalcInProgress) {
throw new IllegalStateException("Already doing it");
}
isPathCalcInProgress = true;
}
2018-09-16 21:14:50 +00:00
Baritone.INSTANCE.getExecutor().execute(() -> {
2018-08-05 23:56:21 +00:00
if (talkAboutIt) {
2018-09-11 20:45:43 +00:00
logDebug("Starting to search for path from " + start + " to " + goal);
2018-08-05 23:56:21 +00:00
}
2018-08-05 22:38:11 +00:00
2018-08-19 18:45:50 +00:00
Optional<IPath> path = findPath(start, previous);
if (Baritone.settings().cutoffAtLoadBoundary.get()) {
2018-10-09 00:23:43 +00:00
path = path.map(p -> {
IPath result = p.cutoffAtLoadedChunks();
2018-10-09 00:23:43 +00:00
if (result instanceof CutoffPath) {
2018-10-09 00:23:43 +00:00
logDebug("Cutting off path at edge of loaded chunks");
logDebug("Length decreased by " + (p.length() - result.length()));
2018-10-09 00:23:43 +00:00
} else {
logDebug("Path ends within loaded chunks");
}
return result;
2018-10-09 00:23:43 +00:00
});
2018-08-19 18:45:50 +00:00
}
2018-10-09 00:23:43 +00:00
Optional<PathExecutor> executor = path.map(p -> {
IPath result = p.staticCutoff(goal);
2018-10-09 00:23:43 +00:00
if (result instanceof CutoffPath) {
logDebug("Static cutoff " + p.length() + " to " + result.length());
2018-10-09 00:23:43 +00:00
}
return result;
2018-10-09 00:23:43 +00:00
}).map(PathExecutor::new);
2018-08-23 22:39:02 +00:00
synchronized (pathPlanLock) {
if (current == null) {
if (executor.isPresent()) {
queuePathEvent(PathEvent.CALC_FINISHED_NOW_EXECUTING);
2018-08-23 22:39:02 +00:00
current = executor.get();
2018-08-13 19:35:44 +00:00
} else {
queuePathEvent(PathEvent.CALC_FAILED);
2018-08-23 22:39:02 +00:00
}
} else {
if (next == null) {
if (executor.isPresent()) {
queuePathEvent(PathEvent.NEXT_SEGMENT_CALC_FINISHED);
2018-08-23 22:39:02 +00:00
next = executor.get();
2018-08-13 19:35:44 +00:00
} else {
queuePathEvent(PathEvent.NEXT_CALC_FAILED);
2018-08-13 19:35:44 +00:00
}
2018-08-23 22:39:02 +00:00
} else {
throw new IllegalStateException("I have no idea what to do with this path");
2018-08-13 19:35:44 +00:00
}
}
2018-08-23 22:39:02 +00:00
}
2018-08-06 14:14:20 +00:00
if (talkAboutIt && current != null && current.getPath() != null) {
2018-08-16 23:51:43 +00:00
if (goal == null || goal.isInGoal(current.getPath().getDest())) {
2018-09-11 20:45:43 +00:00
logDebug("Finished finding a path from " + start + " to " + goal + ". " + current.getPath().getNumNodesConsidered() + " nodes considered");
2018-08-16 00:53:42 +00:00
} else {
2018-09-11 20:45:43 +00:00
logDebug("Found path segment from " + start + " towards " + goal + ". " + current.getPath().getNumNodesConsidered() + " nodes considered");
2018-08-16 00:53:42 +00:00
}
2018-08-06 14:14:20 +00:00
}
2018-08-13 19:35:44 +00:00
synchronized (pathCalcLock) {
isPathCalcInProgress = false;
}
2018-09-16 21:14:50 +00:00
});
2018-08-05 22:38:11 +00:00
}
/**
* Actually do the pathing
*
* @param start
* @return
*/
2018-08-16 22:10:15 +00:00
private Optional<IPath> findPath(BlockPos start, Optional<IPath> previous) {
Goal goal = this.goal;
2018-08-05 22:38:11 +00:00
if (goal == null) {
2018-09-11 20:45:43 +00:00
logDebug("no goal");
return Optional.empty();
2018-08-05 22:38:11 +00:00
}
if (Baritone.settings().simplifyUnloadedYCoord.get()) {
BlockPos pos = null;
2018-09-17 02:24:13 +00:00
if (goal instanceof IGoalRenderPos) {
2018-09-17 02:18:39 +00:00
pos = ((IGoalRenderPos) goal).getGoalPos();
2018-09-17 02:24:13 +00:00
}
2018-09-17 02:18:39 +00:00
if (pos != null && world().getChunk(pos) instanceof EmptyChunk) {
logDebug("Simplifying " + goal.getClass() + " to GoalXZ due to distance");
goal = new GoalXZ(pos.getX(), pos.getZ());
}
}
long timeout;
if (current == null) {
timeout = Baritone.settings().pathTimeoutMS.<Long>get();
} else {
timeout = Baritone.settings().planAheadTimeoutMS.<Long>get();
}
Optional<HashSet<Long>> favoredPositions;
if (Baritone.settings().backtrackCostFavoringCoefficient.get() == 1D) {
favoredPositions = Optional.empty();
} else {
favoredPositions = previous.map(IPath::positions).map(Collection::stream).map(x -> x.map(BetterBlockPos::longHash)).map(x -> x.collect(Collectors.toList())).map(HashSet::new); // <-- okay this is EPIC
}
2018-08-05 22:38:11 +00:00
try {
2018-10-03 14:57:24 +00:00
IPathFinder pf = new AStarPathFinder(start.getX(), start.getY(), start.getZ(), goal, favoredPositions);
return pf.calculate(timeout);
2018-08-05 22:38:11 +00:00
} catch (Exception e) {
2018-09-11 20:45:43 +00:00
logDebug("Pathing exception: " + e);
2018-08-05 22:38:11 +00:00
e.printStackTrace();
return Optional.empty();
2018-08-05 22:38:11 +00:00
}
}
2018-09-17 17:56:37 +00:00
public void revalidateGoal() {
if (!Baritone.settings().cancelOnGoalInvalidation.get()) {
return;
}
synchronized (pathPlanLock) {
if (current == null || goal == null) {
return;
}
Goal intended = current.getPath().getGoal();
BlockPos end = current.getPath().getDest();
if (intended.isInGoal(end) && !goal.isInGoal(end)) {
// this path used to end in the goal
// but the goal has changed, so there's no reason to continue...
cancel();
}
2018-09-17 17:56:37 +00:00
}
}
2018-08-05 21:48:10 +00:00
@Override
public void onRenderPass(RenderEvent event) {
2018-10-08 22:11:07 +00:00
PathRenderer.render(event, this);
2018-08-05 22:53:11 +00:00
}
@Override
public void onDisable() {
Baritone.INSTANCE.getInputOverrideHandler().clearAllKeys();
}
2018-08-05 03:19:32 +00:00
}