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 General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
2018-08-08 04:15:22 +00:00
|
|
|
* 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 General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2018-08-05 05:15:18 +00:00
|
|
|
package baritone.bot.behavior.impl;
|
2018-08-05 03:19:32 +00:00
|
|
|
|
2018-08-07 04:07:15 +00:00
|
|
|
import baritone.bot.Baritone;
|
2018-08-05 05:15:18 +00:00
|
|
|
import baritone.bot.behavior.Behavior;
|
2018-08-05 21:48:10 +00:00
|
|
|
import baritone.bot.event.events.RenderEvent;
|
2018-08-05 23:57:50 +00:00
|
|
|
import baritone.bot.event.events.TickEvent;
|
2018-08-05 22:38:11 +00:00
|
|
|
import baritone.bot.pathing.calc.AStarPathFinder;
|
2018-08-05 22:53:11 +00:00
|
|
|
import baritone.bot.pathing.calc.AbstractNodeCostSearch;
|
2018-08-05 22:38:11 +00:00
|
|
|
import baritone.bot.pathing.calc.IPathFinder;
|
|
|
|
import baritone.bot.pathing.goals.Goal;
|
2018-08-05 03:28:32 +00:00
|
|
|
import baritone.bot.pathing.path.IPath;
|
2018-08-05 03:25:05 +00:00
|
|
|
import baritone.bot.pathing.path.PathExecutor;
|
2018-08-09 21:48:10 +00:00
|
|
|
import baritone.bot.utils.PathRenderer;
|
2018-08-05 22:38:11 +00:00
|
|
|
import net.minecraft.util.math.BlockPos;
|
|
|
|
|
|
|
|
import java.awt.*;
|
2018-08-14 20:23:57 +00:00
|
|
|
import java.util.Collections;
|
2018-08-06 07:21:47 +00:00
|
|
|
import java.util.Optional;
|
2018-08-05 03:25:05 +00:00
|
|
|
|
2018-08-05 03:28:32 +00:00
|
|
|
public class PathingBehavior extends Behavior {
|
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-14 03:17:16 +00:00
|
|
|
private 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();
|
|
|
|
|
2018-08-05 03:28:32 +00:00
|
|
|
@Override
|
2018-08-05 23:57:50 +00:00
|
|
|
public void onTick(TickEvent event) {
|
2018-08-13 20:13:42 +00:00
|
|
|
if (event.getType() == TickEvent.Type.OUT) {
|
|
|
|
current = null;
|
|
|
|
next = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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-13 19:56:08 +00:00
|
|
|
if (goal.isInGoal(playerFeet())) {
|
|
|
|
displayChatMessageRaw("All done. At " + goal);
|
|
|
|
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
|
|
|
|
displayChatMessageRaw("Discarding next path as it does not contain current position");
|
|
|
|
// 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.
|
|
|
|
next = null;
|
|
|
|
}
|
|
|
|
if (next != null) {
|
2018-08-13 20:00:57 +00:00
|
|
|
displayChatMessageRaw("Continuing on to planned next path");
|
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) {
|
|
|
|
// if we aren't calculating right now
|
|
|
|
return;
|
|
|
|
}
|
2018-08-16 22:10:15 +00:00
|
|
|
findPathInNewThread(playerFeet(), 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-08-13 20:00:57 +00:00
|
|
|
displayChatMessageRaw("Splicing into planned next path 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;
|
|
|
|
}
|
|
|
|
if (goal.isInGoal(current.getPath().getDest())) {
|
|
|
|
// 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-08-13 19:56:08 +00:00
|
|
|
displayChatMessageRaw("Path almost over. Planning ahead...");
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-14 22:32:16 +00:00
|
|
|
public PathExecutor getCurrent() {
|
2018-08-09 21:48:10 +00:00
|
|
|
return current;
|
|
|
|
}
|
|
|
|
|
2018-08-14 22:32:16 +00:00
|
|
|
public PathExecutor getNext() {return next;}
|
|
|
|
|
2018-08-09 21:48:10 +00:00
|
|
|
public Optional<IPath> getPath() {
|
|
|
|
return Optional.ofNullable(current).map(PathExecutor::getPath);
|
2018-08-05 22:38:11 +00:00
|
|
|
}
|
|
|
|
|
2018-08-14 03:57:29 +00:00
|
|
|
public void cancel() {
|
|
|
|
current = null;
|
|
|
|
next = null;
|
|
|
|
Baritone.INSTANCE.getInputOverrideHandler().clearAllKeys();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void path() {
|
2018-08-16 22:10:15 +00:00
|
|
|
synchronized (pathPlanLock) {
|
|
|
|
if (current != null) {
|
|
|
|
displayChatMessageRaw("Currently executing a path. Please cancel it first.");
|
2018-08-14 03:57:29 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-08-16 22:10:15 +00:00
|
|
|
synchronized (pathCalcLock) {
|
|
|
|
if (isPathCalcInProgress) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
findPathInNewThread(playerFeet(), true, Optional.empty());
|
|
|
|
}
|
2018-08-14 03:57:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-08-05 23:56:21 +00:00
|
|
|
new Thread(() -> {
|
|
|
|
if (talkAboutIt) {
|
|
|
|
displayChatMessageRaw("Starting to search for path from " + start + " to " + goal);
|
|
|
|
}
|
2018-08-05 22:38:11 +00:00
|
|
|
|
2018-08-16 22:10:15 +00:00
|
|
|
findPath(start, previous).map(IPath::cutoffAtLoadedChunks).map(PathExecutor::new).ifPresent(path -> {
|
2018-08-13 19:35:44 +00:00
|
|
|
synchronized (pathPlanLock) {
|
|
|
|
if (current == null) {
|
|
|
|
current = path;
|
|
|
|
} else {
|
|
|
|
if (next == null) {
|
|
|
|
next = path;
|
|
|
|
} else {
|
|
|
|
throw new IllegalStateException("I have no idea what to do with this path");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2018-08-06 14:14:20 +00:00
|
|
|
if (talkAboutIt && current != null && current.getPath() != null) {
|
2018-08-16 00:53:42 +00:00
|
|
|
if (goal.isInGoal(current.getPath().getDest())) {
|
|
|
|
displayChatMessageRaw("Finished finding a path from " + start + " to " + goal + ". " + current.getPath().getNumNodesConsidered() + " nodes considered");
|
|
|
|
} else {
|
|
|
|
displayChatMessageRaw("Found path segment from " + start + " towards " + goal + ". " + current.getPath().getNumNodesConsidered() + " nodes considered");
|
|
|
|
|
|
|
|
}
|
2018-08-06 14:14:20 +00:00
|
|
|
}
|
2018-08-13 19:35:44 +00:00
|
|
|
synchronized (pathCalcLock) {
|
|
|
|
isPathCalcInProgress = false;
|
|
|
|
}
|
2018-08-05 23:56:21 +00:00
|
|
|
}).start();
|
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) {
|
2018-08-05 22:38:11 +00:00
|
|
|
if (goal == null) {
|
2018-08-05 23:56:21 +00:00
|
|
|
displayChatMessageRaw("no goal");
|
2018-08-06 07:21:47 +00:00
|
|
|
return Optional.empty();
|
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));
|
2018-08-06 07:21:47 +00:00
|
|
|
return pf.calculate();
|
2018-08-05 22:38:11 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
2018-08-06 07:21:47 +00:00
|
|
|
return Optional.empty();
|
2018-08-05 22:38:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-05 21:48:10 +00:00
|
|
|
@Override
|
|
|
|
public void onRenderPass(RenderEvent event) {
|
2018-08-09 21:48:10 +00:00
|
|
|
// 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-08-09 23:37:08 +00:00
|
|
|
long start = System.nanoTime();
|
2018-08-06 07:21:47 +00:00
|
|
|
|
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
|
|
|
|
2018-08-06 07:21:47 +00:00
|
|
|
// Render the current path, if there is one
|
2018-08-09 20:08:56 +00:00
|
|
|
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-09 20:08:56 +00:00
|
|
|
}
|
2018-08-13 19:57:21 +00:00
|
|
|
if (next != null && next.getPath() != null) {
|
2018-08-14 22:04:41 +00:00
|
|
|
PathRenderer.drawPath(next.getPath(), 0, player(), partialTicks, Color.GREEN, Baritone.settings().fadePath.get(), 10, 20);
|
2018-08-13 19:57:21 +00:00
|
|
|
}
|
2018-08-09 23:44:41 +00:00
|
|
|
|
2018-08-09 23:37:08 +00:00
|
|
|
long split = System.nanoTime();
|
|
|
|
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
|
|
|
}
|
2018-08-06 07:21:47 +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);
|
2018-08-06 07:21:47 +00:00
|
|
|
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);
|
2018-08-14 20:23:57 +00:00
|
|
|
PathRenderer.drawManySelectionBoxes(player(), Collections.singletonList(mr.getDest()), partialTicks, Color.CYAN);
|
2018-08-06 07:21:47 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2018-08-09 23:37:08 +00:00
|
|
|
long end = System.nanoTime();
|
|
|
|
//System.out.println((end - split) + " " + (split - start));
|
2018-08-07 02:48:09 +00:00
|
|
|
// if (end - start > 0)
|
|
|
|
// System.out.println("Frame took " + (split - start) + " " + (end - split));
|
2018-08-09 23:37:08 +00:00
|
|
|
|
2018-08-05 22:53:11 +00:00
|
|
|
}
|
2018-08-05 03:19:32 +00:00
|
|
|
}
|