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

411 lines
16 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.Behavior;
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-08-22 20:15:56 +00:00
import baritone.pathing.calc.AStarPathFinder;
import baritone.pathing.calc.AbstractNodeCostSearch;
import baritone.pathing.calc.IPathFinder;
2018-09-17 02:50:07 +00:00
import baritone.pathing.goals.Goal;
import baritone.pathing.goals.GoalXZ;
import baritone.pathing.movement.MovementHelper;
2018-08-22 20:15:56 +00:00
import baritone.pathing.path.IPath;
import baritone.pathing.path.PathExecutor;
import baritone.utils.BlockStateInterface;
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-09-17 02:18:39 +00:00
import baritone.utils.interfaces.IGoalRenderPos;
2018-08-18 19:21:21 +00:00
import net.minecraft.init.Blocks;
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
import java.awt.*;
import java.util.Collections;
import java.util.Optional;
2018-08-05 03:25:05 +00:00
2018-09-12 22:53:29 +00:00
public final class PathingBehavior extends Behavior implements 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;
2018-09-17 03:16:05 +00:00
private PathingBehavior() {}
2018-08-23 22:39:02 +00:00
private void dispatchPathEvent(PathEvent event) {
2018-09-16 21:14:50 +00:00
Baritone.INSTANCE.getExecutor().execute(() -> 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) {
2018-08-13 20:13:42 +00:00
if (event.getType() == TickEvent.Type.OUT) {
2018-09-12 22:25:01 +00:00
this.cancel();
2018-08-13 20:13:42 +00:00
return;
}
2018-09-12 22:25:01 +00:00
mc.playerController.setPlayerCapabilities(mc.player);
2018-08-13 20:13:42 +00:00
if (current == null) {
2018-08-05 03:28:32 +00:00
return;
}
2018-08-13 19:35:44 +00:00
boolean safe = current.onTick(event);
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);
2018-08-23 22:39:02 +00:00
dispatchPathEvent(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.
2018-08-23 22:39:02 +00:00
dispatchPathEvent(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");
2018-08-23 22:39:02 +00:00
dispatchPathEvent(PathEvent.CONTINUING_ONTO_PLANNED_NEXT);
2018-08-13 19:35:44 +00:00
current = next;
next = null;
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) {
2018-08-23 22:39:02 +00:00
dispatchPathEvent(PathEvent.PATH_FINISHED_NEXT_STILL_CALCULATING);
2018-08-13 19:35:44 +00:00
// if we aren't calculating right now
return;
}
2018-08-23 22:39:02 +00:00
dispatchPathEvent(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
if (safe) {
// a movement just ended
if (next != null) {
if (next.getPath().positions().contains(playerFeet())) {
// jump directly onto the next path
2018-09-11 20:45:43 +00:00
logDebug("Splicing into planned next path early...");
2018-08-23 22:39:02 +00:00
dispatchPathEvent(PathEvent.SPLICING_ONTO_NEXT_EARLY);
2018-08-13 19:35:44 +00:00
current = next;
next = null;
return;
}
}
}
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...");
2018-08-23 22:39:02 +00:00
dispatchPathEvent(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;
}
}
}
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()));
}
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-08-25 22:09:47 +00:00
public Goal getGoal() {
return goal;
}
2018-08-14 22:32:16 +00:00
public PathExecutor getCurrent() {
return current;
}
public PathExecutor getNext() {
return next;
}
2018-08-14 22:32:16 +00:00
public Optional<IPath> getPath() {
return Optional.ofNullable(current).map(PathExecutor::getPath);
2018-08-05 22:38:11 +00:00
}
2018-09-12 22:25:01 +00:00
public void cancel() {
dispatchPathEvent(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);
2018-09-12 01:26:10 +00:00
}
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
*/
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
}
2018-08-23 22:39:02 +00:00
dispatchPathEvent(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-08-18 19:21:21 +00:00
public BlockPos pathStart() {
BlockPos feet = playerFeet();
if (BlockStateInterface.get(feet.down()).getBlock().equals(Blocks.AIR) && MovementHelper.canWalkOn(feet.down().down())) {
2018-08-18 19:21:21 +00:00
return feet.down();
}
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()) {
path = path.map(IPath::cutoffAtLoadedChunks);
}
2018-08-23 22:39:02 +00:00
Optional<PathExecutor> executor = path.map(p -> p.staticCutoff(goal)).map(PathExecutor::new);
synchronized (pathPlanLock) {
if (current == null) {
if (executor.isPresent()) {
dispatchPathEvent(PathEvent.CALC_FINISHED_NOW_EXECUTING);
current = executor.get();
2018-08-13 19:35:44 +00:00
} else {
2018-08-23 22:39:02 +00:00
dispatchPathEvent(PathEvent.CALC_FAILED);
}
} else {
if (next == null) {
if (executor.isPresent()) {
dispatchPathEvent(PathEvent.NEXT_SEGMENT_CALC_FINISHED);
next = executor.get();
2018-08-13 19:35:44 +00:00
} else {
2018-08-23 22:39:02 +00:00
dispatchPathEvent(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
// TODO simplify each individual goal in a GoalComposite
if (pos != null && world().getChunk(pos) instanceof EmptyChunk) {
2018-09-11 20:45:43 +00:00
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();
}
2018-08-05 22:38:11 +00:00
try {
2018-08-16 22:10:15 +00:00
IPathFinder pf = new AStarPathFinder(start, goal, previous.map(IPath::positions));
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;
}
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-08-05 21:48:10 +00:00
@Override
public void onRenderPass(RenderEvent event) {
// System.out.println("Render passing");
// System.out.println(event.getPartialTicks());
2018-08-05 22:53:11 +00:00
float partialTicks = event.getPartialTicks();
2018-08-16 22:34:23 +00:00
if (goal != null && Baritone.settings().renderGoal.value) {
PathRenderer.drawLitDankGoalBox(player(), goal, partialTicks, Color.GREEN);
2018-08-16 00:53:42 +00:00
}
if (!Baritone.settings().renderPath.get()) {
return;
}
2018-09-17 00:15:33 +00:00
//long start = System.nanoTime();
2018-08-16 00:53:42 +00:00
2018-08-13 20:49:05 +00:00
PathExecutor current = this.current; // this should prevent most race conditions?
PathExecutor next = this.next; // like, now it's not possible for current!=null to be true, then suddenly false because of another thread
// TODO is this enough, or do we need to acquire a lock here?
2018-08-16 22:10:15 +00:00
// TODO benchmark synchronized in render loop
2018-08-13 20:49:05 +00:00
// Render the current path, if there is one
if (current != null && current.getPath() != null) {
int renderBegin = Math.max(current.getPosition() - 3, 0);
2018-08-14 22:04:41 +00:00
PathRenderer.drawPath(current.getPath(), renderBegin, player(), partialTicks, Color.RED, Baritone.settings().fadePath.get(), 10, 20);
}
2018-08-13 19:57:21 +00:00
if (next != null && next.getPath() != null) {
2018-08-18 02:19:25 +00:00
PathRenderer.drawPath(next.getPath(), 0, player(), partialTicks, Color.MAGENTA, Baritone.settings().fadePath.get(), 10, 20);
2018-08-13 19:57:21 +00:00
}
2018-08-09 23:44:41 +00:00
2018-09-17 00:15:33 +00:00
//long split = System.nanoTime();
2018-08-09 23:37:08 +00:00
if (current != null) {
2018-08-09 23:44:41 +00:00
PathRenderer.drawManySelectionBoxes(player(), current.toBreak(), partialTicks, Color.RED);
PathRenderer.drawManySelectionBoxes(player(), current.toPlace(), partialTicks, Color.GREEN);
2018-08-12 15:40:44 +00:00
PathRenderer.drawManySelectionBoxes(player(), current.toWalkInto(), partialTicks, Color.MAGENTA);
2018-08-09 23:37:08 +00:00
}
// If there is a path calculation currently running, render the path calculation process
AbstractNodeCostSearch.getCurrentlyRunning().ifPresent(currentlyRunning -> {
currentlyRunning.bestPathSoFar().ifPresent(p -> {
2018-08-14 22:04:41 +00:00
PathRenderer.drawPath(p, 0, player(), partialTicks, Color.BLUE, Baritone.settings().fadePath.get(), 10, 20);
currentlyRunning.pathToMostRecentNodeConsidered().ifPresent(mr -> {
2018-08-09 23:44:41 +00:00
2018-08-14 22:04:41 +00:00
PathRenderer.drawPath(mr, 0, player(), partialTicks, Color.CYAN, Baritone.settings().fadePath.get(), 10, 20);
PathRenderer.drawManySelectionBoxes(player(), Collections.singletonList(mr.getDest()), partialTicks, Color.CYAN);
});
});
});
2018-09-17 00:15:33 +00:00
//long end = System.nanoTime();
2018-08-09 23:37:08 +00:00
//System.out.println((end - split) + " " + (split - start));
2018-09-08 04:32:25 +00:00
// if (end - start > 0) {
2018-08-07 02:48:09 +00:00
// System.out.println("Frame took " + (split - start) + " " + (end - split));
2018-09-08 04:32:25 +00:00
//}
2018-08-05 22:53:11 +00:00
}
2018-08-05 03:19:32 +00:00
}