From d1a6de06e20ec677d902fd4234dfa7eb9422792d Mon Sep 17 00:00:00 2001 From: Brady Date: Sat, 17 Jun 2023 14:15:57 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=98=BC=20`PathManager`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baritone/behavior/ElytraBehavior.java | 366 +++++++++++------- .../behavior/elytra/UnpackedSegment.java | 65 ++++ 2 files changed, 299 insertions(+), 132 deletions(-) create mode 100644 src/main/java/baritone/behavior/elytra/UnpackedSegment.java diff --git a/src/main/java/baritone/behavior/ElytraBehavior.java b/src/main/java/baritone/behavior/ElytraBehavior.java index bd430b9e5..2226f5b1b 100644 --- a/src/main/java/baritone/behavior/ElytraBehavior.java +++ b/src/main/java/baritone/behavior/ElytraBehavior.java @@ -26,6 +26,7 @@ import baritone.api.utils.Helper; import baritone.api.utils.Rotation; import baritone.api.utils.RotationUtils; import baritone.behavior.elytra.NetherPathfinderContext; +import baritone.behavior.elytra.UnpackedSegment; import baritone.utils.BlockStateInterface; import com.mojang.realmsclient.util.Pair; import net.minecraft.block.material.Material; @@ -41,8 +42,8 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.world.chunk.Chunk; import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.concurrent.CompletableFuture; +import java.util.function.UnaryOperator; public final class ElytraBehavior extends Behavior implements Helper { @@ -58,9 +59,7 @@ public final class ElytraBehavior extends Behavior implements Helper { // :sunglasses: private final NetherPathfinderContext context; - private List path; - public int playerNear; - private int goingTo; + private final PathManager pathManager; private int sinceFirework; public ElytraBehavior(Baritone baritone) { @@ -68,7 +67,217 @@ public final class ElytraBehavior extends Behavior implements Helper { this.context = new NetherPathfinderContext(NETHER_SEED); this.lines = new ArrayList<>(); this.visiblePath = Collections.emptyList(); - this.path = new ArrayList<>(); + this.pathManager = new PathManager(); + } + + private final class PathManager { + + private BlockPos destination; + private List path; + private boolean completePath; + + private int playerNear; + private int goingTo; + + private boolean recalculating; + + public PathManager() { + // lol imagine initializing fields normally + this.clear(); + } + + public void tick() { + // Recalculate closest path node + this.playerNear = this.calculateNear(this.playerNear); + + // Obstacles are more important than an incomplete path, handle those first. + this.pathfindAroundObstacles(); + this.attemptNextSegment(); + } + + public void pathToDestination(BlockPos destination) { + this.destination = destination; + this.path0(ctx.playerFeet(), destination, UnaryOperator.identity()) + .thenRun(() -> { + final int distance = (int) this.pathAt(0).distanceTo(this.pathAt(this.path.size() - 1)); + if (this.completePath) { + logDirect(String.format("Computed path (%d blocks)", distance)); + } else { + logDirect(String.format("Computed segment (Next %d blocks)", distance)); + } + }); + } + + public void recalcSegment(final int upToIncl) { + if (this.recalculating) { + return; + } + + this.recalculating = true; + final List after = this.path.subList(upToIncl, this.path.size()); + final boolean complete = this.completePath; + + this.path0(ctx.playerFeet(), this.path.get(upToIncl), segment -> segment.append(after.stream(), complete)) + .thenRun(() -> { + this.recalculating = false; + final int recompute = this.path.size() - after.size() - 1; + final int distance = (int) this.pathAt(0).distanceTo(this.pathAt(recompute)); + logDirect(String.format("Recomputed segment (Next %d blocks)", distance)); + }); + } + + public void nextSegment(final int afterIncl) { + if (this.recalculating) { + return; + } + + this.recalculating = true; + final List before = this.path.subList(0, afterIncl + 1); + + this.path0(this.path.get(afterIncl), this.destination, segment -> segment.prepend(before.stream())) + .thenRun(() -> { + this.recalculating = false; + final int recompute = this.path.size() - before.size() - 1; + final int distance = (int) this.pathAt(0).distanceTo(this.pathAt(recompute)); + + if (this.completePath) { + logDirect(String.format("Computed path (%d blocks)", distance)); + } else { + logDirect(String.format("Computed next segment (Next %d blocks)", distance)); + } + }); + } + + private Vec3d pathAt(int i) { + return new Vec3d( + this.path.get(i).x + 0.5, + this.path.get(i).y + 0.5, + this.path.get(i).z + 0.5 + ); + } + + public void clear() { + this.path = Collections.emptyList(); + this.playerNear = 0; + this.goingTo = 0; + this.completePath = true; + } + + private void setPath(final UnpackedSegment segment) { + this.path = segment.collect(); + this.removeBacktracks(); + this.playerNear = 0; + this.goingTo = 0; + this.completePath = segment.isFinished(); + } + + public List getPath() { + return this.path; + } + + public int getNear() { + return this.playerNear; + } + + public void setGoingTo(int index) { + this.goingTo = index; + } + + public BetterBlockPos goingTo() { + return this.path.get(this.goingTo); + } + + // mickey resigned + private CompletableFuture path0(BlockPos src, BlockPos dst, UnaryOperator operator) { + return ElytraBehavior.this.context.pathFindAsync(src, dst) + .thenApply(UnpackedSegment::from) + .thenApply(operator) + .thenAcceptAsync(this::setPath, ctx.minecraft()::addScheduledTask); + } + + private void pathfindAroundObstacles() { + if (this.recalculating) { + return; + } + + outer: + while (true) { + int rangeStartIncl = playerNear; + int rangeEndExcl = playerNear; + while (rangeEndExcl < path.size() && ctx.world().isBlockLoaded(path.get(rangeEndExcl), false)) { + rangeEndExcl++; + } + if (rangeStartIncl >= rangeEndExcl) { + // not loaded yet? + return; + } + if (!passable(ctx.world().getBlockState(path.get(rangeStartIncl)))) { + // we're in a wall + return; // previous iterations of this function SHOULD have fixed this by now :rage_cat: + } + for (int i = rangeStartIncl; i < rangeEndExcl - 1; i++) { + if (!clearView(pathAt(i), pathAt(i + 1))) { + // obstacle. where do we return to pathing? + // find the next valid segment + this.recalcSegment(rangeEndExcl - 1); + break outer; + } + } + break; + } + } + + private void attemptNextSegment() { + if (this.recalculating) { + return; + } + + final int last = this.path.size() - 1; + if (!this.completePath && ctx.world().isBlockLoaded(this.path.get(last), false)) { + this.nextSegment(last); + } + } + + private int calculateNear(int index) { + final BetterBlockPos pos = ctx.playerFeet(); + for (int i = index; i >= Math.max(index - 1000, 0); i -= 10) { + if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) { + index = i; // intentional: this changes the bound of the loop + } + } + for (int i = index; i < Math.min(index + 1000, path.size()); i += 10) { + if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) { + index = i; // intentional: this changes the bound of the loop + } + } + for (int i = index; i >= Math.max(index - 50, 0); i--) { + if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) { + index = i; // intentional: this changes the bound of the loop + } + } + for (int i = index; i < Math.min(index + 50, path.size()); i++) { + if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) { + index = i; // intentional: this changes the bound of the loop + } + } + return index; + } + + private void removeBacktracks() { + Map positionFirstSeen = new HashMap<>(); + for (int i = 0; i < this.path.size(); i++) { + BetterBlockPos pos = this.path.get(i); + if (positionFirstSeen.containsKey(pos)) { + int j = positionFirstSeen.get(pos); + while (i > j) { + this.path.remove(i); + i--; + } + } else { + positionFirstSeen.put(pos, i); + } + } + } } @Override @@ -79,106 +288,38 @@ public final class ElytraBehavior extends Behavior implements Helper { } } - boolean recalculating; - - private void pathfindAroundObstacles() { - if (this.recalculating) { - return; - } - outer: - while (true) { - int rangeStartIncl = playerNear; - int rangeEndExcl = playerNear; - while (rangeEndExcl < path.size() && ctx.world().isBlockLoaded(path.get(rangeEndExcl), false)) { - rangeEndExcl++; - } - if (rangeStartIncl >= rangeEndExcl) { - // not loaded yet? - return; - } - if (!passable(ctx.world().getBlockState(path.get(rangeStartIncl)))) { - // we're in a wall - return; // previous iterations of this function SHOULD have fixed this by now :rage_cat: - } - for (int i = rangeStartIncl; i < rangeEndExcl - 1; i++) { - if (!clearView(pathAt(i), pathAt(i + 1))) { - // obstacle. where do we return to pathing? - // find the next valid segment - List afterSplice = path.subList(rangeEndExcl - 1, path.size()); - path(path.get(rangeEndExcl - 1), afterSplice); - break outer; - } - } - break; - } - } - public void path(BlockPos destination) { - path(destination, Collections.emptyList()); - } - - public void path(BlockPos destination, List andThen) { - final boolean isRecalc = !andThen.isEmpty(); - this.recalculating = isRecalc; - - this.context.pathFindAsync(ctx.playerFeet(), destination).thenAccept(segment -> { - final List joined = Stream.concat( - Arrays.stream(segment.packed) - .mapToObj(BlockPos::fromLong) - .map(BetterBlockPos::new), - andThen.stream() - ).collect(Collectors.toList()); - - ctx.minecraft().addScheduledTask(() -> { - this.path = joined; - this.playerNear = 0; - this.goingTo = 0; - - final int distance = (int) Math.sqrt(this.path.get(0).distanceSq(this.path.get(segment.packed.length - 1))); - - if (isRecalc) { - this.recalculating = false; - logDirect(String.format("Recalculated segment (%d blocks)", distance)); - } else { - logDirect(String.format("Computed path (%d blocks)", distance)); - } - removeBacktracks(); - }); - }); + this.pathManager.pathToDestination(destination); } public void cancel() { this.visiblePath = Collections.emptyList(); - this.path.clear(); + this.pathManager.clear(); this.aimPos = null; - this.playerNear = 0; - this.goingTo = 0; this.sinceFirework = 0; } - private Vec3d pathAt(int i) { - return new Vec3d(path.get(i).x + 0.5, path.get(i).y + 0.5, path.get(i).z + 0.5); - } - @Override public void onTick(TickEvent event) { if (event.getType() == TickEvent.Type.OUT) { return; } + this.lines.clear(); + + final List path = this.pathManager.getPath(); if (path.isEmpty()) { return; } - playerNear = calculateNear(playerNear); - visiblePath = path.subList( + this.pathManager.tick(); + + final int playerNear = this.pathManager.getNear(); + this.visiblePath = path.subList( Math.max(playerNear - 30, 0), Math.min(playerNear + 30, path.size()) ); baritone.getInputOverrideHandler().clearAllKeys(); // FIXME: This breaks the regular path-finder - lines.clear(); - - pathfindAroundObstacles(); if (!ctx.player().isElytraFlying()) { return; @@ -204,19 +345,19 @@ public final class ElytraBehavior extends Behavior implements Helper { int minStep = playerNear; for (int i = Math.min(playerNear + 20, path.size() - 1); i >= minStep; i--) { for (int dy : heights) { - Vec3d dest = pathAt(i).add(0, dy, 0); + Vec3d dest = this.pathManager.pathAt(i).add(0, dy, 0); if (dy != 0) { if (i + lookahead >= path.size()) { continue; } if (start.distanceTo(dest) < 40) { - if (!clearView(dest, pathAt(i + lookahead).add(0, dy, 0)) || !clearView(dest, pathAt(i + lookahead))) { + if (!clearView(dest, this.pathManager.pathAt(i + lookahead).add(0, dy, 0)) || !clearView(dest, this.pathManager.pathAt(i + lookahead))) { // aka: don't go upwards if doing so would prevent us from being able to see the next position **OR** the modified next position continue; } } else { // but if it's far away, allow gaining altitude if we could lose it again by the time we get there - if (!clearView(dest, pathAt(i))) { + if (!clearView(dest, this.pathManager.pathAt(i))) { continue; } } @@ -231,8 +372,8 @@ public final class ElytraBehavior extends Behavior implements Helper { } long b = System.currentTimeMillis(); System.out.println("Solved pitch in " + (b - a) + " total time " + (b - t)); - goingTo = i; - aimPos = path.get(i).add(0, dy, 0); + this.pathManager.setGoingTo(i); + this.aimPos = path.get(i).add(0, dy, 0); baritone.getLookBehavior().updateTarget(new Rotation(rot.getYaw(), pitch), false); break outermost; } @@ -244,11 +385,13 @@ public final class ElytraBehavior extends Behavior implements Helper { } } + final BetterBlockPos goingTo = this.pathManager.goingTo(); + if (!firework && sinceFirework > 10 - && (Baritone.settings().wasteFireworks.value || ctx.player().posY < path.get(goingTo).y + 5) // don't firework if trying to descend - && (ctx.player().posY < path.get(goingTo).y - 5 || start.distanceTo(new Vec3d(path.get(goingTo).x + 0.5, ctx.player().posY, path.get(goingTo).z + 0.5)) > 5) // UGH!!!!!!! - && new Vec3d(ctx.player().motionX, ctx.player().posY < path.get(goingTo).y ? Math.max(0, ctx.player().motionY) : ctx.player().motionY, ctx.player().motionZ).length() < Baritone.settings().elytraFireworkSpeed.value // ignore y component if we are BOTH below where we want to be AND descending + && (Baritone.settings().wasteFireworks.value || ctx.player().posY < goingTo.y + 5) // don't firework if trying to descend + && (ctx.player().posY < goingTo.y - 5 || start.distanceTo(new Vec3d(goingTo.x + 0.5, ctx.player().posY, goingTo.z + 0.5)) > 5) // UGH!!!!!!! + && new Vec3d(ctx.player().motionX, ctx.player().posY < goingTo.y ? Math.max(0, ctx.player().motionY) : ctx.player().motionY, ctx.player().motionZ).length() < Baritone.settings().elytraFireworkSpeed.value // ignore y component if we are BOTH below where we want to be AND descending ) { logDirect("firework"); ctx.playerController().processRightClick(ctx.player(), ctx.world(), EnumHand.MAIN_HAND); @@ -377,47 +520,6 @@ public final class ElytraBehavior extends Behavior implements Helper { return new Vec3d(motionX, motionY, motionZ); } - private int calculateNear(int index) { - final BetterBlockPos pos = ctx.playerFeet(); - for (int i = index; i >= Math.max(index - 1000, 0); i -= 10) { - if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) { - index = i; // intentional: this changes the bound of the loop - } - } - for (int i = index; i < Math.min(index + 1000, path.size()); i += 10) { - if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) { - index = i; // intentional: this changes the bound of the loop - } - } - for (int i = index; i >= Math.max(index - 50, 0); i--) { - if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) { - index = i; // intentional: this changes the bound of the loop - } - } - for (int i = index; i < Math.min(index + 50, path.size()); i++) { - if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) { - index = i; // intentional: this changes the bound of the loop - } - } - return index; - } - - private void removeBacktracks() { - Map positionFirstSeen = new HashMap<>(); - for (int i = 0; i < path.size(); i++) { - BetterBlockPos pos = path.get(i); - if (positionFirstSeen.containsKey(pos)) { - int j = positionFirstSeen.get(pos); - while (i > j) { - path.remove(i); - i--; - } - } else { - positionFirstSeen.put(pos, i); - } - } - } - // TODO: Use the optimized version from builder-2 private RayTraceResult rayTraceBlocks(Vec3d start, Vec3d end) { int x1 = MathHelper.floor(end.x); diff --git a/src/main/java/baritone/behavior/elytra/UnpackedSegment.java b/src/main/java/baritone/behavior/elytra/UnpackedSegment.java new file mode 100644 index 000000000..7798113da --- /dev/null +++ b/src/main/java/baritone/behavior/elytra/UnpackedSegment.java @@ -0,0 +1,65 @@ +/* + * 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 . + */ + +package baritone.behavior.elytra; + +import baritone.api.utils.BetterBlockPos; +import dev.babbaj.pathfinder.PathSegment; +import net.minecraft.util.math.BlockPos; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Brady + */ +public final class UnpackedSegment { + + private final Stream path; + private final boolean finished; + + public UnpackedSegment(Stream path, boolean finished) { + this.path = path; + this.finished = finished; + } + + public UnpackedSegment append(Stream other, boolean otherFinished) { + // The new segment is only finished if the one getting added on is + return new UnpackedSegment(Stream.concat(this.path, other), otherFinished); + } + + public UnpackedSegment prepend(Stream other) { + return new UnpackedSegment(Stream.concat(other, this.path), this.finished); + } + + public List collect() { + return this.path.collect(Collectors.toList()); + } + + public boolean isFinished() { + return this.finished; + } + + public static UnpackedSegment from(final PathSegment segment) { + return new UnpackedSegment( + Arrays.stream(segment.packed).mapToObj(BlockPos::fromLong).map(BetterBlockPos::new), + segment.finished + ); + } +}