From 75a3acd840b600b45e73d5a6b1bc4c67e1d796f4 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sat, 15 Mar 2025 03:32:51 -0600 Subject: [PATCH 01/48] Add initial support for overworld/end flight --- src/api/java/baritone/api/Settings.java | 11 +++++ .../command/defaults/ElytraCommand.java | 3 -- .../java/baritone/process/ElytraProcess.java | 33 ++++++++++---- .../elytra/BlockStateOctreeInterface.java | 4 +- .../process/elytra/ElytraBehavior.java | 34 ++++++++++++--- .../elytra/NetherPathfinderContext.java | 43 +++++++++++++------ 6 files changed, 96 insertions(+), 32 deletions(-) diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index 44dabae2a..70c2e235f 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -1543,6 +1543,17 @@ public final class Settings { */ public final Setting elytraChatSpam = new Setting<>(false); + /** + * May reduce memory usage by using a custom allocator for pathfinding + */ + public final Setting elytraCustomAllocator = new Setting<>(false); + + /** + * Allow the pathfinder to attempt flight in tighter spaces, useful in caves but can be dangerous. + */ + public final Setting elytraAllowTightSpaces = new Setting<>(false); + + /** * A map of lowercase setting field names to their respective setting */ diff --git a/src/main/java/baritone/command/defaults/ElytraCommand.java b/src/main/java/baritone/command/defaults/ElytraCommand.java index 2f5eff352..a5453ff63 100644 --- a/src/main/java/baritone/command/defaults/ElytraCommand.java +++ b/src/main/java/baritone/command/defaults/ElytraCommand.java @@ -71,9 +71,6 @@ public void execute(String label, IArgConsumer args) throws CommandException { if (iGoal == null) { throw new CommandInvalidStateException("No goal has been set"); } - if (ctx.world().dimension() != Level.NETHER) { - throw new CommandInvalidStateException("Only works in the nether"); - } try { elytra.pathTo(iGoal); } catch (IllegalArgumentException ex) { diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index e6d7ee34a..be3d5404b 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -54,6 +54,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.phys.Vec3; import java.util.*; @@ -189,7 +190,9 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { behavior.landingMode = this.state == State.LANDING; this.goal = null; baritone.getInputOverrideHandler().clearAllKeys(); - behavior.tick(); + synchronized (behavior.context.cullingLock) { + behavior.tick(); + } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } else if (this.state == State.LANDING) { if (ctx.playerMotion().multiply(1, 0, 1).length() > 0.001) { @@ -322,11 +325,11 @@ public void pathTo(BlockPos destination) { } private void pathTo0(BlockPos destination, boolean appendDestination) { - if (ctx.player() == null || ctx.player().level.dimension() != Level.NETHER) { + if (ctx.player() == null) { return; } this.onLostControl(); - this.predictingTerrain = Baritone.settings().elytraPredictTerrain.value; + this.predictingTerrain = ctx.player().level.dimension() == Level.NETHER && Baritone.settings().elytraPredictTerrain.value; this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination); if (ctx.world() != null) { this.behavior.repackChunks(); @@ -352,8 +355,12 @@ public void pathTo(Goal iGoal) { } else { throw new IllegalArgumentException("The goal must be a GoalXZ or GoalBlock"); } - if (y <= 0 || y >= 128) { - throw new IllegalArgumentException("The y of the goal is not between 0 and 128"); + + // TODO: Optional limit on nether to y=127 + int minY = ctx.world().dimensionType().minY(); + int maxY = Math.min(minY + 384, ctx.world().dimensionType().height() + minY); + if (y < minY || y >= maxY) { + throw new IllegalArgumentException("The goal must have a y value between " + minY + " and " + maxY); } this.pathTo(new BlockPos(x, y, z)); } @@ -465,12 +472,20 @@ public double placeBucketCost() { } } - private static boolean isInBounds(BlockPos pos) { - return pos.getY() >= 0 && pos.getY() < 128; + private static boolean isInBounds(Level dim, BlockPos pos) { + // TODO: Optionally limit nether to just y=127 + DimensionType dimType = dim.dimensionType(); + int minY = dimType.minY(); + int maxY = Math.min(minY + 384, dimType.height() + minY); + return pos.getY() >= minY && pos.getY() < maxY; } private boolean isSafeBlock(Block block) { - return block == Blocks.NETHERRACK || block == Blocks.GRAVEL || (block == Blocks.NETHER_BRICKS && Baritone.settings().elytraAllowLandOnNetherFortress.value); + return block == Blocks.NETHERRACK || block == Blocks.GRAVEL || block == Blocks.SOUL_SAND || block == Blocks.SOUL_SOIL || (block == Blocks.NETHER_BRICKS && Baritone.settings().elytraAllowLandOnNetherFortress.value) + || block == Blocks.STONE || block == Blocks.DEEPSLATE || block == Blocks.GRASS_BLOCK || block == Blocks.SAND || block == Blocks.RED_SAND || block == Blocks.TERRACOTTA + || block == Blocks.SNOW || block == Blocks.ICE || block == Blocks.MYCELIUM || block == Blocks.PODZOL + || block == Blocks.DARK_OAK_LEAVES || block == Blocks.JUNGLE_LEAVES + || block == Blocks.END_STONE; } private boolean isSafeBlock(BlockPos pos) { @@ -551,7 +566,7 @@ private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) { while (!queue.isEmpty()) { BetterBlockPos pos = queue.poll(); - if (ctx.world().isLoaded(pos) && isInBounds(pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { + if (ctx.world().isLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions); if (actualLandingSpot != null && isColumnAir(actualLandingSpot, LANDING_COLUMN_HEIGHT) && hasAirBubble(actualLandingSpot.above(LANDING_COLUMN_HEIGHT)) && !badLandingSpots.contains(actualLandingSpot.above(LANDING_COLUMN_HEIGHT))) { return actualLandingSpot.above(LANDING_COLUMN_HEIGHT); diff --git a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java index 7db0e2d64..a88881fd2 100644 --- a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java +++ b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java @@ -39,7 +39,7 @@ public BlockStateOctreeInterface(final NetherPathfinderContext context) { } public boolean get0(final int x, final int y, final int z) { - if ((y | (127 - y)) < 0) { + if (y < 0 || y > 383) { return false; } final int chunkX = x >> 4; @@ -49,6 +49,6 @@ public boolean get0(final int x, final int y, final int z) { this.prevChunkZ = chunkZ; this.chunkPtr = NetherPathfinder.getOrCreateChunk(this.contextPtr, chunkX, chunkZ); } - return Octree.getBlock(this.chunkPtr, x & 0xF, y & 0x7F, z & 0xF); + return Octree.getBlock(this.chunkPtr, x & 0xF, y, z & 0xF); } } diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index d4913f466..6a03ccc0e 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -128,7 +128,11 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destina this.solverExecutor = Executors.newSingleThreadExecutor(); this.nextTickBoostCounter = new int[2]; - this.context = new NetherPathfinderContext(Baritone.settings().elytraNetherSeed.value); + this.context = new NetherPathfinderContext( + Baritone.settings().elytraNetherSeed.value, + baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache"), + ctx.world() + ); this.boi = new BlockStateOctreeInterface(context); } @@ -228,7 +232,7 @@ public void pathNextSegment(final int afterIncl) { this.path0(pathStart, ElytraBehavior.this.destination, segment -> segment.prepend(before.stream())) .thenRun(() -> { final int recompute = this.path.size() - before.size() - 1; - final double distance = this.path.get(0).distanceTo(this.path.get(recompute)); + final double distance = recompute > 0 ? this.path.get(0).distanceTo(this.path.get(recompute)) : 0; if (this.completePath) { logVerbose(String.format("Computed path (%.1f blocks in %.4f seconds)", distance, (System.nanoTime() - start) / 1e9d)); @@ -289,10 +293,18 @@ public int getNear() { return this.playerNear; } + private static UnpackedSegment adjustSegment(UnpackedSegment segment, int minY) { + return new UnpackedSegment(segment.collect().stream().map((pos)-> { + return pos.above(minY); + }), segment.isFinished()); + } + // mickey resigned private CompletableFuture path0(BlockPos src, BlockPos dst, UnaryOperator operator) { - return ElytraBehavior.this.context.pathFindAsync(src, dst) + final int minY = ctx.world().dimensionType().minY(); + return ElytraBehavior.this.context.pathFindAsync(src.below(minY), dst.below(minY)) .thenApply(UnpackedSegment::from) + .thenApply(segment -> adjustSegment(segment, minY)) .thenApply(operator) .thenAcceptAsync(this::setPath, ctx.minecraft()::execute); } @@ -453,7 +465,9 @@ public void onChunkEvent(ChunkEvent event) { } public void onBlockChange(BlockChangeEvent event) { - this.context.queueBlockUpdate(event); + final int minY = ctx.world().dimensionType().minY(); + var blocks = event.getBlocks().stream().map((update) -> new Pair<>(update.first().below(minY), update.second())).toList(); + this.context.queueBlockUpdate(new BlockChangeEvent(event.getChunkPos(), blocks)); } public void onReceivePacket(PacketEvent event) { @@ -999,12 +1013,20 @@ private boolean isHitboxClear(final SolverContext context, final Vec3 dest, fina return clear; } + final int minY = ctx.world().dimensionType().minY(); + for (int i = 1; i < src.length; i += 3) { + src[i] -= minY; + dst[i] -= minY; + } return this.context.raytrace(8, src, dst, NetherPathfinderContext.Visibility.ALL); } public boolean clearView(Vec3 start, Vec3 dest, boolean ignoreLava) { final boolean clear; if (!ignoreLava) { + start = start.subtract(0, ctx.world().dimensionType().minY(), 0); + dest = dest.subtract(0, ctx.world().dimensionType().minY(), 0); + // if start == dest then the cpp raytracer dies clear = start.equals(dest) || this.context.raytrace(start, dest); } else { @@ -1012,6 +1034,8 @@ public boolean clearView(Vec3 start, Vec3 dest, boolean ignoreLava) { } if (Baritone.settings().elytraRenderRaytraces.value) { + start = start.add(0, ctx.world().dimensionType().minY(), 0); + dest = dest.add(0, ctx.world().dimensionType().minY(), 0); (clear ? this.clearLines : this.blockedLines).add(new Pair<>(start, dest)); } return clear; @@ -1267,7 +1291,7 @@ private boolean passable(int x, int y, int z, boolean ignoreLava) { final Material mat = this.bsi.get0(x, y, z).getMaterial(); return mat == Material.AIR || mat == Material.LAVA; } else { - return !this.boi.get0(x, y, z); + return !this.boi.get0(x, y-ctx.world().dimensionType().minY(), z); } } diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index aa9f4965a..84293ff8e 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -24,8 +24,10 @@ import dev.babbaj.pathfinder.Octree; import dev.babbaj.pathfinder.PathSegment; import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; import net.minecraft.util.BitStorage; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; @@ -34,6 +36,7 @@ import net.minecraft.world.phys.Vec3; import java.lang.ref.SoftReference; +import java.nio.file.Path; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -53,9 +56,14 @@ public final class NetherPathfinderContext { final long context; private final long seed; private final ExecutorService executor; - - public NetherPathfinderContext(long seed) { - this.context = NetherPathfinder.newContext(seed); + private final ResourceKey dimension; + + public NetherPathfinderContext(long seed, Path cache, Level world) { + this.dimension = world.dimension(); + int dimension = (this.dimension == Level.NETHER) ? NetherPathfinder.DIMENSION_NETHER : (this.dimension == Level.OVERWORLD) ? NetherPathfinder.DIMENSION_OVERWORLD : NetherPathfinder.DIMENSION_END; + int height = Math.min(world.dimensionType().height(), 384); + // TODO: Support optional limit of Nether to y=127 + this.context = NetherPathfinder.newContext(seed, cache.toString(), dimension, height, Baritone.settings().elytraCustomAllocator.value); this.seed = seed; this.executor = Executors.newSingleThreadExecutor(); } @@ -81,7 +89,7 @@ public void queueForPacking(final LevelChunk chunkIn) { final LevelChunk chunk = ref.get(); if (chunk != null) { long ptr = NetherPathfinder.getOrCreateChunk(this.context, chunk.getPos().x, chunk.getPos().z); - writeChunkData(chunk, ptr); + writeChunkData(chunk, ptr, this.context); } }); } @@ -93,7 +101,7 @@ public void queueBlockUpdate(BlockChangeEvent event) { if (ptr == 0) return; // this shouldn't ever happen event.getBlocks().forEach(pair -> { BlockPos pos = pair.first(); - if (pos.getY() >= 128) return; + if(pos.getY() < 0 || pos.getY() >= 384) return; boolean isSolid = pair.second() != AIR_BLOCK_STATE; Octree.setBlock(ptr, pos.getX() & 15, pos.getY(), pos.getZ() & 15, isSolid); }); @@ -106,10 +114,12 @@ public CompletableFuture pathFindAsync(final BlockPos src, final Bl this.context, src.getX(), src.getY(), src.getZ(), dst.getX(), dst.getY(), dst.getZ(), - true, - false, - 10000, - !Baritone.settings().elytraPredictTerrain.value + !Baritone.settings().elytraAllowTightSpaces.value, // atleastX4 + false, // refine + 10000, // timeoutMs + !(Baritone.settings().elytraPredictTerrain.value && this.dimension == Level.NETHER), // useAirIfChunkNotLoaded + // TODO: Determine appropiate cost value + 8.0 // fakeChunkCost ); if (segment == null) { throw new PathCalculationException("Path calculation failed"); @@ -186,10 +196,11 @@ public long getSeed() { return this.seed; } - private static void writeChunkData(LevelChunk chunk, long ptr) { + private static void writeChunkData(LevelChunk chunk, long chunkPtr, long contextPtr) { try { LevelChunkSection[] chunkInternalStorageArray = chunk.getSections(); - for (int y0 = 0; y0 < 8; y0++) { + final int maxSections = Math.min(chunkInternalStorageArray.length, 24); // pathfinder support stops at 384/16 sections + for (int y0 = 0; y0 < maxSections; y0++) { final LevelChunkSection extendedblockstorage = chunkInternalStorageArray[y0]; if (extendedblockstorage == null) { continue; @@ -212,11 +223,17 @@ private static void writeChunkData(LevelChunk chunk, long ptr) { int x = (idx & 15); int y = yReal + (idx >> 8); int z = ((idx >> 4) & 15); - Octree.setBlock(ptr, x, y, z, value != airId); + + // Avoid unnecessary writes that may trigger a page allocation + // TODO: Make this predictor friendly + if(value != airId) { + Octree.setBlock(chunkPtr, x, y, z, true); + } } } } - Octree.setIsFromJava(ptr); + final ChunkPos pos = chunk.getPos(); + NetherPathfinder.setChunkState(contextPtr, pos.x, pos.z, true); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); From dd2240a39efd93a4616f2c276d7949a2aa1dc23c Mon Sep 17 00:00:00 2001 From: Babbaj Date: Sat, 15 Mar 2025 23:41:53 -0400 Subject: [PATCH 02/48] update to nether-pathfinder api/concurrency changes --- src/api/java/baritone/api/Settings.java | 4 + .../java/baritone/process/ElytraProcess.java | 7 +- .../elytra/BlockStateOctreeInterface.java | 2 +- .../process/elytra/ElytraBehavior.java | 5 +- .../elytra/NetherPathfinderContext.java | 134 ++++++++++++------ 5 files changed, 105 insertions(+), 47 deletions(-) diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index 70c2e235f..4917ba921 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -1553,6 +1553,10 @@ public final class Settings { */ public final Setting elytraAllowTightSpaces = new Setting<>(false); + /** + * Allow the pathfinder to fly above y 128 in the nether. + */ + public final Setting elytraAllowAboveRoof = new Setting<>(false); /** * A map of lowercase setting field names to their respective setting diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index be3d5404b..bc2896cf6 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -190,8 +190,11 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { behavior.landingMode = this.state == State.LANDING; this.goal = null; baritone.getInputOverrideHandler().clearAllKeys(); - synchronized (behavior.context.cullingLock) { + behavior.context.readLock.lock(); + try { behavior.tick(); + } finally { + behavior.context.readLock.unlock(); } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } else if (this.state == State.LANDING) { @@ -345,7 +348,7 @@ public void pathTo(Goal iGoal) { if (iGoal instanceof GoalXZ) { GoalXZ goal = (GoalXZ) iGoal; x = goal.getX(); - y = 64; + y = 64; // TODO: if we go above the roof this doesn't work z = goal.getZ(); } else if (iGoal instanceof GoalBlock) { GoalBlock goal = (GoalBlock) iGoal; diff --git a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java index a88881fd2..226788c7e 100644 --- a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java +++ b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java @@ -47,7 +47,7 @@ public boolean get0(final int x, final int y, final int z) { if (this.chunkPtr == 0 | ((chunkX ^ this.prevChunkX) | (chunkZ ^ this.prevChunkZ)) != 0) { this.prevChunkX = chunkX; this.prevChunkZ = chunkZ; - this.chunkPtr = NetherPathfinder.getOrCreateChunk(this.contextPtr, chunkX, chunkZ); + this.chunkPtr = NetherPathfinder.getChunkOrDefault(this.contextPtr, chunkX, chunkZ, true); } return Octree.getBlock(this.chunkPtr, x & 0xF, y, z & 0xF); } diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 6a03ccc0e..1a856f50f 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -522,8 +522,11 @@ public void repackChunks() { } public void onTick() { - synchronized (this.context.cullingLock) { + this.context.readLock.lock(); + try { this.onTick0(); + } finally { + this.context.readLock.unlock(); } final long now = System.currentTimeMillis(); if ((now - this.timeLastCacheCull) / 1000 > Baritone.settings().elytraTimeBetweenCacheCullSecs.value) { diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 84293ff8e..3a26acc87 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -41,6 +41,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @author Brady @@ -50,22 +52,29 @@ public final class NetherPathfinderContext { private static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState(); // This lock must be held while there are active pointers to chunks in java, // but we just hold it for the entire tick so we don't have to think much about it. - public final Object cullingLock = new Object(); + public final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + public final ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); + public final ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); // Visible for access in BlockStateOctreeInterface final long context; private final long seed; - private final ExecutorService executor; + // write locked operations + private final ExecutorService writeExecutor = Executors.newSingleThreadExecutor(); + // operations that don't make changes to the chunk cache. could use multiple threads but i'm not sure if it would cause problems. + private final ExecutorService readExecutor = Executors.newSingleThreadExecutor(); private final ResourceKey dimension; public NetherPathfinderContext(long seed, Path cache, Level world) { this.dimension = world.dimension(); - int dimension = (this.dimension == Level.NETHER) ? NetherPathfinder.DIMENSION_NETHER : (this.dimension == Level.OVERWORLD) ? NetherPathfinder.DIMENSION_OVERWORLD : NetherPathfinder.DIMENSION_END; + final int dim; + if (this.dimension == Level.NETHER) dim = NetherPathfinder.DIMENSION_NETHER; + else if (this.dimension == Level.END) dim = NetherPathfinder.DIMENSION_END; + else dim = NetherPathfinder.DIMENSION_END; int height = Math.min(world.dimensionType().height(), 384); - // TODO: Support optional limit of Nether to y=127 - this.context = NetherPathfinder.newContext(seed, cache.toString(), dimension, height, Baritone.settings().elytraCustomAllocator.value); + if (!Baritone.settings().elytraAllowAboveRoof.value && dim == NetherPathfinder.DIMENSION_NETHER) height = Math.min(height, 128); + this.context = NetherPathfinder.newContext(seed, cache.toString(), dim, height, Baritone.settings().elytraCustomAllocator.value); this.seed = seed; - this.executor = Executors.newSingleThreadExecutor(); } public boolean hasChunk(ChunkPos pos) { @@ -73,59 +82,81 @@ public boolean hasChunk(ChunkPos pos) { } public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks, BlockStateOctreeInterface boi) { - this.executor.execute(() -> { - synchronized (this.cullingLock) { + this.writeExecutor.execute(() -> { + writeLock.lock(); + try { boi.chunkPtr = 0L; NetherPathfinder.cullFarChunks(this.context, chunkX, chunkZ, maxDistanceBlocks); + } finally { + writeLock.unlock(); } }); } public void queueForPacking(final LevelChunk chunkIn) { final SoftReference ref = new SoftReference<>(chunkIn); - this.executor.execute(() -> { + this.writeExecutor.execute(() -> { // TODO: Prioritize packing recent chunks and/or ones that the path goes through, // and prune the oldest chunks per chunkPackerQueueMaxSize final LevelChunk chunk = ref.get(); if (chunk != null) { - long ptr = NetherPathfinder.getOrCreateChunk(this.context, chunk.getPos().x, chunk.getPos().z); - writeChunkData(chunk, ptr, this.context); + writeLock.lock(); + try { + long ptr = NetherPathfinder.allocateAndInsertChunk(this.context, chunk.getPos().x, chunk.getPos().z); + writeChunkData(chunk, ptr); + } finally { + writeLock.unlock(); + } } }); } public void queueBlockUpdate(BlockChangeEvent event) { - this.executor.execute(() -> { + this.readExecutor.execute(() -> { ChunkPos chunkPos = event.getChunkPos(); - long ptr = NetherPathfinder.getChunkPointer(this.context, chunkPos.x, chunkPos.z); - if (ptr == 0) return; // this shouldn't ever happen - event.getBlocks().forEach(pair -> { - BlockPos pos = pair.first(); - if(pos.getY() < 0 || pos.getY() >= 384) return; - boolean isSolid = pair.second() != AIR_BLOCK_STATE; - Octree.setBlock(ptr, pos.getX() & 15, pos.getY(), pos.getZ() & 15, isSolid); - }); + // not inserting or deleting from the cache hashmap so we should be fine not being exclusive + readLock.lock(); + try { + long ptr = NetherPathfinder.getChunk(this.context, chunkPos.x, chunkPos.z); + if (ptr == 0) return; // this shouldn't ever happen + event.getBlocks().forEach(pair -> { + BlockPos pos = pair.first(); + if (pos.getY() < 0 || pos.getY() >= 384) return; + boolean isSolid = pair.second() != AIR_BLOCK_STATE; + Octree.setBlock(ptr, pos.getX() & 15, pos.getY(), pos.getZ() & 15, isSolid); + }); + } finally { + readLock.unlock(); + } }); } public CompletableFuture pathFindAsync(final BlockPos src, final BlockPos dst) { + boolean generate = Baritone.settings().elytraPredictTerrain.value && this.dimension == Level.NETHER; + Lock l = generate ? writeLock : readLock; + ExecutorService exec = generate ? writeExecutor : readExecutor; return CompletableFuture.supplyAsync(() -> { - final PathSegment segment = NetherPathfinder.pathFind( - this.context, - src.getX(), src.getY(), src.getZ(), - dst.getX(), dst.getY(), dst.getZ(), - !Baritone.settings().elytraAllowTightSpaces.value, // atleastX4 - false, // refine - 10000, // timeoutMs - !(Baritone.settings().elytraPredictTerrain.value && this.dimension == Level.NETHER), // useAirIfChunkNotLoaded - // TODO: Determine appropiate cost value - 8.0 // fakeChunkCost - ); - if (segment == null) { - throw new PathCalculationException("Path calculation failed"); + l.lock(); + try { + final PathSegment segment = NetherPathfinder.pathFind( + this.context, + src.getX(), src.getY(), src.getZ(), + dst.getX(), dst.getY(), dst.getZ(), + !Baritone.settings().elytraAllowTightSpaces.value, // atleastX4 + false, // refine + 10000, // timeoutMs + !generate, // useAirIfChunkNotLoaded + // TODO: Determine appropiate cost value + 8.0 // fakeChunkCost + ); + if (segment == null) { + throw new PathCalculationException("Path calculation failed"); + } + return segment; + } finally { + l.unlock(); } - return segment; - }, this.executor); + }, exec); } /** @@ -181,10 +212,12 @@ public void cancel() { public void destroy() { this.cancel(); // Ignore anything that was queued up, just shutdown the executor - this.executor.shutdownNow(); + this.readExecutor.shutdownNow(); + this.writeExecutor.shutdownNow(); try { - while (!this.executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)) {} + while (!this.readExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)) {} + while (!this.writeExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)) {} } catch (InterruptedException e) { e.printStackTrace(); } @@ -196,17 +229,33 @@ public long getSeed() { return this.seed; } - private static void writeChunkData(LevelChunk chunk, long chunkPtr, long contextPtr) { + private static void writeChunkData(LevelChunk chunk, long chunkPtr) { try { LevelChunkSection[] chunkInternalStorageArray = chunk.getSections(); final int maxSections = Math.min(chunkInternalStorageArray.length, 24); // pathfinder support stops at 384/16 sections for (int y0 = 0; y0 < maxSections; y0++) { final LevelChunkSection extendedblockstorage = chunkInternalStorageArray[y0]; - if (extendedblockstorage == null) { + if (extendedblockstorage == null || extendedblockstorage.hasOnlyAir()) { continue; } final PalettedContainer bsc = extendedblockstorage.getStates(); - final int airId = ((IPalettedContainer) bsc).getPalette().idFor(AIR_BLOCK_STATE); + var palette = ((IPalettedContainer) bsc).getPalette(); + // Mushrooms spawn on the roof and writing them as solid will cause pages to be unnecessarily allocated. + // idFor can't be used because it may update the palette + int airId = -1; + int caveAirId = -1; + int redMushroomId = -1; + int brownMushroomId = -1; + for (int i = 0; i < palette.getSize(); i++) { + BlockState bs = palette.valueFor(i); + if (bs == Blocks.AIR.defaultBlockState()) airId = i; + else if (bs == Blocks.CAVE_AIR.defaultBlockState()) caveAirId = i; + else if (bs == Blocks.RED_MUSHROOM.defaultBlockState()) redMushroomId = i; + else if (bs == Blocks.BROWN_MUSHROOM.defaultBlockState()) brownMushroomId = i; + } + if (airId == -1 & caveAirId == -1) { + // TODO: memset + } // pasted from FasterWorldScanner final BitStorage array = ((IPalettedContainer) bsc).getStorage(); if (array == null) continue; @@ -226,14 +275,13 @@ private static void writeChunkData(LevelChunk chunk, long chunkPtr, long context // Avoid unnecessary writes that may trigger a page allocation // TODO: Make this predictor friendly - if(value != airId) { + // TODO: cave air? + if ((value != airId) & value != redMushroomId & value != brownMushroomId) { Octree.setBlock(chunkPtr, x, y, z, true); } } } } - final ChunkPos pos = chunk.getPos(); - NetherPathfinder.setChunkState(contextPtr, pos.x, pos.z, true); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); From 9d0a4cb5547a94707227881341ab8aa862f2efa2 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sun, 16 Mar 2025 01:08:57 -0600 Subject: [PATCH 03/48] Make usage of the Baritone cache optional for elytra flight --- src/api/java/baritone/api/Settings.java | 5 +++++ src/main/java/baritone/process/elytra/ElytraBehavior.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index 4917ba921..bdec25bf9 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -1558,6 +1558,11 @@ public final class Settings { */ public final Setting elytraAllowAboveRoof = new Setting<>(false); + /** + * Allow the pathfinder to access the baritone cache to improve pathing + */ + public final Setting elytraUseBaritoneCache = new Setting<>(true); + /** * A map of lowercase setting field names to their respective setting */ diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 1a856f50f..6dd2b598c 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -130,7 +130,7 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destina this.context = new NetherPathfinderContext( Baritone.settings().elytraNetherSeed.value, - baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache"), + Baritone.settings().elytraUseBaritoneCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, ctx.world() ); this.boi = new BlockStateOctreeInterface(context); From 984c71e01d3406289ba0a6031da4ad0200ec75e1 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sun, 16 Mar 2025 02:17:51 -0600 Subject: [PATCH 04/48] Set a better default Y for elytra GoalXZ --- src/main/java/baritone/process/ElytraProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index bc2896cf6..4a809839b 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -348,7 +348,7 @@ public void pathTo(Goal iGoal) { if (iGoal instanceof GoalXZ) { GoalXZ goal = (GoalXZ) iGoal; x = goal.getX(); - y = 64; // TODO: if we go above the roof this doesn't work + y = Math.max((int)ctx.player().getY(), 64); z = goal.getZ(); } else if (iGoal instanceof GoalBlock) { GoalBlock goal = (GoalBlock) iGoal; From 319aa586d227ea0794d70b496704a9cfd7c8eb3e Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sun, 16 Mar 2025 02:18:31 -0600 Subject: [PATCH 05/48] Make sure player is in bounds before attempting elytra pathfinding --- src/main/java/baritone/process/ElytraProcess.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 4a809839b..b44120c4e 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -359,12 +359,17 @@ public void pathTo(Goal iGoal) { throw new IllegalArgumentException("The goal must be a GoalXZ or GoalBlock"); } - // TODO: Optional limit on nether to y=127 int minY = ctx.world().dimensionType().minY(); - int maxY = Math.min(minY + 384, ctx.world().dimensionType().height() + minY); + int maxY = (ctx.world().dimension() == Level.NETHER && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : Math.min(minY + 384, ctx.world().dimensionType().height() + minY); if (y < minY || y >= maxY) { throw new IllegalArgumentException("The goal must have a y value between " + minY + " and " + maxY); } + + int playerY = (int)ctx.player().getY(); + if (playerY < minY || playerY >= maxY) { + throw new IllegalArgumentException("The player must have a y value between " + minY + " and " + maxY); + } + this.pathTo(new BlockPos(x, y, z)); } From 18a689928071483da3fa2c173d7349793acd53a4 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sun, 16 Mar 2025 02:19:01 -0600 Subject: [PATCH 06/48] Allow landing on bedrock (necessary for nether roof) --- src/main/java/baritone/process/ElytraProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index b44120c4e..fd596a3c8 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -493,7 +493,7 @@ private boolean isSafeBlock(Block block) { || block == Blocks.STONE || block == Blocks.DEEPSLATE || block == Blocks.GRASS_BLOCK || block == Blocks.SAND || block == Blocks.RED_SAND || block == Blocks.TERRACOTTA || block == Blocks.SNOW || block == Blocks.ICE || block == Blocks.MYCELIUM || block == Blocks.PODZOL || block == Blocks.DARK_OAK_LEAVES || block == Blocks.JUNGLE_LEAVES - || block == Blocks.END_STONE; + || block == Blocks.END_STONE || block == Blocks.BEDROCK; } private boolean isSafeBlock(BlockPos pos) { From 662ba6564b729586fd64bffeee0c301c4ed67f5d Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sun, 16 Mar 2025 02:41:52 -0600 Subject: [PATCH 07/48] Revert "Set a better default Y for elytra GoalXZ" This reverts commit 984c71e01d3406289ba0a6031da4ad0200ec75e1. --- src/main/java/baritone/process/ElytraProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index fd596a3c8..1cf9dc0b7 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -348,7 +348,7 @@ public void pathTo(Goal iGoal) { if (iGoal instanceof GoalXZ) { GoalXZ goal = (GoalXZ) iGoal; x = goal.getX(); - y = Math.max((int)ctx.player().getY(), 64); + y = 64; // TODO: if we go above the roof this doesn't work z = goal.getZ(); } else if (iGoal instanceof GoalBlock) { GoalBlock goal = (GoalBlock) iGoal; From c9aa9f7910f8de5e1cbbc61d070b8728d296abe5 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Mon, 24 Mar 2025 14:05:25 -0600 Subject: [PATCH 08/48] GoalXZ y-level will consider if player is above nether roof --- src/main/java/baritone/process/ElytraProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 1cf9dc0b7..ccf04b96c 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -348,7 +348,7 @@ public void pathTo(Goal iGoal) { if (iGoal instanceof GoalXZ) { GoalXZ goal = (GoalXZ) iGoal; x = goal.getX(); - y = 64; // TODO: if we go above the roof this doesn't work + y = ctx.world().dimension() == Level.NETHER && ctx.playerFeet().y >= 128 ? 128 : 65; z = goal.getZ(); } else if (iGoal instanceof GoalBlock) { GoalBlock goal = (GoalBlock) iGoal; From b0ba8d3430486573cb88bc7a9ec7c21a65887db1 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Mon, 24 Mar 2025 14:07:50 -0600 Subject: [PATCH 09/48] Rework landing to be safed when descending from high y-levels, and utilize height map to save some cycles if player is flying above ground. --- .../java/baritone/process/ElytraProcess.java | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index ccf04b96c..7844415ed 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -55,6 +55,7 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.Vec3; import java.util.*; @@ -179,7 +180,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { Rotation rotation = RotationUtils.calcRotationFromVec3d(from, to, ctx.playerRotations()); baritone.getLookBehavior().updateTarget(new Rotation(rotation.getYaw(), 0), false); // this will be overwritten, probably, by behavior tick - if (ctx.player().position().y < endPos.y - LANDING_COLUMN_HEIGHT) { + if (ctx.player().position().y < endPos.y - this.landingColumnHeight) { logDirect("bad landing spot, trying again..."); landingSpotIsBad(endPos); } @@ -563,10 +564,20 @@ private BetterBlockPos checkLandingSpot(BlockPos pos, LongOpenHashSet checkedSpo return null; // void } - private static final int LANDING_COLUMN_HEIGHT = 15; + private static final int SHORT_LANDING_COLUMN_HEIGHT = 15; + private static final int LONG_LANDING_COLUMN_HEIGHT = 39; + private int landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT; private Set badLandingSpots = new HashSet<>(); private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) { + if(ctx.player().getY() > ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, start.getX(), start.getZ())) { + return findSafeLandingSpot_heightmap(start); + } else { + return findSafeLandingSpot_underground(start); + } + } + + private BetterBlockPos findSafeLandingSpot_underground(BetterBlockPos start) { Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y)); Set visited = new HashSet<>(); LongOpenHashSet checkedPositions = new LongOpenHashSet(); @@ -576,8 +587,11 @@ private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) { BetterBlockPos pos = queue.poll(); if (ctx.world().isLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions); - if (actualLandingSpot != null && isColumnAir(actualLandingSpot, LANDING_COLUMN_HEIGHT) && hasAirBubble(actualLandingSpot.above(LANDING_COLUMN_HEIGHT)) && !badLandingSpots.contains(actualLandingSpot.above(LANDING_COLUMN_HEIGHT))) { - return actualLandingSpot.above(LANDING_COLUMN_HEIGHT); + if(actualLandingSpot != null) { + landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT; + if (isColumnAir(actualLandingSpot, this.landingColumnHeight) && hasAirBubble(actualLandingSpot.above(this.landingColumnHeight)) && !badLandingSpots.contains(actualLandingSpot.above(this.landingColumnHeight))) { + return actualLandingSpot.above(this.landingColumnHeight); + } } if (visited.add(pos.north())) queue.add(pos.north()); if (visited.add(pos.east())) queue.add(pos.east()); @@ -589,4 +603,34 @@ private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) { } return null; } + + private BetterBlockPos findSafeLandingSpot_heightmap(BetterBlockPos start) { + Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y)); + Set visited = new HashSet<>(); + LongOpenHashSet checkedPositions = new LongOpenHashSet(); + queue.add(start); + + while (!queue.isEmpty()) { + BetterBlockPos qPos = queue.poll(); + if (!ctx.world().isLoaded(qPos)) continue; + + var height = ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, qPos.getX(), qPos.getZ()); + var pos = new BetterBlockPos(qPos.getX(), height+1, qPos.getZ()); + if (ctx.world().isLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { + BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions); + if(actualLandingSpot != null) { + landingColumnHeight = ctx.playerFeet().y - actualLandingSpot.y < LONG_LANDING_COLUMN_HEIGHT ? SHORT_LANDING_COLUMN_HEIGHT : LONG_LANDING_COLUMN_HEIGHT; + if (hasAirBubble(actualLandingSpot.above(landingColumnHeight)) && !badLandingSpots.contains(actualLandingSpot.above(landingColumnHeight))) { + return actualLandingSpot.above(landingColumnHeight); + } + } + + if (visited.add(pos.north())) queue.add(pos.north()); + if (visited.add(pos.east())) queue.add(pos.east()); + if (visited.add(pos.south())) queue.add(pos.south()); + if (visited.add(pos.west())) queue.add(pos.west()); + } + } + return null; + } } From 1a6c43ecc29e84a52b68f543935a87e2868fa6cf Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Mon, 24 Mar 2025 14:12:00 -0600 Subject: [PATCH 10/48] Check negative y landing spots too --- src/main/java/baritone/process/ElytraProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 7844415ed..28bbceb62 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -544,7 +544,7 @@ private boolean hasAirBubble(BlockPos pos) { private BetterBlockPos checkLandingSpot(BlockPos pos, LongOpenHashSet checkedSpots) { BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(pos.getX(), pos.getY(), pos.getZ()); - while (mut.getY() >= 0) { + while (mut.getY() >= ctx.world().dimensionType().minY()) { if (checkedSpots.contains(mut.asLong())) { return null; } From cf3260c1a0fd68d524bea5c03bf13031436721a7 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Wed, 2 Apr 2025 03:03:59 -0400 Subject: [PATCH 11/48] dynamically change destination y if the roof gets in the way --- .../java/baritone/process/ElytraProcess.java | 3 +- .../process/elytra/ElytraBehavior.java | 37 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 28bbceb62..5255098d7 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -349,7 +349,8 @@ public void pathTo(Goal iGoal) { if (iGoal instanceof GoalXZ) { GoalXZ goal = (GoalXZ) iGoal; x = goal.getX(); - y = ctx.world().dimension() == Level.NETHER && ctx.playerFeet().y >= 128 ? 128 : 65; + // ElytraBehavior will automatically change the destination height depending on if we're above or below the roof + y = 64; z = goal.getZ(); } else if (iGoal instanceof GoalBlock) { GoalBlock goal = (GoalBlock) iGoal; diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 6dd2b598c..2e9ccb481 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -45,6 +45,7 @@ import net.minecraft.world.item.Items; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.ChunkSource; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.material.Material; @@ -174,7 +175,7 @@ public CompletableFuture pathToDestination() { public CompletableFuture pathToDestination(final BlockPos from) { final long start = System.nanoTime(); - return this.path0(from, ElytraBehavior.this.destination, UnaryOperator.identity()) + return this.path0(from, destinationFixed(), UnaryOperator.identity()) .thenRun(() -> { final double distance = this.path.get(0).distanceTo(this.path.get(this.path.size() - 1)); if (this.completePath) { @@ -205,7 +206,7 @@ public CompletableFuture pathRecalcSegment(final OptionalInt upToIncl) { final List after = upToIncl.isPresent() ? this.path.subList(upToIncl.getAsInt() + 1, this.path.size()) : Collections.emptyList(); final boolean complete = this.completePath; - return this.path0(ctx.playerFeet(), upToIncl.isPresent() ? this.path.get(upToIncl.getAsInt()) : ElytraBehavior.this.destination, segment -> segment.append(after.stream(), complete || (segment.isFinished() && !upToIncl.isPresent()))) + return this.path0(ctx.playerFeet(), upToIncl.isPresent() ? fixDestination(this.path.get(upToIncl.getAsInt())) : destinationFixed(), segment -> segment.append(after.stream(), complete || (segment.isFinished() && !upToIncl.isPresent()))) .whenComplete((result, ex) -> { this.recalculating = false; if (ex != null) { @@ -229,7 +230,7 @@ public void pathNextSegment(final int afterIncl) { final long start = System.nanoTime(); final BetterBlockPos pathStart = this.path.get(afterIncl); - this.path0(pathStart, ElytraBehavior.this.destination, segment -> segment.prepend(before.stream())) + this.path0(pathStart, destinationFixed(), segment -> segment.prepend(before.stream())) .thenRun(() -> { final int recompute = this.path.size() - before.size() - 1; final double distance = recompute > 0 ? this.path.get(0).distanceTo(this.path.get(recompute)) : 0; @@ -269,13 +270,13 @@ public void clear() { private void setPath(final UnpackedSegment segment) { List path = segment.collect(); if (ElytraBehavior.this.appendDestination) { - BlockPos dest = ElytraBehavior.this.destination; + BlockPos dest = destinationFixed(); BlockPos last = !path.isEmpty() ? path.get(path.size() - 1) : null; if (last != null && ElytraBehavior.this.clearView(Vec3.atLowerCornerOf(dest), Vec3.atLowerCornerOf(last), false)) { path.add(new BetterBlockPos(dest)); } else { - logDirect("unable to land at " + ElytraBehavior.this.destination); - process.landingSpotIsBad(new BetterBlockPos(ElytraBehavior.this.destination)); + logDirect("unable to land at " + dest); + process.landingSpotIsBad(new BetterBlockPos(dest)); } } this.path = new NetherPath(path); @@ -301,6 +302,7 @@ private static UnpackedSegment adjustSegment(UnpackedSegment segment, int minY) // mickey resigned private CompletableFuture path0(BlockPos src, BlockPos dst, UnaryOperator operator) { + // +64 in overworld to normalize y to 0-384 final int minY = ctx.world().dimensionType().minY(); return ElytraBehavior.this.context.pathFindAsync(src.below(minY), dst.below(minY)) .thenApply(UnpackedSegment::from) @@ -348,7 +350,8 @@ private void pathfindAroundObstacles() { // obstacle. where do we return to pathing? // if the end of render distance is closer to goal, then that's fine, otherwise we'd be "digging our hole deeper" and making an already bad backtrack worse OptionalInt rejoinMainPathAt; - if (this.path.get(rangeEndExcl - 1).distanceSq(ElytraBehavior.this.destination) < ctx.playerFeet().distanceSq(ElytraBehavior.this.destination)) { + var dest = destinationFixed(); + if (this.path.get(rangeEndExcl - 1).distanceSq(dest) < ctx.playerFeet().distanceSq(dest)) { rejoinMainPathAt = OptionalInt.of(rangeEndExcl - 1); // rejoin after current render distance } else { rejoinMainPathAt = OptionalInt.empty(); // large backtrack detected. ignore render distance, rejoin later on @@ -571,7 +574,7 @@ private void onTick0() { final List path = this.pathManager.getPath(); if (path.isEmpty()) { return; - } else if (this.destination == null) { + } else if (this.destination == null) { // null check why???? this.pathManager.clear(); return; } @@ -594,7 +597,6 @@ public void tick() { if (this.pathManager.getPath().isEmpty()) { return; } - trySwapElytra(); if (ctx.player().horizontalCollision) { @@ -1350,4 +1352,21 @@ void logVerbose(String message) { logDebug(message); } } + + // so we don't get stuck trying to pathfind through the roof + BetterBlockPos fixDestination(BetterBlockPos dst) { + if (ctx.world().dimension() == Level.NETHER) { + if (ctx.player().getY() >= 128 && dst.y < 128) { + return new BetterBlockPos(dst.x, 128, dst.z); + } + else if (ctx.player().getY() < 128 && dst.y >= 128) { + return new BetterBlockPos(dst.x, 64, dst.z); + } + } + return dst; + } + + BetterBlockPos destinationFixed() { + return fixDestination(this.destination); + } } From 2290801d4cdfe0534a363ef07589f49793f7c533 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Fri, 4 Apr 2025 01:22:28 -0400 Subject: [PATCH 12/48] 1.6 --- gradle.properties | 2 +- src/main/java/baritone/process/elytra/ElytraBehavior.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 845c524f6..dfcdd8ece 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ forge_version=45.0.43 fabric_version=0.14.11 -nether_pathfinder_version=1.4.1 +nether_pathfinder_version=1.6 // These dependencies are used for common and tweaker // while mod loaders usually ship their own version diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 2e9ccb481..efef0d7a0 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -1354,7 +1354,7 @@ void logVerbose(String message) { } // so we don't get stuck trying to pathfind through the roof - BetterBlockPos fixDestination(BetterBlockPos dst) { + private BetterBlockPos fixDestination(BetterBlockPos dst) { if (ctx.world().dimension() == Level.NETHER) { if (ctx.player().getY() >= 128 && dst.y < 128) { return new BetterBlockPos(dst.x, 128, dst.z); @@ -1366,7 +1366,7 @@ else if (ctx.player().getY() < 128 && dst.y >= 128) { return dst; } - BetterBlockPos destinationFixed() { + private BetterBlockPos destinationFixed() { return fixDestination(this.destination); } } From 3fb41ea4e363c1cdbf9f237649ff00b4ae374de4 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Fri, 4 Apr 2025 03:27:51 -0400 Subject: [PATCH 13/48] memset if no air also cave air is real --- .../elytra/NetherPathfinderContext.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 3a26acc87..07cd2c31d 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -34,8 +34,10 @@ import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.phys.Vec3; +import sun.misc.Unsafe; import java.lang.ref.SoftReference; +import java.lang.reflect.Field; import java.nio.file.Path; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -44,11 +46,23 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import static net.minecraft.world.level.chunk.LevelChunkSection.SECTION_SIZE; + /** * @author Brady */ public final class NetherPathfinderContext { + private static final Unsafe UNSAFE; + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + UNSAFE = (Unsafe) f.get(null); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } private static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState(); // This lock must be held while there are active pointers to chunks in java, // but we just hold it for the entire tick so we don't have to think much about it. @@ -254,7 +268,9 @@ private static void writeChunkData(LevelChunk chunk, long chunkPtr) { else if (bs == Blocks.BROWN_MUSHROOM.defaultBlockState()) brownMushroomId = i; } if (airId == -1 & caveAirId == -1) { - // TODO: memset + final long bytesInSection = SECTION_SIZE / 8; + UNSAFE.setMemory(chunkPtr + (y0 * bytesInSection), bytesInSection, (byte) 0xFF); + continue; } // pasted from FasterWorldScanner final BitStorage array = ((IPalettedContainer) bsc).getStorage(); @@ -274,9 +290,7 @@ private static void writeChunkData(LevelChunk chunk, long chunkPtr) { int z = ((idx >> 4) & 15); // Avoid unnecessary writes that may trigger a page allocation - // TODO: Make this predictor friendly - // TODO: cave air? - if ((value != airId) & value != redMushroomId & value != brownMushroomId) { + if (!(value == airId | value == caveAirId) & value != redMushroomId & value != brownMushroomId) { Octree.setBlock(chunkPtr, x, y, z, true); } } From c7b76bca6ae8e6ef443388e82c753b9c4cb454f8 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Sat, 5 Apr 2025 00:05:22 -0400 Subject: [PATCH 14/48] custom allocator by default :^) --- src/api/java/baritone/api/Settings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index bdec25bf9..5cf744fd2 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -1546,7 +1546,7 @@ public final class Settings { /** * May reduce memory usage by using a custom allocator for pathfinding */ - public final Setting elytraCustomAllocator = new Setting<>(false); + public final Setting elytraCustomAllocator = new Setting<>(true); /** * Allow the pathfinder to attempt flight in tighter spaces, useful in caves but can be dangerous. From 094b3ef002f37c8a24a1ca767fd2fd4b60a8aee1 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sat, 5 Apr 2025 11:33:56 -0600 Subject: [PATCH 15/48] Respect allowAboveRoof roof setting when looking for safe landing spots --- src/main/java/baritone/process/ElytraProcess.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 5255098d7..10353be13 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -483,10 +483,9 @@ public double placeBucketCost() { } private static boolean isInBounds(Level dim, BlockPos pos) { - // TODO: Optionally limit nether to just y=127 DimensionType dimType = dim.dimensionType(); int minY = dimType.minY(); - int maxY = Math.min(minY + 384, dimType.height() + minY); + int maxY = (dim.dimension() == Level.NETHER && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : Math.min(minY + 384, dimType.height() + minY); return pos.getY() >= minY && pos.getY() < maxY; } From 1ea67c2589b53d99f3586ad6c026f91273b8c208 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sat, 5 Apr 2025 11:34:15 -0600 Subject: [PATCH 16/48] Allow landing on cobblestone and obsidian --- src/main/java/baritone/process/ElytraProcess.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 10353be13..fb6c8f471 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -494,7 +494,8 @@ private boolean isSafeBlock(Block block) { || block == Blocks.STONE || block == Blocks.DEEPSLATE || block == Blocks.GRASS_BLOCK || block == Blocks.SAND || block == Blocks.RED_SAND || block == Blocks.TERRACOTTA || block == Blocks.SNOW || block == Blocks.ICE || block == Blocks.MYCELIUM || block == Blocks.PODZOL || block == Blocks.DARK_OAK_LEAVES || block == Blocks.JUNGLE_LEAVES - || block == Blocks.END_STONE || block == Blocks.BEDROCK; + || block == Blocks.END_STONE || block == Blocks.BEDROCK + || block == Blocks.OBSIDIAN || block == Blocks.COBBLESTONE; } private boolean isSafeBlock(BlockPos pos) { From 7feaa511ac8599bc2cd9c0dc175a570cd5e559aa Mon Sep 17 00:00:00 2001 From: Babbaj Date: Thu, 10 Apr 2025 00:12:14 -0400 Subject: [PATCH 17/48] elytra fixes --- .../baritone/command/defaults/ElytraCommand.java | 16 ++++++++++------ .../java/baritone/process/ElytraProcess.java | 10 ++++++++-- .../process/elytra/NetherPathfinderContext.java | 4 ++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/baritone/command/defaults/ElytraCommand.java b/src/main/java/baritone/command/defaults/ElytraCommand.java index a5453ff63..2caa35ca3 100644 --- a/src/main/java/baritone/command/defaults/ElytraCommand.java +++ b/src/main/java/baritone/command/defaults/ElytraCommand.java @@ -125,22 +125,26 @@ private Component suggest2b2tSeeds() { private void gatekeep() { MutableComponent gatekeep = Component.literal(""); gatekeep.append("To disable this message, enable the setting elytraTermsAccepted\n"); - gatekeep.append("Baritone Elytra is an experimental feature. It is only intended for long distance travel in the Nether using fireworks for vanilla boost. It will not work with any other mods (\"hacks\") for non-vanilla boost. "); + gatekeep.append("Baritone Elytra is an experimental feature. It is intended for long distance travel in the Nether but will also work in the Overworld, using fireworks for vanilla boost. It will not work with any other mods (\"hacks\") for non-vanilla boost. "); MutableComponent gatekeep2 = Component.literal("If you want Baritone to attempt to take off from the ground for you, you can enable the elytraAutoJump setting (not advisable on laggy servers!). "); gatekeep2.setStyle(gatekeep2.getStyle().withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraAutoJump true")))); gatekeep.append(gatekeep2); MutableComponent gatekeep3 = Component.literal("If you want Baritone to go slower, enable the elytraConserveFireworks setting and/or decrease the elytraFireworkSpeed setting. "); gatekeep3.setStyle(gatekeep3.getStyle().withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraConserveFireworks true\n" + Baritone.settings().prefix.value + "set elytraFireworkSpeed 0.6\n(the 0.6 number is just an example, tweak to your liking)")))); gatekeep.append(gatekeep3); - MutableComponent gatekeep4 = Component.literal("Baritone Elytra "); - MutableComponent red = Component.literal("wants to know the seed"); - red.setStyle(red.getStyle().withColor(ChatFormatting.RED).withUnderlined(true).withBold(true)); - gatekeep4.append(red); + MutableComponent gatekeep4 = Component.literal("Baritone Elytra for use in the "); + MutableComponent red1 = Component.literal("Nether"); + red1.setStyle(red1.getStyle().withColor(ChatFormatting.RED).withUnderlined(true).withBold(true)); + gatekeep4.append(red1); + gatekeep4.append(", "); + MutableComponent red2 = Component.literal("wants to know the seed"); + red2.setStyle(red2.getStyle().withColor(ChatFormatting.RED).withUnderlined(true).withBold(true)); + gatekeep4.append(red2); gatekeep4.append(" of the world you are in. If it doesn't have the correct seed, it will frequently backtrack. It uses the seed to generate terrain far beyond what you can see, since terrain obstacles in the Nether can be much larger than your render distance. "); gatekeep.append(gatekeep4); gatekeep.append("\n"); if (detectOn2b2t()) { - MutableComponent gatekeep5 = Component.literal("It looks like you're on 2b2t. "); + MutableComponent gatekeep5 = Component.literal("It looks like you're on 2b2t. Terrain prediction can be used but new nether terrain can not be predicted on 2b2t. "); gatekeep5.append(suggest2b2tSeeds()); if (!Baritone.settings().elytraPredictTerrain.value) { gatekeep5.append(Baritone.settings().prefix.value + "elytraPredictTerrain is currently disabled. "); diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index fb6c8f471..415472843 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -70,6 +70,7 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private Goal goal; private ElytraBehavior behavior; private boolean predictingTerrain; + private boolean allowTight; @Override public void onLostControl() { @@ -116,11 +117,16 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { logDirect("Nether seed changed, recalculating path"); this.resetState(); } - if (predictingTerrain != Baritone.settings().elytraPredictTerrain.value) { - logDirect("elytraPredictTerrain setting changed, recalculating path"); + if (predictingTerrain != Baritone.settings().elytraPredictTerrain.value && ctx.player().level.dimension() == Level.NETHER) { + logDirect("elytraPredictTerrain setting changed, recalculating path from scratch"); predictingTerrain = Baritone.settings().elytraPredictTerrain.value; this.resetState(); } + if (allowTight != Baritone.settings().elytraAllowTightSpaces.value) { + logDirect("elytraAllowTightSpaces setting changed, recalculating path from scratch"); + allowTight = Baritone.settings().elytraAllowTightSpaces.value; + this.resetState(); + } this.behavior.onTick(); diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 07cd2c31d..ea2b28777 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -84,10 +84,10 @@ public NetherPathfinderContext(long seed, Path cache, Level world) { final int dim; if (this.dimension == Level.NETHER) dim = NetherPathfinder.DIMENSION_NETHER; else if (this.dimension == Level.END) dim = NetherPathfinder.DIMENSION_END; - else dim = NetherPathfinder.DIMENSION_END; + else dim = NetherPathfinder.DIMENSION_OVERWORLD; int height = Math.min(world.dimensionType().height(), 384); if (!Baritone.settings().elytraAllowAboveRoof.value && dim == NetherPathfinder.DIMENSION_NETHER) height = Math.min(height, 128); - this.context = NetherPathfinder.newContext(seed, cache.toString(), dim, height, Baritone.settings().elytraCustomAllocator.value); + this.context = NetherPathfinder.newContext(seed, cache != null ? cache.toString() : null, dim, height, Baritone.settings().elytraCustomAllocator.value); this.seed = seed; } From f3e611621c5e2b46c6420b2c35bb227753ae40a4 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Thu, 10 Apr 2025 22:52:51 -0400 Subject: [PATCH 18/48] hold write lock in queueBlockUpdate --- .../baritone/process/elytra/NetherPathfinderContext.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index ea2b28777..6321fd900 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -126,10 +126,10 @@ public void queueForPacking(final LevelChunk chunkIn) { } public void queueBlockUpdate(BlockChangeEvent event) { - this.readExecutor.execute(() -> { + this.writeExecutor.execute(() -> { ChunkPos chunkPos = event.getChunkPos(); - // not inserting or deleting from the cache hashmap so we should be fine not being exclusive - readLock.lock(); + // not inserting or deleting from the cache hashmap but it would still be bad for this function to race with itself + writeLock.lock(); try { long ptr = NetherPathfinder.getChunk(this.context, chunkPos.x, chunkPos.z); if (ptr == 0) return; // this shouldn't ever happen @@ -140,7 +140,7 @@ public void queueBlockUpdate(BlockChangeEvent event) { Octree.setBlock(ptr, pos.getX() & 15, pos.getY(), pos.getZ() & 15, isSolid); }); } finally { - readLock.unlock(); + writeLock.unlock(); } }); } From d0c3aeac783d8d97c859553849d1da8c888900db Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sat, 12 Apr 2025 16:41:12 -0600 Subject: [PATCH 19/48] Keep zero-based coordinates to within pathfinder code --- .../elytra/BlockStateOctreeInterface.java | 10 +++-- .../process/elytra/ElytraBehavior.java | 30 ++----------- .../elytra/NetherPathfinderContext.java | 42 +++++++++++++++---- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java index 226788c7e..f667db41e 100644 --- a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java +++ b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java @@ -19,6 +19,7 @@ import dev.babbaj.pathfinder.NetherPathfinder; import dev.babbaj.pathfinder.Octree; +import net.minecraft.world.level.dimension.DimensionType; /** * @author Brady @@ -27,19 +28,22 @@ public final class BlockStateOctreeInterface { private final NetherPathfinderContext context; private final long contextPtr; + private final DimensionType dimType; transient long chunkPtr; // Guarantee that the first lookup will fetch the context by setting MAX_VALUE private int prevChunkX = Integer.MAX_VALUE; private int prevChunkZ = Integer.MAX_VALUE; - public BlockStateOctreeInterface(final NetherPathfinderContext context) { + public BlockStateOctreeInterface(final NetherPathfinderContext context, final DimensionType dimType) { this.context = context; this.contextPtr = context.context; + this.dimType = dimType; } public boolean get0(final int x, final int y, final int z) { - if (y < 0 || y > 383) { + final int adjustedY = y - dimType.minY(); + if (adjustedY < 0 || adjustedY > 383) { return false; } final int chunkX = x >> 4; @@ -49,6 +53,6 @@ public boolean get0(final int x, final int y, final int z) { this.prevChunkZ = chunkZ; this.chunkPtr = NetherPathfinder.getChunkOrDefault(this.contextPtr, chunkX, chunkZ, true); } - return Octree.getBlock(this.chunkPtr, x & 0xF, y, z & 0xF); + return Octree.getBlock(this.chunkPtr, x & 0xF, adjustedY, z & 0xF); } } diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index efef0d7a0..a36205cc9 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -134,7 +134,7 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destina Baritone.settings().elytraUseBaritoneCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, ctx.world() ); - this.boi = new BlockStateOctreeInterface(context); + this.boi = new BlockStateOctreeInterface(context, ctx.world().dimensionType()); } public final class PathManager { @@ -294,19 +294,9 @@ public int getNear() { return this.playerNear; } - private static UnpackedSegment adjustSegment(UnpackedSegment segment, int minY) { - return new UnpackedSegment(segment.collect().stream().map((pos)-> { - return pos.above(minY); - }), segment.isFinished()); - } - // mickey resigned private CompletableFuture path0(BlockPos src, BlockPos dst, UnaryOperator operator) { - // +64 in overworld to normalize y to 0-384 - final int minY = ctx.world().dimensionType().minY(); - return ElytraBehavior.this.context.pathFindAsync(src.below(minY), dst.below(minY)) - .thenApply(UnpackedSegment::from) - .thenApply(segment -> adjustSegment(segment, minY)) + return ElytraBehavior.this.context.pathFindAsync(src, dst) .thenApply(operator) .thenAcceptAsync(this::setPath, ctx.minecraft()::execute); } @@ -468,9 +458,7 @@ public void onChunkEvent(ChunkEvent event) { } public void onBlockChange(BlockChangeEvent event) { - final int minY = ctx.world().dimensionType().minY(); - var blocks = event.getBlocks().stream().map((update) -> new Pair<>(update.first().below(minY), update.second())).toList(); - this.context.queueBlockUpdate(new BlockChangeEvent(event.getChunkPos(), blocks)); + this.context.queueBlockUpdate(event); } public void onReceivePacket(PacketEvent event) { @@ -1018,20 +1006,12 @@ private boolean isHitboxClear(final SolverContext context, final Vec3 dest, fina return clear; } - final int minY = ctx.world().dimensionType().minY(); - for (int i = 1; i < src.length; i += 3) { - src[i] -= minY; - dst[i] -= minY; - } return this.context.raytrace(8, src, dst, NetherPathfinderContext.Visibility.ALL); } public boolean clearView(Vec3 start, Vec3 dest, boolean ignoreLava) { final boolean clear; if (!ignoreLava) { - start = start.subtract(0, ctx.world().dimensionType().minY(), 0); - dest = dest.subtract(0, ctx.world().dimensionType().minY(), 0); - // if start == dest then the cpp raytracer dies clear = start.equals(dest) || this.context.raytrace(start, dest); } else { @@ -1039,8 +1019,6 @@ public boolean clearView(Vec3 start, Vec3 dest, boolean ignoreLava) { } if (Baritone.settings().elytraRenderRaytraces.value) { - start = start.add(0, ctx.world().dimensionType().minY(), 0); - dest = dest.add(0, ctx.world().dimensionType().minY(), 0); (clear ? this.clearLines : this.blockedLines).add(new Pair<>(start, dest)); } return clear; @@ -1296,7 +1274,7 @@ private boolean passable(int x, int y, int z, boolean ignoreLava) { final Material mat = this.bsi.get0(x, y, z).getMaterial(); return mat == Material.AIR || mat == Material.LAVA; } else { - return !this.boi.get0(x, y-ctx.world().dimensionType().minY(), z); + return !this.boi.get0(x, y, z); } } diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 6321fd900..d76c86876 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -33,6 +33,7 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.phys.Vec3; import sun.misc.Unsafe; @@ -78,9 +79,11 @@ public final class NetherPathfinderContext { // operations that don't make changes to the chunk cache. could use multiple threads but i'm not sure if it would cause problems. private final ExecutorService readExecutor = Executors.newSingleThreadExecutor(); private final ResourceKey dimension; + private final int minY; public NetherPathfinderContext(long seed, Path cache, Level world) { this.dimension = world.dimension(); + this.minY = world.dimensionType().minY(); final int dim; if (this.dimension == Level.NETHER) dim = NetherPathfinder.DIMENSION_NETHER; else if (this.dimension == Level.END) dim = NetherPathfinder.DIMENSION_END; @@ -134,7 +137,7 @@ public void queueBlockUpdate(BlockChangeEvent event) { long ptr = NetherPathfinder.getChunk(this.context, chunkPos.x, chunkPos.z); if (ptr == 0) return; // this shouldn't ever happen event.getBlocks().forEach(pair -> { - BlockPos pos = pair.first(); + BlockPos pos = pair.first().below(minY); if (pos.getY() < 0 || pos.getY() >= 384) return; boolean isSolid = pair.second() != AIR_BLOCK_STATE; Octree.setBlock(ptr, pos.getX() & 15, pos.getY(), pos.getZ() & 15, isSolid); @@ -145,7 +148,9 @@ public void queueBlockUpdate(BlockChangeEvent event) { }); } - public CompletableFuture pathFindAsync(final BlockPos src, final BlockPos dst) { + public CompletableFuture pathFindAsync(final BlockPos src, final BlockPos dst) { + final BlockPos adjustedSrc = src.below(minY); + final BlockPos adjustedDst = dst.below(minY); boolean generate = Baritone.settings().elytraPredictTerrain.value && this.dimension == Level.NETHER; Lock l = generate ? writeLock : readLock; ExecutorService exec = generate ? writeExecutor : readExecutor; @@ -154,8 +159,8 @@ public CompletableFuture pathFindAsync(final BlockPos src, final Bl try { final PathSegment segment = NetherPathfinder.pathFind( this.context, - src.getX(), src.getY(), src.getZ(), - dst.getX(), dst.getY(), dst.getZ(), + adjustedSrc.getX(), adjustedSrc.getY(), adjustedSrc.getZ(), + adjustedDst.getX(), adjustedDst.getY(), adjustedDst.getZ(), !Baritone.settings().elytraAllowTightSpaces.value, // atleastX4 false, // refine 10000, // timeoutMs @@ -166,7 +171,8 @@ public CompletableFuture pathFindAsync(final BlockPos src, final Bl if (segment == null) { throw new PathCalculationException("Path calculation failed"); } - return segment; + + return new UnpackedSegment(UnpackedSegment.from(segment).collect().stream().map(pos -> pos.above(minY)), segment.finished); } finally { l.unlock(); } @@ -187,7 +193,9 @@ public CompletableFuture pathFindAsync(final BlockPos src, final Bl */ public boolean raytrace(final double startX, final double startY, final double startZ, final double endX, final double endY, final double endZ) { - return NetherPathfinder.isVisible(this.context, NetherPathfinder.CACHE_MISS_SOLID, startX, startY, startZ, endX, endY, endZ); + final double adjustedStartY = startY - this.minY; + final double adjustedEndY = endY - this.minY; + return NetherPathfinder.isVisible(this.context, NetherPathfinder.CACHE_MISS_SOLID, startX, adjustedStartY, startZ, endX, adjustedEndY, endZ); } /** @@ -199,10 +207,21 @@ public boolean raytrace(final double startX, final double startY, final double s * @return {@code true} if there is visibility between the points */ public boolean raytrace(final Vec3 start, final Vec3 end) { - return NetherPathfinder.isVisible(this.context, NetherPathfinder.CACHE_MISS_SOLID, start.x, start.y, start.z, end.x, end.y, end.z); + final Vec3 adjustedStart = start.subtract(0, this.minY, 0); + final Vec3 adjustedEnd = end.subtract(0, this.minY, 0); + return NetherPathfinder.isVisible(this.context, NetherPathfinder.CACHE_MISS_SOLID, adjustedStart.x, adjustedStart.y, adjustedStart.z, adjustedEnd.x, adjustedEnd.y, adjustedEnd.z); } public boolean raytrace(final int count, final double[] src, final double[] dst, final int visibility) { + if (src.length != count * 3 || dst.length != count * 3) { + throw new IllegalArgumentException("Bad array lengths"); + } + + for(int i = 1; i < src.length; i+= 3) { + src[i] -= this.minY; + dst[i] -= this.minY; + } + switch (visibility) { case Visibility.ALL: return NetherPathfinder.isVisibleMulti(this.context, NetherPathfinder.CACHE_MISS_SOLID, count, src, dst, false) == -1; @@ -216,6 +235,15 @@ public boolean raytrace(final int count, final double[] src, final double[] dst, } public void raytrace(final int count, final double[] src, final double[] dst, final boolean[] hitsOut, final double[] hitPosOut) { + if (src.length != count * 3 || dst.length != count * 3) { + throw new IllegalArgumentException("Bad array lengths"); + } + + for(int i = 1; i < src.length; i+= 3) { + src[i] -= this.minY; + dst[i] -= this.minY; + } + NetherPathfinder.raytrace(this.context, NetherPathfinder.CACHE_MISS_SOLID, count, src, dst, hitsOut, hitPosOut); } From 75096e1d352d0cc208d1f1be471ecf8f0e82259d Mon Sep 17 00:00:00 2001 From: Babbaj Date: Sat, 12 Apr 2025 20:15:53 -0400 Subject: [PATCH 20/48] fix crashes --- .../java/baritone/process/ElytraProcess.java | 1 + .../process/elytra/ElytraBehavior.java | 22 ++++++++++++++----- .../elytra/NetherPathfinderContext.java | 6 ++++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 415472843..9b6baac52 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -340,6 +340,7 @@ private void pathTo0(BlockPos destination, boolean appendDestination) { } this.onLostControl(); this.predictingTerrain = ctx.player().level.dimension() == Level.NETHER && Baritone.settings().elytraPredictTerrain.value; + this.allowTight = Baritone.settings().elytraAllowTightSpaces.value; this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination); if (ctx.world() != null) { this.behavior.repackChunks(); diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index efef0d7a0..244541b3b 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -164,9 +164,11 @@ public void tick() { this.ticksNearUnchanged = 0; } - // Obstacles are more important than an incomplete path, handle those first. - this.pathfindAroundObstacles(); - this.attemptNextSegment(); + if (ctx.player().position().y < context.maxHeight && ctx.player().position().y > 0) { + // Obstacles are more important than an incomplete path, handle those first. + this.pathfindAroundObstacles(); + this.attemptNextSegment(); + } } public CompletableFuture pathToDestination() { @@ -463,7 +465,7 @@ public void onRenderPass(RenderEvent event) { public void onChunkEvent(ChunkEvent event) { if (event.isPostPopulate() && this.context != null) { final LevelChunk chunk = ctx.world().getChunk(event.getX(), event.getZ()); - this.context.queueForPacking(chunk); + this.context.queueForPacking(chunk, boi); } } @@ -518,7 +520,7 @@ public void repackChunks() { LevelChunk chunk = chunkProvider.getChunk(x, z, false); if (chunk != null && !chunk.isEmpty()) { - this.context.queueForPacking(chunk); + this.context.queueForPacking(chunk, boi); } } } @@ -656,7 +658,14 @@ public void onPostTick(TickEvent event) { this.pathManager.updatePlayerNear(); final SolverContext context = this.new SolverContext(true); - this.solver = this.solverExecutor.submit(() -> this.solveAngles(context)); + this.solver = this.solverExecutor.submit(() -> { + this.context.readLock.lock(); + try { + return this.solveAngles(context); + } finally { + this.context.readLock.unlock(); + } + }); this.solveNextTick = false; } } @@ -1291,6 +1300,7 @@ private static Vec3 step(final Vec3 motion, final Vec3 lookDirection, final floa return new Vec3(motionX, motionY, motionZ); } + // any call to this must be done with the lock held private boolean passable(int x, int y, int z, boolean ignoreLava) { if (ignoreLava) { final Material mat = this.bsi.get0(x, y, z).getMaterial(); diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 6321fd900..864948c56 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -69,6 +69,7 @@ public final class NetherPathfinderContext { public final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public final ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); public final ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); + public int maxHeight; // Visible for access in BlockStateOctreeInterface final long context; @@ -87,6 +88,7 @@ public NetherPathfinderContext(long seed, Path cache, Level world) { else dim = NetherPathfinder.DIMENSION_OVERWORLD; int height = Math.min(world.dimensionType().height(), 384); if (!Baritone.settings().elytraAllowAboveRoof.value && dim == NetherPathfinder.DIMENSION_NETHER) height = Math.min(height, 128); + this.maxHeight = height; this.context = NetherPathfinder.newContext(seed, cache != null ? cache.toString() : null, dim, height, Baritone.settings().elytraCustomAllocator.value); this.seed = seed; } @@ -107,7 +109,7 @@ public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks, Blo }); } - public void queueForPacking(final LevelChunk chunkIn) { + public void queueForPacking(final LevelChunk chunkIn, BlockStateOctreeInterface boi) { final SoftReference ref = new SoftReference<>(chunkIn); this.writeExecutor.execute(() -> { // TODO: Prioritize packing recent chunks and/or ones that the path goes through, @@ -116,6 +118,8 @@ public void queueForPacking(final LevelChunk chunkIn) { if (chunk != null) { writeLock.lock(); try { + // we might free this chunk + boi.chunkPtr = 0L; long ptr = NetherPathfinder.allocateAndInsertChunk(this.context, chunk.getPos().x, chunk.getPos().z); writeChunkData(chunk, ptr); } finally { From 470ef108334c4c15a927fec904cf00ebac16ef02 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Sat, 12 Apr 2025 20:24:13 -0400 Subject: [PATCH 21/48] final --- .../java/baritone/process/elytra/NetherPathfinderContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 734b71a7c..b0259243d 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -70,7 +70,7 @@ public final class NetherPathfinderContext { public final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public final ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); public final ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); - public int maxHeight; + public final int maxHeight; // Visible for access in BlockStateOctreeInterface final long context; From 091206918738698762c62e1b6eb20443fd8e3f63 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Sat, 12 Apr 2025 20:26:16 -0400 Subject: [PATCH 22/48] rename elytraUseBaritoneCache --- src/api/java/baritone/api/Settings.java | 2 +- src/main/java/baritone/process/elytra/ElytraBehavior.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index 5cf744fd2..ca839fb8e 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -1561,7 +1561,7 @@ public final class Settings { /** * Allow the pathfinder to access the baritone cache to improve pathing */ - public final Setting elytraUseBaritoneCache = new Setting<>(true); + public final Setting elytraUseCache = new Setting<>(true); /** * A map of lowercase setting field names to their respective setting diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index fb9260c42..222013132 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -131,7 +131,7 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destina this.context = new NetherPathfinderContext( Baritone.settings().elytraNetherSeed.value, - Baritone.settings().elytraUseBaritoneCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, + Baritone.settings().elytraUseCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, ctx.world() ); this.boi = new BlockStateOctreeInterface(context, ctx.world().dimensionType()); From 20c1631abfe8b64fa119e8012b348ebdd55a32a8 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Sat, 12 Apr 2025 21:52:14 -0400 Subject: [PATCH 23/48] oops --- src/main/java/baritone/process/elytra/ElytraBehavior.java | 4 +++- .../java/baritone/process/elytra/NetherPathfinderContext.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 222013132..3469686c4 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -164,7 +164,9 @@ public void tick() { this.ticksNearUnchanged = 0; } - if (ctx.player().position().y < context.maxHeight && ctx.player().position().y > 0) { + int minY = ctx.world().dimensionType().minY(); + int y = ctx.playerFeet().y; + if (y >= minY && y < minY + context.maxHeight) { // Obstacles are more important than an incomplete path, handle those first. this.pathfindAroundObstacles(); this.attemptNextSegment(); diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index b0259243d..b6ff7802b 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -155,7 +155,7 @@ public void queueBlockUpdate(BlockChangeEvent event) { public CompletableFuture pathFindAsync(final BlockPos src, final BlockPos dst) { final BlockPos adjustedSrc = src.below(minY); final BlockPos adjustedDst = dst.below(minY); - boolean generate = Baritone.settings().elytraPredictTerrain.value && this.dimension == Level.NETHER; + boolean generate = Baritone.settings().elytraPredictTerrain.value && this.dimension == Level.NETHER; Lock l = generate ? writeLock : readLock; ExecutorService exec = generate ? writeExecutor : readExecutor; return CompletableFuture.supplyAsync(() -> { From 68dc110b8b6f85c38e01390dd25bfbe8ba6809ea Mon Sep 17 00:00:00 2001 From: Babbaj Date: Mon, 14 Apr 2025 23:46:49 -0400 Subject: [PATCH 24/48] fix #elytra reset bypassing y checks --- .../command/defaults/ElytraCommand.java | 6 ++++- .../java/baritone/process/ElytraProcess.java | 22 +++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/baritone/command/defaults/ElytraCommand.java b/src/main/java/baritone/command/defaults/ElytraCommand.java index 2caa35ca3..5b362be5f 100644 --- a/src/main/java/baritone/command/defaults/ElytraCommand.java +++ b/src/main/java/baritone/command/defaults/ElytraCommand.java @@ -82,7 +82,11 @@ public void execute(String label, IArgConsumer args) throws CommandException { final String action = args.getString(); switch (action) { case "reset": { - elytra.resetState(); + try { + elytra.resetState(); + } catch (IllegalArgumentException ex) { + throw new CommandInvalidStateException(ex.getMessage()); + } logDirect("Reset state but still flying to same goal"); break; } diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 9b6baac52..2719f8466 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -331,6 +331,17 @@ public BlockPos currentDestination() { @Override public void pathTo(BlockPos destination) { + int minY = ctx.world().dimensionType().minY(); + int maxY = (ctx.world().dimension() == Level.NETHER && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : Math.min(minY + 384, ctx.world().dimensionType().height() + minY); + if (destination.getY() < minY || destination.getY() >= maxY) { + throw new IllegalArgumentException("The goal must have a y value between " + minY + " and " + maxY); + } + + int playerY = (int)ctx.player().getY(); + if (playerY < minY || playerY >= maxY) { + throw new IllegalArgumentException("The player must have a y value between " + minY + " and " + maxY); + } + this.pathTo0(destination, false); } @@ -368,17 +379,6 @@ public void pathTo(Goal iGoal) { throw new IllegalArgumentException("The goal must be a GoalXZ or GoalBlock"); } - int minY = ctx.world().dimensionType().minY(); - int maxY = (ctx.world().dimension() == Level.NETHER && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : Math.min(minY + 384, ctx.world().dimensionType().height() + minY); - if (y < minY || y >= maxY) { - throw new IllegalArgumentException("The goal must have a y value between " + minY + " and " + maxY); - } - - int playerY = (int)ctx.player().getY(); - if (playerY < minY || playerY >= maxY) { - throw new IllegalArgumentException("The player must have a y value between " + minY + " and " + maxY); - } - this.pathTo(new BlockPos(x, y, z)); } From 04813a8312550ba5c18a0611b175f5062d21db46 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Tue, 15 Apr 2025 00:38:39 -0400 Subject: [PATCH 25/48] i forgor to commit this --- src/main/java/baritone/process/elytra/ElytraBehavior.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 3469686c4..05ff69bc3 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -660,6 +660,7 @@ public void onPostTick(TickEvent event) { } } + // calls passable which requires a read lock private Solution solveAngles(final SolverContext context) { final NetherPath path = context.path; final int playerNear = landingMode ? path.size() - 1 : context.playerNear; From 3d5b744a3c6e5666de8bc9daa2f5089e01be41eb Mon Sep 17 00:00:00 2001 From: Babbaj Date: Tue, 15 Apr 2025 15:06:58 -0400 Subject: [PATCH 26/48] fixes --- .../java/baritone/process/ElytraProcess.java | 34 +++++++++++-------- .../elytra/BlockStateOctreeInterface.java | 8 ++--- .../process/elytra/ElytraBehavior.java | 3 +- .../elytra/NetherPathfinderContext.java | 2 +- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 2719f8466..34e5c6aee 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -44,6 +44,7 @@ import baritone.utils.BaritoneProcessHelper; import baritone.utils.PathingCommandContext; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.core.NonNullList; import net.minecraft.world.entity.EquipmentSlot; @@ -112,20 +113,25 @@ public void resetState() { @Override public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { - final long seedSetting = Baritone.settings().elytraNetherSeed.value; - if (seedSetting != this.behavior.context.getSeed()) { - logDirect("Nether seed changed, recalculating path"); - this.resetState(); - } - if (predictingTerrain != Baritone.settings().elytraPredictTerrain.value && ctx.player().level.dimension() == Level.NETHER) { - logDirect("elytraPredictTerrain setting changed, recalculating path from scratch"); - predictingTerrain = Baritone.settings().elytraPredictTerrain.value; - this.resetState(); - } - if (allowTight != Baritone.settings().elytraAllowTightSpaces.value) { - logDirect("elytraAllowTightSpaces setting changed, recalculating path from scratch"); - allowTight = Baritone.settings().elytraAllowTightSpaces.value; - this.resetState(); + try { + final long seedSetting = Baritone.settings().elytraNetherSeed.value; + if (seedSetting != this.behavior.context.getSeed()) { + logDirect("Nether seed changed, recalculating path"); + this.resetState(); + } + if (predictingTerrain != Baritone.settings().elytraPredictTerrain.value && ctx.player().level.dimension() == Level.NETHER) { + logDirect("elytraPredictTerrain setting changed, recalculating path from scratch"); + predictingTerrain = Baritone.settings().elytraPredictTerrain.value; + this.resetState(); + } + if (allowTight != Baritone.settings().elytraAllowTightSpaces.value) { + logDirect("elytraAllowTightSpaces setting changed, recalculating path from scratch"); + allowTight = Baritone.settings().elytraAllowTightSpaces.value; + this.resetState(); + } + } catch (IllegalArgumentException e) { + logDirect(e.getMessage(), ChatFormatting.RED); + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } this.behavior.onTick(); diff --git a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java index f667db41e..52fdfdd66 100644 --- a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java +++ b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java @@ -28,21 +28,21 @@ public final class BlockStateOctreeInterface { private final NetherPathfinderContext context; private final long contextPtr; - private final DimensionType dimType; + private final int minY; transient long chunkPtr; // Guarantee that the first lookup will fetch the context by setting MAX_VALUE private int prevChunkX = Integer.MAX_VALUE; private int prevChunkZ = Integer.MAX_VALUE; - public BlockStateOctreeInterface(final NetherPathfinderContext context, final DimensionType dimType) { + public BlockStateOctreeInterface(final NetherPathfinderContext context) { this.context = context; this.contextPtr = context.context; - this.dimType = dimType; + this.minY = context.minY; } public boolean get0(final int x, final int y, final int z) { - final int adjustedY = y - dimType.minY(); + final int adjustedY = y - this.minY; if (adjustedY < 0 || adjustedY > 383) { return false; } diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 05ff69bc3..572813325 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -134,7 +134,7 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destina Baritone.settings().elytraUseCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, ctx.world() ); - this.boi = new BlockStateOctreeInterface(context, ctx.world().dimensionType()); + this.boi = new BlockStateOctreeInterface(context); } public final class PathManager { @@ -152,6 +152,7 @@ public PathManager() { this.clear(); } + // requires read lock to be held public void tick() { // Recalculate closest path node this.updatePlayerNear(); diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index b6ff7802b..d1784061b 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -80,7 +80,7 @@ public final class NetherPathfinderContext { // operations that don't make changes to the chunk cache. could use multiple threads but i'm not sure if it would cause problems. private final ExecutorService readExecutor = Executors.newSingleThreadExecutor(); private final ResourceKey dimension; - private final int minY; + final int minY; public NetherPathfinderContext(long seed, Path cache, Level world) { this.dimension = world.dimension(); From e545294d79c16a7491c3fb7bedd911081396f795 Mon Sep 17 00:00:00 2001 From: Babbaj Date: Tue, 22 Apr 2025 02:58:05 -0400 Subject: [PATCH 27/48] fix exception when setting out of bounds goal while elytra flying --- src/main/java/baritone/process/CustomGoalProcess.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/CustomGoalProcess.java b/src/main/java/baritone/process/CustomGoalProcess.java index d0dca9cbf..3e34e0d8a 100644 --- a/src/main/java/baritone/process/CustomGoalProcess.java +++ b/src/main/java/baritone/process/CustomGoalProcess.java @@ -23,6 +23,7 @@ import baritone.api.process.PathingCommand; import baritone.api.process.PathingCommandType; import baritone.utils.BaritoneProcessHelper; +import net.minecraft.ChatFormatting; /** * As set by ExampleBaritoneControl or something idk @@ -57,7 +58,11 @@ public void setGoal(Goal goal) { this.goal = goal; this.mostRecentGoal = goal; if (baritone.getElytraProcess().isActive()) { - baritone.getElytraProcess().pathTo(goal); + try { + baritone.getElytraProcess().pathTo(goal); + } catch (IllegalArgumentException e) { + logDirect("Failed to update elytra goal because: " + e.getMessage(), ChatFormatting.RED); + } } if (this.state == State.NONE) { this.state = State.GOAL_SET; From 5624d4a67982ece2f9f4ccfa9544a412249d1ecc Mon Sep 17 00:00:00 2001 From: Babbaj Date: Tue, 22 Apr 2025 23:21:06 -0400 Subject: [PATCH 28/48] fix deadlock --- .../process/elytra/ElytraBehavior.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 572813325..30bae2a27 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -152,7 +152,6 @@ public PathManager() { this.clear(); } - // requires read lock to be held public void tick() { // Recalculate closest path node this.updatePlayerNear(); @@ -168,8 +167,13 @@ public void tick() { int minY = ctx.world().dimensionType().minY(); int y = ctx.playerFeet().y; if (y >= minY && y < minY + context.maxHeight) { - // Obstacles are more important than an incomplete path, handle those first. - this.pathfindAroundObstacles(); + context.readLock.lock(); + try { + // Obstacles are more important than an incomplete path, handle those first. + this.pathfindAroundObstacles(); + } finally { + context.readLock.unlock(); + } this.attemptNextSegment(); } } @@ -306,6 +310,7 @@ private CompletableFuture path0(BlockPos src, BlockPos dst, UnaryOperator< .thenAcceptAsync(this::setPath, ctx.minecraft()::execute); } + // required read lock to be held private void pathfindAroundObstacles() { if (this.recalculating) { return; @@ -518,12 +523,7 @@ public void repackChunks() { } public void onTick() { - this.context.readLock.lock(); - try { - this.onTick0(); - } finally { - this.context.readLock.unlock(); - } + this.onTick0(); final long now = System.currentTimeMillis(); if ((now - this.timeLastCacheCull) / 1000 > Baritone.settings().elytraTimeBetweenCacheCullSecs.value) { this.context.queueCacheCulling(ctx.player().chunkPosition().x, ctx.player().chunkPosition().z, Baritone.settings().elytraCacheCullDistance.value, this.boi); From a2784be16542416efde65e2663066aa905276547 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Thu, 10 Jul 2025 23:26:07 -0600 Subject: [PATCH 29/48] Create an interface for elytra pathfinding --- .../elytra/IElytraPathfinderContext.java | 59 +++++++++++++++++++ .../api/pathing}/elytra/UnpackedSegment.java | 2 +- .../java/baritone/process/ElytraProcess.java | 4 +- .../process/elytra/ElytraBehavior.java | 26 ++++---- .../elytra/NetherPathfinderContext.java | 41 ++++++++----- 5 files changed, 101 insertions(+), 31 deletions(-) create mode 100644 src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java rename src/{main/java/baritone/process => api/java/baritone/api/pathing}/elytra/UnpackedSegment.java (98%) diff --git a/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java b/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java new file mode 100644 index 000000000..d534465da --- /dev/null +++ b/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java @@ -0,0 +1,59 @@ +/* + * 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.api.pathing.elytra; + +import baritone.api.event.events.BlockChangeEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.phys.Vec3; + +import java.util.concurrent.CompletableFuture; + +public interface IElytraPathfinderContext { + public boolean hasChunk(ChunkPos pos); + public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks); + public void queueForPacking(final LevelChunk chunkIn); + public void queueBlockUpdate(BlockChangeEvent event); + public CompletableFuture pathFindAsync(final BlockPos src, final BlockPos dst); + public boolean raytrace(final double startX, final double startY, final double startZ, + final double endX, final double endY, final double endZ); + public boolean raytrace(final Vec3 start, final Vec3 end); + public boolean raytrace(final int count, final double[] src, final double[] dst, final int visibility); + public void raytrace(final int count, final double[] src, final double[] dst, final boolean[] hitsOut, final double[] hitPosOut); + public void cancel(); + public void destroy(); + public long getSeed(); + + + // zi: It appears only the read lock is used outside of the context + public void RLock(); + public void RUnlock(); + + public int getMaxHeight(); + public boolean passable(int x, int y, int z); + + public static final class Visibility { + public static final int ALL = 0; + public static final int NONE = 1; + public static final int ANY = 2; + private Visibility() {} + } +} + + diff --git a/src/main/java/baritone/process/elytra/UnpackedSegment.java b/src/api/java/baritone/api/pathing/elytra/UnpackedSegment.java similarity index 98% rename from src/main/java/baritone/process/elytra/UnpackedSegment.java rename to src/api/java/baritone/api/pathing/elytra/UnpackedSegment.java index e50ab3235..63a936741 100644 --- a/src/main/java/baritone/process/elytra/UnpackedSegment.java +++ b/src/api/java/baritone/api/pathing/elytra/UnpackedSegment.java @@ -15,7 +15,7 @@ * along with Baritone. If not, see . */ -package baritone.process.elytra; +package baritone.api.pathing.elytra; import baritone.api.utils.BetterBlockPos; import dev.babbaj.pathfinder.PathSegment; diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 9b6baac52..bd69d73f9 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -197,11 +197,11 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { behavior.landingMode = this.state == State.LANDING; this.goal = null; baritone.getInputOverrideHandler().clearAllKeys(); - behavior.context.readLock.lock(); + behavior.context.RLock(); try { behavior.tick(); } finally { - behavior.context.readLock.unlock(); + behavior.context.RUnlock(); } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } else if (this.state == State.LANDING) { diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 3469686c4..078660410 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -22,6 +22,8 @@ import baritone.api.behavior.look.IAimProcessor; import baritone.api.behavior.look.ITickableAimProcessor; import baritone.api.event.events.*; +import baritone.api.pathing.elytra.IElytraPathfinderContext; +import baritone.api.pathing.elytra.UnpackedSegment; import baritone.api.pathing.goals.GoalBlock; import baritone.api.utils.*; import baritone.api.utils.input.Input; @@ -75,7 +77,7 @@ public final class ElytraBehavior implements Helper { private List visiblePath; // :sunglasses: - public final NetherPathfinderContext context; + public final IElytraPathfinderContext context; public final PathManager pathManager; private final ElytraProcess process; @@ -102,7 +104,6 @@ public final class ElytraBehavior implements Helper { private final int[] nextTickBoostCounter; private BlockStateInterface bsi; - private final BlockStateOctreeInterface boi; public final BetterBlockPos destination; private final boolean appendDestination; @@ -134,7 +135,6 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destina Baritone.settings().elytraUseCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, ctx.world() ); - this.boi = new BlockStateOctreeInterface(context, ctx.world().dimensionType()); } public final class PathManager { @@ -166,7 +166,7 @@ public void tick() { int minY = ctx.world().dimensionType().minY(); int y = ctx.playerFeet().y; - if (y >= minY && y < minY + context.maxHeight) { + if (y >= minY && y < minY + context.getMaxHeight()) { // Obstacles are more important than an incomplete path, handle those first. this.pathfindAroundObstacles(); this.attemptNextSegment(); @@ -457,7 +457,7 @@ public void onRenderPass(RenderEvent event) { public void onChunkEvent(ChunkEvent event) { if (event.isPostPopulate() && this.context != null) { final LevelChunk chunk = ctx.world().getChunk(event.getX(), event.getZ()); - this.context.queueForPacking(chunk, boi); + this.context.queueForPacking(chunk); } } @@ -510,22 +510,22 @@ public void repackChunks() { LevelChunk chunk = chunkProvider.getChunk(x, z, false); if (chunk != null && !chunk.isEmpty()) { - this.context.queueForPacking(chunk, boi); + this.context.queueForPacking(chunk); } } } } public void onTick() { - this.context.readLock.lock(); + this.context.RLock(); try { this.onTick0(); } finally { - this.context.readLock.unlock(); + this.context.RUnlock(); } final long now = System.currentTimeMillis(); if ((now - this.timeLastCacheCull) / 1000 > Baritone.settings().elytraTimeBetweenCacheCullSecs.value) { - this.context.queueCacheCulling(ctx.player().chunkPosition().x, ctx.player().chunkPosition().z, Baritone.settings().elytraCacheCullDistance.value, this.boi); + this.context.queueCacheCulling(ctx.player().chunkPosition().x, ctx.player().chunkPosition().z, Baritone.settings().elytraCacheCullDistance.value); this.timeLastCacheCull = now; } } @@ -649,11 +649,11 @@ public void onPostTick(TickEvent event) { final SolverContext context = this.new SolverContext(true); this.solver = this.solverExecutor.submit(() -> { - this.context.readLock.lock(); + this.context.RLock(); try { return this.solveAngles(context); } finally { - this.context.readLock.unlock(); + this.context.RUnlock(); } }); this.solveNextTick = false; @@ -1017,7 +1017,7 @@ private boolean isHitboxClear(final SolverContext context, final Vec3 dest, fina return clear; } - return this.context.raytrace(8, src, dst, NetherPathfinderContext.Visibility.ALL); + return this.context.raytrace(8, src, dst, IElytraPathfinderContext.Visibility.ALL); } public boolean clearView(Vec3 start, Vec3 dest, boolean ignoreLava) { @@ -1286,7 +1286,7 @@ private boolean passable(int x, int y, int z, boolean ignoreLava) { final Material mat = this.bsi.get0(x, y, z).getMaterial(); return mat == Material.AIR || mat == Material.LAVA; } else { - return !this.boi.get0(x, y, z); + return this.context.passable(x, y, z); } } diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index b6ff7802b..2b494b8db 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -19,6 +19,8 @@ import baritone.Baritone; import baritone.api.event.events.BlockChangeEvent; +import baritone.api.pathing.elytra.IElytraPathfinderContext; +import baritone.api.pathing.elytra.UnpackedSegment; import baritone.utils.accessor.IPalettedContainer; import dev.babbaj.pathfinder.NetherPathfinder; import dev.babbaj.pathfinder.Octree; @@ -33,7 +35,7 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.PalettedContainer; -import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.material.Material; import net.minecraft.world.phys.Vec3; import sun.misc.Unsafe; @@ -52,7 +54,7 @@ /** * @author Brady */ -public final class NetherPathfinderContext { +public final class NetherPathfinderContext implements IElytraPathfinderContext { private static final Unsafe UNSAFE; static { @@ -81,6 +83,7 @@ public final class NetherPathfinderContext { private final ExecutorService readExecutor = Executors.newSingleThreadExecutor(); private final ResourceKey dimension; private final int minY; + private final BlockStateOctreeInterface boi; public NetherPathfinderContext(long seed, Path cache, Level world) { this.dimension = world.dimension(); @@ -94,17 +97,18 @@ public NetherPathfinderContext(long seed, Path cache, Level world) { this.maxHeight = height; this.context = NetherPathfinder.newContext(seed, cache != null ? cache.toString() : null, dim, height, Baritone.settings().elytraCustomAllocator.value); this.seed = seed; + this.boi = new BlockStateOctreeInterface(this, world.dimensionType()); } public boolean hasChunk(ChunkPos pos) { return NetherPathfinder.hasChunkFromJava(this.context, pos.x, pos.z); } - public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks, BlockStateOctreeInterface boi) { + public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks) { this.writeExecutor.execute(() -> { writeLock.lock(); try { - boi.chunkPtr = 0L; + this.boi.chunkPtr = 0L; NetherPathfinder.cullFarChunks(this.context, chunkX, chunkZ, maxDistanceBlocks); } finally { writeLock.unlock(); @@ -112,7 +116,7 @@ public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks, Blo }); } - public void queueForPacking(final LevelChunk chunkIn, BlockStateOctreeInterface boi) { + public void queueForPacking(final LevelChunk chunkIn) { final SoftReference ref = new SoftReference<>(chunkIn); this.writeExecutor.execute(() -> { // TODO: Prioritize packing recent chunks and/or ones that the path goes through, @@ -122,7 +126,7 @@ public void queueForPacking(final LevelChunk chunkIn, BlockStateOctreeInterface writeLock.lock(); try { // we might free this chunk - boi.chunkPtr = 0L; + this.boi.chunkPtr = 0L; long ptr = NetherPathfinder.allocateAndInsertChunk(this.context, chunk.getPos().x, chunk.getPos().z); writeChunkData(chunk, ptr); } finally { @@ -251,6 +255,10 @@ public void raytrace(final int count, final double[] src, final double[] dst, fi NetherPathfinder.raytrace(this.context, NetherPathfinder.CACHE_MISS_SOLID, count, src, dst, hitsOut, hitPosOut); } + public boolean passable(int x, int y, int z) { + return !this.boi.get0(x, y, z); + } + public void cancel() { NetherPathfinder.cancel(this.context); } @@ -275,6 +283,18 @@ public long getSeed() { return this.seed; } + public void RLock() { + this.readLock.lock(); + } + + public void RUnlock() { + this.readLock.unlock(); + } + + public int getMaxHeight() { + return this.maxHeight; + } + private static void writeChunkData(LevelChunk chunk, long chunkPtr) { try { LevelChunkSection[] chunkInternalStorageArray = chunk.getSections(); @@ -334,15 +354,6 @@ private static void writeChunkData(LevelChunk chunk, long chunkPtr) { } } - public static final class Visibility { - - public static final int ALL = 0; - public static final int NONE = 1; - public static final int ANY = 2; - - private Visibility() {} - } - public static boolean isSupported() { return NetherPathfinder.isThisSystemSupported(); } From 04c4ba2ea28af964f987e350d8b117c2494edcae Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Fri, 11 Jul 2025 13:35:22 -0600 Subject: [PATCH 30/48] Add IElytraContextFactory --- .../pathing/elytra/IElytraContextFactory.java | 26 +++++++++++ .../baritone/api/process/IElytraProcess.java | 6 +++ .../java/baritone/process/ElytraProcess.java | 15 ++++++- .../process/elytra/ElytraBehavior.java | 11 ++--- .../NetherPathfinderContextFactory.java | 43 +++++++++++++++++++ .../process/elytra/NullElytraProcess.java | 13 ++++++ 6 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 src/api/java/baritone/api/pathing/elytra/IElytraContextFactory.java create mode 100644 src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java diff --git a/src/api/java/baritone/api/pathing/elytra/IElytraContextFactory.java b/src/api/java/baritone/api/pathing/elytra/IElytraContextFactory.java new file mode 100644 index 000000000..37cdba9b5 --- /dev/null +++ b/src/api/java/baritone/api/pathing/elytra/IElytraContextFactory.java @@ -0,0 +1,26 @@ +/* + * 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.api.pathing.elytra; + +import baritone.api.utils.IPlayerContext; + +import java.nio.file.Path; + +public interface IElytraContextFactory { + IElytraPathfinderContext create(IPlayerContext ctx, Path cacheDir); +} diff --git a/src/api/java/baritone/api/process/IElytraProcess.java b/src/api/java/baritone/api/process/IElytraProcess.java index 28328f901..9e0532999 100644 --- a/src/api/java/baritone/api/process/IElytraProcess.java +++ b/src/api/java/baritone/api/process/IElytraProcess.java @@ -17,6 +17,7 @@ package baritone.api.process; +import baritone.api.pathing.elytra.IElytraContextFactory; import baritone.api.pathing.goals.Goal; import net.minecraft.core.BlockPos; @@ -43,6 +44,11 @@ public interface IElytraProcess extends IBaritoneProcess { */ boolean isLoaded(); + + IElytraContextFactory getContextFactory(); + void setContextFactory(IElytraContextFactory factory); + + /* * FOR INTERNAL USE ONLY. MAY BE REMOVED AT ANY TIME. */ diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index bd69d73f9..9588b9d9a 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -22,6 +22,7 @@ import baritone.api.event.events.*; import baritone.api.event.events.type.EventState; import baritone.api.event.listener.AbstractGameEventListener; +import baritone.api.pathing.elytra.IElytraContextFactory; import baritone.api.pathing.goals.Goal; import baritone.api.pathing.goals.GoalBlock; import baritone.api.pathing.goals.GoalXZ; @@ -40,6 +41,7 @@ import baritone.pathing.movement.movements.MovementFall; import baritone.process.elytra.ElytraBehavior; import baritone.process.elytra.NetherPathfinderContext; +import baritone.process.elytra.NetherPathfinderContextFactory; import baritone.process.elytra.NullElytraProcess; import baritone.utils.BaritoneProcessHelper; import baritone.utils.PathingCommandContext; @@ -71,6 +73,7 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private ElytraBehavior behavior; private boolean predictingTerrain; private boolean allowTight; + private IElytraContextFactory contextFactory = new NetherPathfinderContextFactory(); @Override public void onLostControl() { @@ -341,7 +344,7 @@ private void pathTo0(BlockPos destination, boolean appendDestination) { this.onLostControl(); this.predictingTerrain = ctx.player().level.dimension() == Level.NETHER && Baritone.settings().elytraPredictTerrain.value; this.allowTight = Baritone.settings().elytraAllowTightSpaces.value; - this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination); + this.behavior = new ElytraBehavior(this.baritone, this, this.contextFactory.create(ctx, baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache")), destination, appendDestination); if (ctx.world() != null) { this.behavior.repackChunks(); } @@ -407,6 +410,16 @@ public boolean isLoaded() { return true; } + @Override + public IElytraContextFactory getContextFactory() { + return this.contextFactory; + } + + @Override + public void setContextFactory(IElytraContextFactory factory) { + this.contextFactory = factory == null ? new NetherPathfinderContextFactory() : factory; + } + @Override public boolean isSafeToCancel() { return !this.isActive() || !(this.state == State.FLYING || this.state == State.START_FLYING); diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 078660410..65a294c20 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -77,7 +77,7 @@ public final class ElytraBehavior implements Helper { private List visiblePath; // :sunglasses: - public final IElytraPathfinderContext context; + public IElytraPathfinderContext context; public final PathManager pathManager; private final ElytraProcess process; @@ -118,7 +118,7 @@ public final class ElytraBehavior implements Helper { private int invTickCountdown = 0; private final Queue invTransactionQueue = new LinkedList<>(); - public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destination, boolean appendDestination) { + public ElytraBehavior(Baritone baritone, ElytraProcess process, IElytraPathfinderContext context, BlockPos destination, boolean appendDestination) { this.baritone = baritone; this.ctx = baritone.getPlayerContext(); this.clearLines = new CopyOnWriteArrayList<>(); @@ -129,12 +129,7 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destina this.appendDestination = appendDestination; this.solverExecutor = Executors.newSingleThreadExecutor(); this.nextTickBoostCounter = new int[2]; - - this.context = new NetherPathfinderContext( - Baritone.settings().elytraNetherSeed.value, - Baritone.settings().elytraUseCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, - ctx.world() - ); + this.context = context; } public final class PathManager { diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java b/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java new file mode 100644 index 000000000..495b75b84 --- /dev/null +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java @@ -0,0 +1,43 @@ +/* + * 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.process.elytra; + +import baritone.Baritone; +import baritone.api.pathing.elytra.IElytraContextFactory; +import baritone.api.pathing.elytra.IElytraPathfinderContext; +import baritone.api.utils.IPlayerContext; + +import java.nio.file.Path; + +public class NetherPathfinderContextFactory implements IElytraContextFactory { + + @Override + public NetherPathfinderContext create(IPlayerContext ctx, Path cacheDir) { + return new NetherPathfinderContext( + Baritone.settings().elytraNetherSeed.value, + Baritone.settings().elytraUseCache.value ? cacheDir : null, + ctx.world() + ); + } + + @Override + public String toString() { + return "NetherPathfinderContextFactory{}"; + } + +} diff --git a/src/main/java/baritone/process/elytra/NullElytraProcess.java b/src/main/java/baritone/process/elytra/NullElytraProcess.java index 07d5fde0e..11c48ee31 100644 --- a/src/main/java/baritone/process/elytra/NullElytraProcess.java +++ b/src/main/java/baritone/process/elytra/NullElytraProcess.java @@ -18,6 +18,7 @@ package baritone.process.elytra; import baritone.Baritone; +import baritone.api.pathing.elytra.IElytraContextFactory; import baritone.api.pathing.goals.Goal; import baritone.api.process.IElytraProcess; import baritone.api.process.PathingCommand; @@ -83,8 +84,20 @@ public boolean isLoaded() { return false; } + @Override + public IElytraContextFactory getContextFactory() { + return null; + } + + @Override + public void setContextFactory(IElytraContextFactory factory) { + + } + @Override public boolean isSafeToCancel() { return true; } + + } From 0f280756c439dc55edd3d5abe85550adba6630bb Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Thu, 28 Aug 2025 20:24:52 -0600 Subject: [PATCH 31/48] Implementation of flight above build limit for long distances --- src/api/java/baritone/api/Settings.java | 10 + .../elytra/IElytraPathfinderContext.java | 3 - .../java/baritone/process/ElytraProcess.java | 43 +- .../process/elytra/ElytraBehavior.java | 8 +- .../elytra/NetherPathfinderContext.java | 5 +- .../NetherPathfinderContextFactory.java | 1 - .../process/elytra/SkyPathfinderContext.java | 412 ++++++++++++++++++ .../elytra/SkyPathfinderContextFactory.java | 37 ++ 8 files changed, 495 insertions(+), 24 deletions(-) create mode 100644 src/main/java/baritone/process/elytra/SkyPathfinderContext.java create mode 100644 src/main/java/baritone/process/elytra/SkyPathfinderContextFactory.java diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index ca839fb8e..aae12889b 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -1563,6 +1563,16 @@ public final class Settings { */ public final Setting elytraUseCache = new Setting<>(true); + /** + * Allow the pathfinder to fly above the build limit in the overworld and end. + */ + public final Setting elytraAllowAboveBuildLimit = new Setting<>(true); + + /** + * Minimum distance in blocks of an elytra trip before the pathfinder will try to fly above build limit. (Minimum: 32). Requires {@link #elytraAllowAboveBuildLimit} to be enabled. + */ + public final Setting elytraLongDistanceThreshold = new Setting<>(500); + /** * A map of lowercase setting field names to their respective setting */ diff --git a/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java b/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java index d534465da..99085f4f1 100644 --- a/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java +++ b/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java @@ -35,13 +35,10 @@ public boolean raytrace(final double startX, final double startY, final double s final double endX, final double endY, final double endZ); public boolean raytrace(final Vec3 start, final Vec3 end); public boolean raytrace(final int count, final double[] src, final double[] dst, final int visibility); - public void raytrace(final int count, final double[] src, final double[] dst, final boolean[] hitsOut, final double[] hitPosOut); public void cancel(); public void destroy(); public long getSeed(); - - // zi: It appears only the read lock is used outside of the context public void RLock(); public void RUnlock(); diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 9588b9d9a..bb11aa63e 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -39,10 +39,7 @@ import baritone.api.utils.input.Input; import baritone.pathing.movement.CalculationContext; import baritone.pathing.movement.movements.MovementFall; -import baritone.process.elytra.ElytraBehavior; -import baritone.process.elytra.NetherPathfinderContext; -import baritone.process.elytra.NetherPathfinderContextFactory; -import baritone.process.elytra.NullElytraProcess; +import baritone.process.elytra.*; import baritone.utils.BaritoneProcessHelper; import baritone.utils.PathingCommandContext; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -73,7 +70,8 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private ElytraBehavior behavior; private boolean predictingTerrain; private boolean allowTight; - private IElytraContextFactory contextFactory = new NetherPathfinderContextFactory(); + private boolean allowAboveBuildLimit; + private IElytraContextFactory contextFactory; @Override public void onLostControl() { @@ -130,6 +128,11 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { allowTight = Baritone.settings().elytraAllowTightSpaces.value; this.resetState(); } + if (allowAboveBuildLimit != Baritone.settings().elytraAllowAboveBuildLimit.value) { + logDirect("elytraAllowAboveBuildLimit setting changed, recalculating path from scratch"); + allowAboveBuildLimit = Baritone.settings().elytraAllowAboveBuildLimit.value; + this.resetState(); + } this.behavior.onTick(); @@ -344,7 +347,8 @@ private void pathTo0(BlockPos destination, boolean appendDestination) { this.onLostControl(); this.predictingTerrain = ctx.player().level.dimension() == Level.NETHER && Baritone.settings().elytraPredictTerrain.value; this.allowTight = Baritone.settings().elytraAllowTightSpaces.value; - this.behavior = new ElytraBehavior(this.baritone, this, this.contextFactory.create(ctx, baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache")), destination, appendDestination); + this.allowAboveBuildLimit = Baritone.settings().elytraAllowAboveBuildLimit.value; + this.behavior = new ElytraBehavior(this.baritone, this, getContextFactory().create(ctx, baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache")), destination, appendDestination); if (ctx.world() != null) { this.behavior.repackChunks(); } @@ -371,15 +375,25 @@ public void pathTo(Goal iGoal) { throw new IllegalArgumentException("The goal must be a GoalXZ or GoalBlock"); } - int minY = ctx.world().dimensionType().minY(); - int maxY = (ctx.world().dimension() == Level.NETHER && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : Math.min(minY + 384, ctx.world().dimensionType().height() + minY); - if (y < minY || y >= maxY) { - throw new IllegalArgumentException("The goal must have a y value between " + minY + " and " + maxY); + int minY = ctx.world().getMinBuildHeight(); + int maxPathfinderY = minY + 384; // maximum supported world size by the nether-pathfinder library + + final boolean inNether = ctx.player().level.dimension() == Level.NETHER; + final boolean allowRoof = Baritone.settings().elytraAllowAboveRoof.value; + final boolean allowBuildLimit = Baritone.settings().elytraAllowAboveBuildLimit.value; + + if(y < minY) { + throw new IllegalArgumentException("The goal must have a y value greater than " + (minY - 1)); + } + + if(inNether) { + if(!allowRoof && y > 127) { + throw new IllegalArgumentException("The goal must have a y value less than 128 in the nether when #elytraAllowAboveRoof is false"); + } } - int playerY = (int)ctx.player().getY(); - if (playerY < minY || playerY >= maxY) { - throw new IllegalArgumentException("The player must have a y value between " + minY + " and " + maxY); + if(!allowBuildLimit && y > maxPathfinderY) { + throw new IllegalArgumentException("The goal must have a y value less than " + maxPathfinderY + " when #elytraAllowAboveBuildLimit is false"); } this.pathTo(new BlockPos(x, y, z)); @@ -412,6 +426,9 @@ public boolean isLoaded() { @Override public IElytraContextFactory getContextFactory() { + if(this.contextFactory == null) { + return Baritone.settings().elytraAllowAboveBuildLimit.value ? new SkyPathfinderContextFactory() : new NetherPathfinderContextFactory(); + } return this.contextFactory; } diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 65a294c20..ba865a9a8 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -159,9 +159,7 @@ public void tick() { this.ticksNearUnchanged = 0; } - int minY = ctx.world().dimensionType().minY(); - int y = ctx.playerFeet().y; - if (y >= minY && y < minY + context.getMaxHeight()) { + if(ctx.playerFeet().y >= ctx.world().dimensionType().minY()) { // Obstacles are more important than an incomplete path, handle those first. this.pathfindAroundObstacles(); this.attemptNextSegment(); @@ -374,7 +372,9 @@ private void attemptNextSegment() { } final int last = this.path.size() - 1; - if (!this.completePath && ctx.world().isLoaded(this.path.get(last))) { + final BetterBlockPos lastPos = this.path.get(this.path.size() - 1); + // `ctx.world().isLoaded` cannot be used here as it returns false is the y-value is beyond the build limits. + if (!this.completePath && ctx.world().getChunkSource().hasChunk(lastPos.x >> 4,lastPos.z >> 4)) { this.pathNextSegment(last); } } diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 2b494b8db..5612e267c 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -35,7 +35,6 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.PalettedContainer; -import net.minecraft.world.level.material.Material; import net.minecraft.world.phys.Vec3; import sun.misc.Unsafe; @@ -72,7 +71,7 @@ public final class NetherPathfinderContext implements IElytraPathfinderContext { public final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public final ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); public final ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); - public final int maxHeight; + private final int maxHeight; // Visible for access in BlockStateOctreeInterface final long context; @@ -173,7 +172,7 @@ public CompletableFuture pathFindAsync(final BlockPos src, fina false, // refine 10000, // timeoutMs !generate, // useAirIfChunkNotLoaded - // TODO: Determine appropiate cost value + // TODO: Determine appropriate cost value 8.0 // fakeChunkCost ); if (segment == null) { diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java b/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java index 495b75b84..f3c1b9e1f 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java @@ -19,7 +19,6 @@ import baritone.Baritone; import baritone.api.pathing.elytra.IElytraContextFactory; -import baritone.api.pathing.elytra.IElytraPathfinderContext; import baritone.api.utils.IPlayerContext; import java.nio.file.Path; diff --git a/src/main/java/baritone/process/elytra/SkyPathfinderContext.java b/src/main/java/baritone/process/elytra/SkyPathfinderContext.java new file mode 100644 index 000000000..379c400a1 --- /dev/null +++ b/src/main/java/baritone/process/elytra/SkyPathfinderContext.java @@ -0,0 +1,412 @@ +/* + * 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.process.elytra; + +import baritone.Baritone; +import baritone.api.event.events.BlockChangeEvent; +import baritone.api.pathing.elytra.IElytraPathfinderContext; +import baritone.api.pathing.elytra.UnpackedSegment; +import baritone.api.utils.BetterBlockPos; +import baritone.api.utils.IPlayerContext; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.util.Tuple; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.level.levelgen.Heightmap; + +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class SkyPathfinderContext implements IElytraPathfinderContext { + NetherPathfinderContext netherCtx; + IPlayerContext playerCtx; + final int flightLevel; + + public SkyPathfinderContext(IPlayerContext ctx, Path cacheDir) { + if (ctx == null) { + throw new IllegalArgumentException("IPlayerContext cannot be null"); + } + + this.netherCtx = new NetherPathfinderContextFactory().create(ctx, cacheDir); + this.playerCtx = ctx; + this.flightLevel = playerCtx.world().getMaxBuildHeight() + 16; + + if(netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight() < playerCtx.world().getMaxBuildHeight()) { + throw new IllegalStateException("Nether pathfinder max height is below world build limit, cannot proceed"); + } + } + + @Override + public boolean hasChunk(ChunkPos pos) { + return netherCtx.hasChunk(pos); + } + + @Override + public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks) { + netherCtx.queueCacheCulling(chunkX, chunkZ, maxDistanceBlocks); + } + + @Override + public void queueForPacking(LevelChunk chunkIn) { + netherCtx.queueForPacking(chunkIn); + } + + @Override + public void queueBlockUpdate(BlockChangeEvent event) { + netherCtx.queueBlockUpdate(event); + } + + + /** + * Generates a direct path from the start to the destination at a fixed y-level above build limit + * @param start + * @param destination + * @param bufferDistance Distance from the destination to halt the direct path + * @param maxPathSize Maximum number of nodes in the returned path + * @return A tuple containing the path as a list of BetterBlockPos and a boolean indicating if the path is complete + */ + public Tuple, Boolean> GenerateDirectPath(BetterBlockPos start, BetterBlockPos destination, int bufferDistance, int maxPathSize) { + final LinkedList path = new LinkedList<>(); + final int stepDistance = 32; + + start = start.y == flightLevel ? start : new BetterBlockPos(start.getX(), flightLevel, start.getZ()); + destination = destination.y == flightLevel ? destination : new BetterBlockPos(destination.getX(), flightLevel, destination.getZ()); + + BetterBlockPos cur = start; + path.add(cur); + + while (path.size() < maxPathSize) { + double deltaX = destination.getX() - cur.getX(); + double deltaZ = destination.getZ() - cur.getZ(); + double remainingDistance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ); + double remainingDistanceSq = deltaX * deltaX + deltaZ * deltaZ; + + if(remainingDistanceSq <= bufferDistance * bufferDistance) { + // We are within the buffer distance, so we can stop here + return new Tuple<>(path, true); + } else if (remainingDistance <= stepDistance) { + path.add(destination); + return new Tuple<>(path, true); + } + + double stepRatio = stepDistance / remainingDistance; + int nextX = (int) Math.round(cur.getX() + deltaX * stepRatio); + int nextZ = (int) Math.round(cur.getZ() + deltaZ * stepRatio); + + cur = new BetterBlockPos(nextX, flightLevel, nextZ); + path.add(cur); + } + + return new Tuple<>(path, false); + } + + /** + * Attempts to find an open spot in the sky to transition up above the build limit so a simple direct path can be followed + * @param start + * @param destination + * @return A tuple containing the path that transitions above build limit and a boolean indicating if a transition was found + */ + public Tuple,Boolean> GenerateTransitionUp(BetterBlockPos start, BetterBlockPos destination) { + final double deltaX = destination.getX() - start.getX(); + final double deltaZ = destination.getZ() - start.getZ(); + final double distance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ); + + final double scale = 8 / distance; + final double stepX = deltaX * scale; + final double stepZ = deltaZ * scale; + + final int netherMaxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight() - 1; + + final ChunkPos startChunk = new ChunkPos(start.x >> 4, start.z >> 4); + + if(!isSkyClear(startChunk, start.y)) { + return new Tuple<>(new LinkedList<>(), false); + } + + LinkedList path = new LinkedList<>(); + + // Start with the middle block so the transition doesn't leave the only chunk we can confirm is clear + final BlockPos middlePos = startChunk.getMiddleBlockPosition(netherMaxHeight+4); + + for(int i = 2; i <= 2; i++) { + BetterBlockPos next = new BetterBlockPos( + (int) (middlePos.getX() + (stepX * i)), + netherMaxHeight + (i * 8), + (int) (middlePos.getZ() + (stepZ * i)) + ); + path.add(next); + } + + return new Tuple<>(path, true); + } + + /** + * Attempts to find an open spot in the sky to transition down to a flight level the nether pathfinder can navigiate at. + * @param start + * @return A tuple containing the path (single point) and a boolean indicating if a transition point was found + */ + public Tuple,Boolean> GenerateTransitionDown(BetterBlockPos start) { + final int netherMaxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight() - 1; + final ChunkPos startChunk = new ChunkPos(start.x >> 4, start.z >> 4); + + LinkedList path = new LinkedList<>(); + + if(!isSkyClear(new ChunkPos(start.x >> 4, start.z >> 4), netherMaxHeight-16)) { + return new Tuple<>(new LinkedList<>(), false); + } + + path.add(new BetterBlockPos(startChunk.getMiddleBlockPosition(netherMaxHeight-8))); + return new Tuple<>(path, true); + } + + public boolean isSkyClear(ChunkPos pos, int y) { + if(!playerCtx.world().getChunkSource().hasChunk(pos.x, pos.z)) { + return false; + } + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + BlockPos blockPos = pos.getBlockAt(x, y, z); + int height = playerCtx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, blockPos.getX(), blockPos.getZ()); + if (height > y) { + return false; + } + } + } + return true; + } + + + @Override + public CompletableFuture pathFindAsync(BlockPos src, BlockPos dst) { + final int netherMaxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight() - 1; + final int maxDirectPathSize = 500; + + // There can be some navigation issues around failed transitions if the threshold distance isn't large enough + final double maxDistance = Baritone.settings().elytraLongDistanceThreshold.value >= 32 ? (double) Baritone.settings().elytraLongDistanceThreshold.value : Double.POSITIVE_INFINITY; + + final double distanceXZ = src.distSqr(new Vec3i(dst.getX(), src.getY(), dst.getZ())); + final boolean isLongDistance = distanceXZ > maxDistance * maxDistance; + final boolean srcAboveSupportedHeight = src.getY() >= netherMaxHeight; + final boolean dstAboveSupportedHeight = dst.getY() >= netherMaxHeight; + + if(srcAboveSupportedHeight && dstAboveSupportedHeight) { + var path = GenerateDirectPath(new BetterBlockPos(src), new BetterBlockPos(dst), 0, maxDirectPathSize); + return CompletableFuture.completedFuture(new UnpackedSegment(path.getA().stream(), path.getB())); + } + + if(isLongDistance) { + if(srcAboveSupportedHeight) { + var directPath = GenerateDirectPath(new BetterBlockPos(src), new BetterBlockPos(dst), (int)maxDistance, maxDirectPathSize); + return CompletableFuture.completedFuture( + new UnpackedSegment( + directPath.getA().stream(), + dstAboveSupportedHeight ? directPath.getB() : false + ) + ); + } else { + var transition = GenerateTransitionUp(new BetterBlockPos(src), new BetterBlockPos(dst)); + var path = transition.getA(); + var success = transition.getB(); + + if(success) { + var directPath = GenerateDirectPath(path.get(path.size()-1), new BetterBlockPos(dst), (int)maxDistance, maxDirectPathSize); + path.addAll(directPath.getA()); + + return CompletableFuture.completedFuture( + new UnpackedSegment( + path.stream(), + dstAboveSupportedHeight? directPath.getB() : false + ) + ); + } + } + + // Failed to find a transition point so navigate a bit in the right direction and try + final double deltaX = dst.getX() - src.getX(); + final double deltaZ = dst.getZ() - src.getZ(); + final double scale = (maxDistance/2) / Math.sqrt(deltaX * deltaX + deltaZ * deltaZ); + final double stepX = deltaX * scale; + final double stepZ = deltaZ * scale; + final BlockPos midDst = new BlockPos((int)(src.getX() + stepX), netherMaxHeight, (int)(src.getZ() + stepZ)); + + return incompletePathfind(src, midDst); + } else { + if(srcAboveSupportedHeight) { + var transition = GenerateTransitionDown(new BetterBlockPos(src)); + List path = transition.getA(); + boolean success = transition.getB(); + + if(!success) { + BetterBlockPos newDest = distanceXZ > 32 ? new BetterBlockPos(dst) : new BetterBlockPos(dst.getX(), playerCtx.world().getMaxBuildHeight(), dst.getZ()); + var directPath = GenerateDirectPath(new BetterBlockPos(src), newDest, 0, 2); + return CompletableFuture.completedFuture(new UnpackedSegment(directPath.getA().stream(), directPath.getB())); + } + + return CompletableFuture.supplyAsync(() -> { + try { + var np = netherCtx.pathFindAsync(path.get(path.size() - 1), dst).get(); + path.addAll(np.collect()); + return new UnpackedSegment(path.stream(), np.isFinished()); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }); + } + + + if(dstAboveSupportedHeight) { + var transition = GenerateTransitionUp(new BetterBlockPos(src), new BetterBlockPos(dst)); + var path = transition.getA(); + var success = transition.getB(); + + if(success) { + var directPath = GenerateDirectPath(path.get(path.size() - 1), new BetterBlockPos(dst), 0, maxDirectPathSize); + path.addAll(directPath.getA()); + return CompletableFuture.completedFuture(new UnpackedSegment(path.stream(), directPath.getB())); + } + + return netherCtx.pathFindAsync(src, new BetterBlockPos(dst.getX(), netherMaxHeight, dst.getZ())); + } + + return netherCtx.pathFindAsync(src, dst); + } + } + + /** + * A wrapper for a nether pathfinder call but the returned path will always indicate it is incomplete + * @param src + * @param dst + * @return a CompletableFuture containing an UnpackedSegment with isFinished always false + */ + private CompletableFuture incompletePathfind(BlockPos src, BlockPos dst) { + return CompletableFuture.supplyAsync(() -> { + UnpackedSegment packed; + try { + packed = netherCtx.pathFindAsync(src, dst).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return new UnpackedSegment( + packed.collect().stream(), + false + ); + }); + } + + @Override + public boolean raytrace(double startX, double startY, double startZ, double endX, double endY, double endZ) { + final int maxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight(); + final int minHeight = playerCtx.world().getMinBuildHeight(); + final boolean isOOB = startY >= maxHeight || endY >= maxHeight || startY < minHeight || endY < minHeight; + if (isOOB) { + Vec3 start = new Vec3(startX, startY, startZ); + Vec3 end = new Vec3(endX, endY, endZ); + return playerCtx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, playerCtx.player())).getType() == HitResult.Type.MISS; + } + + return netherCtx.raytrace(startX, startY, startZ, endX, endY, endZ); + } + + @Override + public boolean raytrace(Vec3 start, Vec3 end) { + final int maxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight(); + final int minHeight = playerCtx.world().getMinBuildHeight(); + final boolean isOOB = start.y >= maxHeight || end.y >= maxHeight || start.y < minHeight || end.y < minHeight; + if (isOOB) { + return playerCtx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, playerCtx.player())).getType() == HitResult.Type.MISS; + } + return netherCtx.raytrace(start.x, start.y, start.z, end.x, end.y, end.z); + } + + + @Override + public boolean raytrace(int count, double[] src, double[] dst, int visibility) { + if (src.length != count * 3 || src.length != dst.length) { + throw new IllegalArgumentException("Expected source and dst to have length of " + (count * 3)); + } + final int maxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight(); + + boolean isOOB = false; + for(int i = 1; i < src.length; i += 3) { + if (src[i] >= maxHeight || src[i] < playerCtx.world().getMinBuildHeight() || + dst[i] >= maxHeight || dst[i] < playerCtx.world().getMinBuildHeight()) { + isOOB = true; + break; + } + } + + if(isOOB) { + for (int i = 0; i < count; i++) { + Vec3 start = new Vec3(src[i * 3], src[i * 3 + 1], src[i * 3 + 2]); + Vec3 end = new Vec3(dst[i * 3], dst[i * 3 + 1], dst[i * 3 + 2]); + if (playerCtx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, playerCtx.player())).getType() != HitResult.Type.MISS) { + return false; + } + } + return true; + } + + return netherCtx.raytrace(count, src, dst, visibility); + } + + @Override + public void cancel() { + netherCtx.cancel(); + } + + @Override + public void destroy() { + netherCtx.destroy(); + } + + @Override + public long getSeed() { + return netherCtx.getSeed(); + } + + @Override + public void RLock() { + netherCtx.RLock(); + } + + @Override + public void RUnlock() { + netherCtx.RUnlock(); + } + + @Override + public int getMaxHeight() { + return netherCtx.getMaxHeight(); + } + + @Override + public boolean passable(int x, int y, int z) { + if(y >= playerCtx.world().getMaxBuildHeight() || y < playerCtx.world().getMinBuildHeight()) { + return true; + } + return netherCtx.passable(x, y, z); + } +} diff --git a/src/main/java/baritone/process/elytra/SkyPathfinderContextFactory.java b/src/main/java/baritone/process/elytra/SkyPathfinderContextFactory.java new file mode 100644 index 000000000..4da4bc753 --- /dev/null +++ b/src/main/java/baritone/process/elytra/SkyPathfinderContextFactory.java @@ -0,0 +1,37 @@ +/* + * 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.process.elytra; + +import baritone.api.pathing.elytra.IElytraContextFactory; +import baritone.api.utils.IPlayerContext; + +import java.nio.file.Path; + +public class SkyPathfinderContextFactory implements IElytraContextFactory { + + @Override + public SkyPathfinderContext create(IPlayerContext ctx, Path cacheDir) { + return new SkyPathfinderContext(ctx, cacheDir); + } + + @Override + public String toString() { + return "CustomPathfinderContextFactory{}"; + } + +} From 6b3e5f3567c19c222a3058df6faf9da67e09ca7b Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Thu, 28 Aug 2025 21:21:13 -0600 Subject: [PATCH 32/48] Don't use the Sky pathfinder when in the nether and no roof allowed --- src/main/java/baritone/process/ElytraProcess.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index bb11aa63e..fa946f684 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -71,6 +71,7 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private boolean predictingTerrain; private boolean allowTight; private boolean allowAboveBuildLimit; + private boolean allowAboveRoof; private IElytraContextFactory contextFactory; @Override @@ -133,6 +134,11 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { allowAboveBuildLimit = Baritone.settings().elytraAllowAboveBuildLimit.value; this.resetState(); } + if (allowAboveRoof != Baritone.settings().elytraAllowAboveRoof.value && ctx.player().level.dimension() == Level.NETHER) { + logDirect("elytraAllowAboveRoof setting changed, recalculating path from scratch"); + allowAboveRoof = Baritone.settings().elytraAllowAboveRoof.value; + this.resetState(); + } this.behavior.onTick(); @@ -348,6 +354,7 @@ private void pathTo0(BlockPos destination, boolean appendDestination) { this.predictingTerrain = ctx.player().level.dimension() == Level.NETHER && Baritone.settings().elytraPredictTerrain.value; this.allowTight = Baritone.settings().elytraAllowTightSpaces.value; this.allowAboveBuildLimit = Baritone.settings().elytraAllowAboveBuildLimit.value; + this.allowAboveRoof = Baritone.settings().elytraAllowAboveRoof.value; this.behavior = new ElytraBehavior(this.baritone, this, getContextFactory().create(ctx, baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache")), destination, appendDestination); if (ctx.world() != null) { this.behavior.repackChunks(); @@ -427,6 +434,11 @@ public boolean isLoaded() { @Override public IElytraContextFactory getContextFactory() { if(this.contextFactory == null) { + if(ctx.world().dimension() == Level.NETHER) { + return Baritone.settings().elytraAllowAboveRoof.value && Baritone.settings().elytraAllowAboveBuildLimit.value + ? new SkyPathfinderContextFactory() + : new NetherPathfinderContextFactory(); + } return Baritone.settings().elytraAllowAboveBuildLimit.value ? new SkyPathfinderContextFactory() : new NetherPathfinderContextFactory(); } return this.contextFactory; From 248e6445c20b6ec7d1fd811e1283642b779d16b2 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Fri, 29 Aug 2025 17:35:11 -0600 Subject: [PATCH 33/48] Prevent races between destruction and creation of ElytraBehavior --- src/main/java/baritone/process/ElytraProcess.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index fa946f684..14277b530 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -58,6 +58,7 @@ import net.minecraft.world.phys.Vec3; import java.util.*; +import java.util.concurrent.Semaphore; import static baritone.api.pathing.movement.ActionCosts.COST_INF; @@ -73,6 +74,7 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private boolean allowAboveBuildLimit; private boolean allowAboveRoof; private IElytraContextFactory contextFactory; + private final Semaphore behaviorSema = new Semaphore(1); @Override public void onLostControl() { @@ -315,7 +317,10 @@ private void destroyBehaviorAsync() { ElytraBehavior behavior = this.behavior; if (behavior != null) { this.behavior = null; - Baritone.getExecutor().execute(behavior::destroy); + Baritone.getExecutor().execute(() -> { + behavior.destroy(); + behaviorSema.release(); + }); } } @@ -355,7 +360,10 @@ private void pathTo0(BlockPos destination, boolean appendDestination) { this.allowTight = Baritone.settings().elytraAllowTightSpaces.value; this.allowAboveBuildLimit = Baritone.settings().elytraAllowAboveBuildLimit.value; this.allowAboveRoof = Baritone.settings().elytraAllowAboveRoof.value; + + this.behaviorSema.acquireUninterruptibly(); this.behavior = new ElytraBehavior(this.baritone, this, getContextFactory().create(ctx, baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache")), destination, appendDestination); + if (ctx.world() != null) { this.behavior.repackChunks(); } From 94a466be9018e87e3f55e9e5148cc32b4ed5e608 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Fri, 29 Aug 2025 18:09:56 -0600 Subject: [PATCH 34/48] Prevent deadlock when rapidly changing goals and the solver never gets executed/finishes by adding a timeout --- src/main/java/baritone/process/elytra/ElytraBehavior.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index ba865a9a8..26baaca18 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -530,7 +530,7 @@ private void onTick0() { this.pendingSolution = null; if (this.solver != null) { try { - this.pendingSolution = this.solver.get(); + this.pendingSolution = this.solver.get(50 * 5, TimeUnit.MILLISECONDS); } catch (Exception ignored) { // it doesn't matter if get() fails since the solution can just be recalculated synchronously } finally { From 9dce625c6a3beecf7c7d97f277afcfa499c2e638 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Sun, 7 Sep 2025 21:56:50 -0600 Subject: [PATCH 35/48] Simplify Elytra pathing interface and pull it back out of the public API --- .../elytra/IElytraPathfinderContext.java | 56 ------ .../baritone/api/process/IElytraProcess.java | 6 - .../java/baritone/process/ElytraProcess.java | 69 +++----- ...Context.java => BuildLimitPathFinder.java} | 163 +++--------------- .../process/elytra/ElytraBehavior.java | 131 ++++++++++---- .../process/elytra/IElytraPathFinder.java} | 10 +- .../elytra/NetherPathfinderContext.java | 11 +- .../NetherPathfinderContextFactory.java | 42 ----- .../process/elytra/NullElytraProcess.java | 11 -- .../elytra/SkyPathfinderContextFactory.java | 37 ---- .../process}/elytra/UnpackedSegment.java | 2 +- 11 files changed, 159 insertions(+), 379 deletions(-) delete mode 100644 src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java rename src/main/java/baritone/process/elytra/{SkyPathfinderContext.java => BuildLimitPathFinder.java} (68%) rename src/{api/java/baritone/api/pathing/elytra/IElytraContextFactory.java => main/java/baritone/process/elytra/IElytraPathFinder.java} (74%) delete mode 100644 src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java delete mode 100644 src/main/java/baritone/process/elytra/SkyPathfinderContextFactory.java rename src/{api/java/baritone/api/pathing => main/java/baritone/process}/elytra/UnpackedSegment.java (98%) diff --git a/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java b/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java deleted file mode 100644 index 99085f4f1..000000000 --- a/src/api/java/baritone/api/pathing/elytra/IElytraPathfinderContext.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.api.pathing.elytra; - -import baritone.api.event.events.BlockChangeEvent; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.phys.Vec3; - -import java.util.concurrent.CompletableFuture; - -public interface IElytraPathfinderContext { - public boolean hasChunk(ChunkPos pos); - public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks); - public void queueForPacking(final LevelChunk chunkIn); - public void queueBlockUpdate(BlockChangeEvent event); - public CompletableFuture pathFindAsync(final BlockPos src, final BlockPos dst); - public boolean raytrace(final double startX, final double startY, final double startZ, - final double endX, final double endY, final double endZ); - public boolean raytrace(final Vec3 start, final Vec3 end); - public boolean raytrace(final int count, final double[] src, final double[] dst, final int visibility); - public void cancel(); - public void destroy(); - public long getSeed(); - - public void RLock(); - public void RUnlock(); - - public int getMaxHeight(); - public boolean passable(int x, int y, int z); - - public static final class Visibility { - public static final int ALL = 0; - public static final int NONE = 1; - public static final int ANY = 2; - private Visibility() {} - } -} - - diff --git a/src/api/java/baritone/api/process/IElytraProcess.java b/src/api/java/baritone/api/process/IElytraProcess.java index 9e0532999..28328f901 100644 --- a/src/api/java/baritone/api/process/IElytraProcess.java +++ b/src/api/java/baritone/api/process/IElytraProcess.java @@ -17,7 +17,6 @@ package baritone.api.process; -import baritone.api.pathing.elytra.IElytraContextFactory; import baritone.api.pathing.goals.Goal; import net.minecraft.core.BlockPos; @@ -44,11 +43,6 @@ public interface IElytraProcess extends IBaritoneProcess { */ boolean isLoaded(); - - IElytraContextFactory getContextFactory(); - void setContextFactory(IElytraContextFactory factory); - - /* * FOR INTERNAL USE ONLY. MAY BE REMOVED AT ANY TIME. */ diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 9677b5771..e618ced5b 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -22,7 +22,6 @@ import baritone.api.event.events.*; import baritone.api.event.events.type.EventState; import baritone.api.event.listener.AbstractGameEventListener; -import baritone.api.pathing.elytra.IElytraContextFactory; import baritone.api.pathing.goals.Goal; import baritone.api.pathing.goals.GoalBlock; import baritone.api.pathing.goals.GoalXZ; @@ -74,7 +73,6 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private boolean allowTight; private boolean allowAboveBuildLimit; private boolean allowAboveRoof; - private IElytraContextFactory contextFactory; private final Semaphore behaviorSema = new Semaphore(1); @Override @@ -119,7 +117,7 @@ public void resetState() { public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { try { final long seedSetting = Baritone.settings().elytraNetherSeed.value; - if (seedSetting != this.behavior.context.getSeed()) { + if (seedSetting != this.behavior.npfContext.getSeed()) { logDirect("Nether seed changed, recalculating path"); this.resetState(); } @@ -217,11 +215,11 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { behavior.landingMode = this.state == State.LANDING; this.goal = null; baritone.getInputOverrideHandler().clearAllKeys(); - behavior.context.RLock(); + this.behavior.npfContext.RLock(); try { behavior.tick(); } finally { - behavior.context.RUnlock(); + this.behavior.npfContext.RUnlock(); } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } else if (this.state == State.LANDING) { @@ -354,15 +352,12 @@ public BlockPos currentDestination() { @Override public void pathTo(BlockPos destination) { - int minY = ctx.world().dimensionType().minY(); - int maxY = (ctx.world().dimension() == Level.NETHER && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : Math.min(minY + 384, ctx.world().dimensionType().height() + minY); - if (destination.getY() < minY || destination.getY() >= maxY) { - throw new IllegalArgumentException("The goal must have a y value between " + minY + " and " + maxY); + if (!isSupportedPos(destination)) { + throw new IllegalArgumentException("The goal must be within bounds to use elytra flight."); } - int playerY = (int)ctx.player().getY(); - if (playerY < minY || playerY >= maxY) { - throw new IllegalArgumentException("The player must have a y value between " + minY + " and " + maxY); + if (ctx.player() != null && !isSupportedPos(ctx.playerFeet())) { + throw new IllegalArgumentException("The player must be within bounds to use elytra flight."); } this.pathTo0(destination, false); @@ -379,7 +374,7 @@ private void pathTo0(BlockPos destination, boolean appendDestination) { this.allowAboveRoof = Baritone.settings().elytraAllowAboveRoof.value; this.behaviorSema.acquireUninterruptibly(); - this.behavior = new ElytraBehavior(this.baritone, this, getContextFactory().create(ctx, baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache")), destination, appendDestination); + this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination); if (ctx.world() != null) { this.behavior.repackChunks(); @@ -407,28 +402,24 @@ public void pathTo(Goal iGoal) { throw new IllegalArgumentException("The goal must be a GoalXZ or GoalBlock"); } - int minY = ctx.world().getMinBuildHeight(); - int maxPathfinderY = minY + 384; // maximum supported world size by the nether-pathfinder library + this.pathTo((new BlockPos(x, y, z))); + } - final boolean inNether = ctx.player().level.dimension() == Level.NETHER; - final boolean allowRoof = Baritone.settings().elytraAllowAboveRoof.value; - final boolean allowBuildLimit = Baritone.settings().elytraAllowAboveBuildLimit.value; + private boolean isSupportedPos(BlockPos pos) { + final boolean isNether = ctx.world().dimension() == Level.NETHER; + final int minY = ctx.world().dimensionType().minY(); + final int maxY = (isNether && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : Math.min(minY + 384, ctx.world().dimensionType().height() + minY); - if(y < minY) { - throw new IllegalArgumentException("The goal must have a y value greater than " + (minY - 1)); - } + final boolean aboveRoof = Baritone.settings().elytraAllowAboveRoof.value; + final boolean aboveBuild = Baritone.settings().elytraAllowAboveBuildLimit.value; - if(inNether) { - if(!allowRoof && y > 127) { - throw new IllegalArgumentException("The goal must have a y value less than 128 in the nether when #elytraAllowAboveRoof is false"); - } - } + final boolean enforceMaxY = isNether ? !(aboveRoof && aboveBuild) : !aboveBuild; - if(!allowBuildLimit && y > maxPathfinderY) { - throw new IllegalArgumentException("The goal must have a y value less than " + maxPathfinderY + " when #elytraAllowAboveBuildLimit is false"); + if (pos.getY() < minY) { + return false; } - this.pathTo(new BlockPos(x, y, z)); + return !enforceMaxY || pos.getY() < maxY; } private boolean shouldLandForSafety() { @@ -456,24 +447,6 @@ public boolean isLoaded() { return true; } - @Override - public IElytraContextFactory getContextFactory() { - if(this.contextFactory == null) { - if(ctx.world().dimension() == Level.NETHER) { - return Baritone.settings().elytraAllowAboveRoof.value && Baritone.settings().elytraAllowAboveBuildLimit.value - ? new SkyPathfinderContextFactory() - : new NetherPathfinderContextFactory(); - } - return Baritone.settings().elytraAllowAboveBuildLimit.value ? new SkyPathfinderContextFactory() : new NetherPathfinderContextFactory(); - } - return this.contextFactory; - } - - @Override - public void setContextFactory(IElytraContextFactory factory) { - this.contextFactory = factory == null ? new NetherPathfinderContextFactory() : factory; - } - @Override public boolean isSafeToCancel() { return !this.isActive() || !(this.state == State.FLYING || this.state == State.START_FLYING); @@ -691,7 +664,7 @@ private BetterBlockPos findSafeLandingSpot_heightmap(BetterBlockPos start) { var height = ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, qPos.getX(), qPos.getZ()); var pos = new BetterBlockPos(qPos.getX(), height+1, qPos.getZ()); - if (ctx.world().isLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { + if (isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions); if(actualLandingSpot != null) { landingColumnHeight = ctx.playerFeet().y - actualLandingSpot.y < LONG_LANDING_COLUMN_HEIGHT ? SHORT_LANDING_COLUMN_HEIGHT : LONG_LANDING_COLUMN_HEIGHT; diff --git a/src/main/java/baritone/process/elytra/SkyPathfinderContext.java b/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java similarity index 68% rename from src/main/java/baritone/process/elytra/SkyPathfinderContext.java rename to src/main/java/baritone/process/elytra/BuildLimitPathFinder.java index 379c400a1..5bb2507ae 100644 --- a/src/main/java/baritone/process/elytra/SkyPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java @@ -18,67 +18,42 @@ package baritone.process.elytra; import baritone.Baritone; -import baritone.api.event.events.BlockChangeEvent; -import baritone.api.pathing.elytra.IElytraPathfinderContext; -import baritone.api.pathing.elytra.UnpackedSegment; import baritone.api.utils.BetterBlockPos; import baritone.api.utils.IPlayerContext; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.util.Tuple; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.ClipContext; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.Vec3; import net.minecraft.world.level.levelgen.Heightmap; -import java.nio.file.Path; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -public class SkyPathfinderContext implements IElytraPathfinderContext { - NetherPathfinderContext netherCtx; - IPlayerContext playerCtx; +public class BuildLimitPathFinder implements IElytraPathFinder { final int flightLevel; + final IPlayerContext playerCtx; + final NetherPathfinderContext netherCtx; - public SkyPathfinderContext(IPlayerContext ctx, Path cacheDir) { + public BuildLimitPathFinder(IPlayerContext ctx, NetherPathfinderContext netherCtx) { if (ctx == null) { throw new IllegalArgumentException("IPlayerContext cannot be null"); } - - this.netherCtx = new NetherPathfinderContextFactory().create(ctx, cacheDir); this.playerCtx = ctx; - this.flightLevel = playerCtx.world().getMaxBuildHeight() + 16; - if(netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight() < playerCtx.world().getMaxBuildHeight()) { - throw new IllegalStateException("Nether pathfinder max height is below world build limit, cannot proceed"); + if (netherCtx == null) { + throw new IllegalArgumentException("NetherPathfinderContext cannot be null"); } - } - @Override - public boolean hasChunk(ChunkPos pos) { - return netherCtx.hasChunk(pos); - } - - @Override - public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks) { - netherCtx.queueCacheCulling(chunkX, chunkZ, maxDistanceBlocks); - } + this.flightLevel = ctx.world().getMaxBuildHeight() + 16; + this.netherCtx = netherCtx; - @Override - public void queueForPacking(LevelChunk chunkIn) { - netherCtx.queueForPacking(chunkIn); - } - - @Override - public void queueBlockUpdate(BlockChangeEvent event) { - netherCtx.queueBlockUpdate(event); + if(netherCtx.getMaxHeight() + ctx.world().getMinBuildHeight() < ctx.world().getMaxBuildHeight()) { + throw new IllegalStateException("Nether pathfinder max height is below world build limit, cannot proceed"); + } } - /** * Generates a direct path from the start to the destination at a fixed y-level above build limit * @param start @@ -87,7 +62,7 @@ public void queueBlockUpdate(BlockChangeEvent event) { * @param maxPathSize Maximum number of nodes in the returned path * @return A tuple containing the path as a list of BetterBlockPos and a boolean indicating if the path is complete */ - public Tuple, Boolean> GenerateDirectPath(BetterBlockPos start, BetterBlockPos destination, int bufferDistance, int maxPathSize) { + public Tuple, Boolean> generateDirectPath(BetterBlockPos start, BetterBlockPos destination, int bufferDistance, int maxPathSize) { final LinkedList path = new LinkedList<>(); final int stepDistance = 32; @@ -128,7 +103,7 @@ public Tuple, Boolean> GenerateDirectPath(BetterBlockPos st * @param destination * @return A tuple containing the path that transitions above build limit and a boolean indicating if a transition was found */ - public Tuple,Boolean> GenerateTransitionUp(BetterBlockPos start, BetterBlockPos destination) { + public Tuple,Boolean> generateTransitionUp(BetterBlockPos start, BetterBlockPos destination) { final double deltaX = destination.getX() - start.getX(); final double deltaZ = destination.getZ() - start.getZ(); final double distance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ); @@ -167,7 +142,7 @@ public Tuple,Boolean> GenerateTransitionUp(BetterBlockPos s * @param start * @return A tuple containing the path (single point) and a boolean indicating if a transition point was found */ - public Tuple,Boolean> GenerateTransitionDown(BetterBlockPos start) { + public Tuple,Boolean> generateTransitionDown(BetterBlockPos start) { final int netherMaxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight() - 1; final ChunkPos startChunk = new ChunkPos(start.x >> 4, start.z >> 4); @@ -199,7 +174,6 @@ public boolean isSkyClear(ChunkPos pos, int y) { } - @Override public CompletableFuture pathFindAsync(BlockPos src, BlockPos dst) { final int netherMaxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight() - 1; final int maxDirectPathSize = 500; @@ -213,13 +187,13 @@ public CompletableFuture pathFindAsync(BlockPos src, BlockPos d final boolean dstAboveSupportedHeight = dst.getY() >= netherMaxHeight; if(srcAboveSupportedHeight && dstAboveSupportedHeight) { - var path = GenerateDirectPath(new BetterBlockPos(src), new BetterBlockPos(dst), 0, maxDirectPathSize); + var path = generateDirectPath(new BetterBlockPos(src), new BetterBlockPos(dst), 0, maxDirectPathSize); return CompletableFuture.completedFuture(new UnpackedSegment(path.getA().stream(), path.getB())); } if(isLongDistance) { if(srcAboveSupportedHeight) { - var directPath = GenerateDirectPath(new BetterBlockPos(src), new BetterBlockPos(dst), (int)maxDistance, maxDirectPathSize); + var directPath = generateDirectPath(new BetterBlockPos(src), new BetterBlockPos(dst), (int)maxDistance, maxDirectPathSize); return CompletableFuture.completedFuture( new UnpackedSegment( directPath.getA().stream(), @@ -227,12 +201,12 @@ public CompletableFuture pathFindAsync(BlockPos src, BlockPos d ) ); } else { - var transition = GenerateTransitionUp(new BetterBlockPos(src), new BetterBlockPos(dst)); + var transition = generateTransitionUp(new BetterBlockPos(src), new BetterBlockPos(dst)); var path = transition.getA(); var success = transition.getB(); if(success) { - var directPath = GenerateDirectPath(path.get(path.size()-1), new BetterBlockPos(dst), (int)maxDistance, maxDirectPathSize); + var directPath = generateDirectPath(path.get(path.size()-1), new BetterBlockPos(dst), (int)maxDistance, maxDirectPathSize); path.addAll(directPath.getA()); return CompletableFuture.completedFuture( @@ -255,13 +229,13 @@ public CompletableFuture pathFindAsync(BlockPos src, BlockPos d return incompletePathfind(src, midDst); } else { if(srcAboveSupportedHeight) { - var transition = GenerateTransitionDown(new BetterBlockPos(src)); + var transition = generateTransitionDown(new BetterBlockPos(src)); List path = transition.getA(); boolean success = transition.getB(); if(!success) { BetterBlockPos newDest = distanceXZ > 32 ? new BetterBlockPos(dst) : new BetterBlockPos(dst.getX(), playerCtx.world().getMaxBuildHeight(), dst.getZ()); - var directPath = GenerateDirectPath(new BetterBlockPos(src), newDest, 0, 2); + var directPath = generateDirectPath(new BetterBlockPos(src), newDest, 0, 2); return CompletableFuture.completedFuture(new UnpackedSegment(directPath.getA().stream(), directPath.getB())); } @@ -278,12 +252,12 @@ public CompletableFuture pathFindAsync(BlockPos src, BlockPos d if(dstAboveSupportedHeight) { - var transition = GenerateTransitionUp(new BetterBlockPos(src), new BetterBlockPos(dst)); + var transition = generateTransitionUp(new BetterBlockPos(src), new BetterBlockPos(dst)); var path = transition.getA(); var success = transition.getB(); if(success) { - var directPath = GenerateDirectPath(path.get(path.size() - 1), new BetterBlockPos(dst), 0, maxDirectPathSize); + var directPath = generateDirectPath(path.get(path.size() - 1), new BetterBlockPos(dst), 0, maxDirectPathSize); path.addAll(directPath.getA()); return CompletableFuture.completedFuture(new UnpackedSegment(path.stream(), directPath.getB())); } @@ -316,97 +290,4 @@ private CompletableFuture incompletePathfind(BlockPos src, Bloc }); } - @Override - public boolean raytrace(double startX, double startY, double startZ, double endX, double endY, double endZ) { - final int maxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight(); - final int minHeight = playerCtx.world().getMinBuildHeight(); - final boolean isOOB = startY >= maxHeight || endY >= maxHeight || startY < minHeight || endY < minHeight; - if (isOOB) { - Vec3 start = new Vec3(startX, startY, startZ); - Vec3 end = new Vec3(endX, endY, endZ); - return playerCtx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, playerCtx.player())).getType() == HitResult.Type.MISS; - } - - return netherCtx.raytrace(startX, startY, startZ, endX, endY, endZ); - } - - @Override - public boolean raytrace(Vec3 start, Vec3 end) { - final int maxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight(); - final int minHeight = playerCtx.world().getMinBuildHeight(); - final boolean isOOB = start.y >= maxHeight || end.y >= maxHeight || start.y < minHeight || end.y < minHeight; - if (isOOB) { - return playerCtx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, playerCtx.player())).getType() == HitResult.Type.MISS; - } - return netherCtx.raytrace(start.x, start.y, start.z, end.x, end.y, end.z); - } - - - @Override - public boolean raytrace(int count, double[] src, double[] dst, int visibility) { - if (src.length != count * 3 || src.length != dst.length) { - throw new IllegalArgumentException("Expected source and dst to have length of " + (count * 3)); - } - final int maxHeight = netherCtx.getMaxHeight() + playerCtx.world().getMinBuildHeight(); - - boolean isOOB = false; - for(int i = 1; i < src.length; i += 3) { - if (src[i] >= maxHeight || src[i] < playerCtx.world().getMinBuildHeight() || - dst[i] >= maxHeight || dst[i] < playerCtx.world().getMinBuildHeight()) { - isOOB = true; - break; - } - } - - if(isOOB) { - for (int i = 0; i < count; i++) { - Vec3 start = new Vec3(src[i * 3], src[i * 3 + 1], src[i * 3 + 2]); - Vec3 end = new Vec3(dst[i * 3], dst[i * 3 + 1], dst[i * 3 + 2]); - if (playerCtx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, playerCtx.player())).getType() != HitResult.Type.MISS) { - return false; - } - } - return true; - } - - return netherCtx.raytrace(count, src, dst, visibility); - } - - @Override - public void cancel() { - netherCtx.cancel(); - } - - @Override - public void destroy() { - netherCtx.destroy(); - } - - @Override - public long getSeed() { - return netherCtx.getSeed(); - } - - @Override - public void RLock() { - netherCtx.RLock(); - } - - @Override - public void RUnlock() { - netherCtx.RUnlock(); - } - - @Override - public int getMaxHeight() { - return netherCtx.getMaxHeight(); - } - - @Override - public boolean passable(int x, int y, int z) { - if(y >= playerCtx.world().getMaxBuildHeight() || y < playerCtx.world().getMinBuildHeight()) { - return true; - } - return netherCtx.passable(x, y, z); - } } diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index d833b869a..896a09725 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -22,8 +22,6 @@ import baritone.api.behavior.look.IAimProcessor; import baritone.api.behavior.look.ITickableAimProcessor; import baritone.api.event.events.*; -import baritone.api.pathing.elytra.IElytraPathfinderContext; -import baritone.api.pathing.elytra.UnpackedSegment; import baritone.api.pathing.goals.GoalBlock; import baritone.api.utils.*; import baritone.api.utils.input.Input; @@ -77,7 +75,9 @@ public final class ElytraBehavior implements Helper { private List visiblePath; // :sunglasses: - public IElytraPathfinderContext context; + public NetherPathfinderContext npfContext; + public IElytraPathFinder pathFinder; + public final PathManager pathManager; private final ElytraProcess process; @@ -118,7 +118,7 @@ public final class ElytraBehavior implements Helper { private int invTickCountdown = 0; private final Queue invTransactionQueue = new LinkedList<>(); - public ElytraBehavior(Baritone baritone, ElytraProcess process, IElytraPathfinderContext context, BlockPos destination, boolean appendDestination) { + public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destination, boolean appendDestination) { this.baritone = baritone; this.ctx = baritone.getPlayerContext(); this.clearLines = new CopyOnWriteArrayList<>(); @@ -129,7 +129,20 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, IElytraPathfinde this.appendDestination = appendDestination; this.solverExecutor = Executors.newSingleThreadExecutor(); this.nextTickBoostCounter = new int[2]; - this.context = context; + + this.npfContext = new NetherPathfinderContext( + Baritone.settings().elytraNetherSeed.value, + Baritone.settings().elytraUseCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, + ctx.world() + ); + + if(ctx.world().dimension() == Level.NETHER) { + this.pathFinder = Baritone.settings().elytraAllowAboveRoof.value && Baritone.settings().elytraAllowAboveBuildLimit.value + ? new BuildLimitPathFinder(ctx, npfContext) + : npfContext; + } else { + this.pathFinder = Baritone.settings().elytraAllowAboveBuildLimit.value ? new BuildLimitPathFinder(ctx, npfContext) : npfContext; + } } public final class PathManager { @@ -161,16 +174,16 @@ public void tick() { int minY = ctx.world().dimensionType().minY(); int y = ctx.playerFeet().y; - if (y >= minY && y < minY + context.getMaxHeight()) { - context.RLock(); - try { - // Obstacles are more important than an incomplete path, handle those first. - this.pathfindAroundObstacles(); - } finally { - context.RUnlock(); - } - this.attemptNextSegment(); + + npfContext.RLock(); + try { + // Obstacles are more important than an incomplete path, handle those first. + this.pathfindAroundObstacles(); + } finally { + npfContext.RUnlock(); } + this.attemptNextSegment(); + } public CompletableFuture pathToDestination() { @@ -300,7 +313,7 @@ public int getNear() { // mickey resigned private CompletableFuture path0(BlockPos src, BlockPos dst, UnaryOperator operator) { - return ElytraBehavior.this.context.pathFindAsync(src, dst) + return ElytraBehavior.this.pathFinder.pathFindAsync(src, dst) .thenApply(operator) .thenAcceptAsync(this::setPath, ctx.minecraft()::execute); } @@ -313,7 +326,7 @@ private void pathfindAroundObstacles() { int rangeStartIncl = playerNear; int rangeEndExcl = playerNear; - while (rangeEndExcl < path.size() && context.hasChunk(new ChunkPos(path.get(rangeEndExcl)))) { + while (rangeEndExcl < path.size() && npfContext.hasChunk(new ChunkPos(path.get(rangeEndExcl)))) { rangeEndExcl++; } // rangeEndExcl now represents an index either not in the path, or just outside render distance @@ -458,14 +471,14 @@ public void onRenderPass(RenderEvent event) { } public void onChunkEvent(ChunkEvent event) { - if (event.isPostPopulate() && this.context != null) { + if (event.isPostPopulate() && this.npfContext != null) { final LevelChunk chunk = ctx.world().getChunk(event.getX(), event.getZ()); - this.context.queueForPacking(chunk); + npfContext.queueForPacking(chunk); } } public void onBlockChange(BlockChangeEvent event) { - this.context.queueBlockUpdate(event); + npfContext.queueBlockUpdate(event); } public void onReceivePacket(PacketEvent event) { @@ -492,7 +505,7 @@ public void destroy() { } catch (InterruptedException e) { e.printStackTrace(); } - this.context.destroy(); + this.npfContext.destroy(); } public void repackChunks() { @@ -513,22 +526,22 @@ public void repackChunks() { LevelChunk chunk = chunkProvider.getChunk(x, z, false); if (chunk != null && !chunk.isEmpty()) { - this.context.queueForPacking(chunk); + npfContext.queueForPacking(chunk); } } } } public void onTick() { - this.context.RLock(); + npfContext.RLock(); try { this.onTick0(); } finally { - this.context.RUnlock(); + npfContext.RUnlock(); } final long now = System.currentTimeMillis(); if ((now - this.timeLastCacheCull) / 1000 > Baritone.settings().elytraTimeBetweenCacheCullSecs.value) { - this.context.queueCacheCulling(ctx.player().chunkPosition().x, ctx.player().chunkPosition().z, Baritone.settings().elytraCacheCullDistance.value); + npfContext.queueCacheCulling(ctx.player().chunkPosition().x, ctx.player().chunkPosition().z, Baritone.settings().elytraCacheCullDistance.value); this.timeLastCacheCull = now; } } @@ -652,11 +665,11 @@ public void onPostTick(TickEvent event) { final SolverContext context = this.new SolverContext(true); this.solver = this.solverExecutor.submit(() -> { - this.context.RLock(); + npfContext.RLock(); try { return this.solveAngles(context); } finally { - this.context.RUnlock(); + npfContext.RUnlock(); } }); this.solveNextTick = false; @@ -1021,14 +1034,15 @@ private boolean isHitboxClear(final SolverContext context, final Vec3 dest, fina return clear; } - return this.context.raytrace(8, src, dst, IElytraPathfinderContext.Visibility.ALL); + + return raytrace(8, src, dst, NetherPathfinderContext.Visibility.ALL); } public boolean clearView(Vec3 start, Vec3 dest, boolean ignoreLava) { final boolean clear; if (!ignoreLava) { // if start == dest then the cpp raytracer dies - clear = start.equals(dest) || this.context.raytrace(start, dest); + clear = start.equals(dest) || raytrace(start, dest); } else { clear = ctx.world().clip(new ClipContext(start, dest, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, ctx.player())).getType() == HitResult.Type.MISS; } @@ -1290,7 +1304,7 @@ private boolean passable(int x, int y, int z, boolean ignoreLava) { final Material mat = this.bsi.get0(x, y, z).getMaterial(); return mat == Material.AIR || mat == Material.LAVA; } else { - return this.context.passable(x, y, z); + return passable(x, y, z); } } @@ -1363,4 +1377,63 @@ else if (ctx.player().getY() < 128 && dst.y >= 128) { private BetterBlockPos destinationFixed() { return fixDestination(this.destination); } + + public boolean raytrace(double startX, double startY, double startZ, double endX, double endY, double endZ) { + final int maxHeight = npfContext.getMaxHeight() + ctx.world().getMinBuildHeight(); + final int minHeight = ctx.world().getMinBuildHeight(); + final boolean isOOB = startY >= maxHeight || endY >= maxHeight || startY < minHeight || endY < minHeight; + if (isOOB) { + Vec3 start = new Vec3(startX, startY, startZ); + Vec3 end = new Vec3(endX, endY, endZ); + return ctx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, ctx.player())).getType() == HitResult.Type.MISS; + } + + return npfContext.raytrace(startX, startY, startZ, endX, endY, endZ); + } + + public boolean raytrace(Vec3 start, Vec3 end) { + final int maxHeight = npfContext.getMaxHeight() + ctx.world().getMinBuildHeight(); + final int minHeight = ctx.world().getMinBuildHeight(); + final boolean isOOB = start.y >= maxHeight || end.y >= maxHeight || start.y < minHeight || end.y < minHeight; + if (isOOB) { + return ctx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, ctx.player())).getType() == HitResult.Type.MISS; + } + return npfContext.raytrace(start.x, start.y, start.z, end.x, end.y, end.z); + } + + public boolean raytrace(int count, double[] src, double[] dst, int visibility) { + if (src.length != count * 3 || src.length != dst.length) { + throw new IllegalArgumentException("Expected source and dst to have length of " + (count * 3)); + } + final int maxHeight = npfContext.getMaxHeight() + ctx.world().getMinBuildHeight(); + + boolean isOOB = false; + for(int i = 1; i < src.length; i += 3) { + if (src[i] >= maxHeight || src[i] < ctx.world().getMinBuildHeight() || + dst[i] >= maxHeight || dst[i] < ctx.world().getMinBuildHeight()) { + isOOB = true; + break; + } + } + + if(isOOB) { + for (int i = 0; i < count; i++) { + Vec3 start = new Vec3(src[i * 3], src[i * 3 + 1], src[i * 3 + 2]); + Vec3 end = new Vec3(dst[i * 3], dst[i * 3 + 1], dst[i * 3 + 2]); + if (ctx.world().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, ctx.player())).getType() != HitResult.Type.MISS) { + return false; + } + } + return true; + } + + return npfContext.raytrace(count, src, dst, visibility); + } + + public boolean passable(int x, int y, int z) { + if(y >= ctx.world().getMaxBuildHeight() || y < ctx.world().getMinBuildHeight()) { + return true; + } + return npfContext.passable(x, y, z); + } } diff --git a/src/api/java/baritone/api/pathing/elytra/IElytraContextFactory.java b/src/main/java/baritone/process/elytra/IElytraPathFinder.java similarity index 74% rename from src/api/java/baritone/api/pathing/elytra/IElytraContextFactory.java rename to src/main/java/baritone/process/elytra/IElytraPathFinder.java index 37cdba9b5..d8148b334 100644 --- a/src/api/java/baritone/api/pathing/elytra/IElytraContextFactory.java +++ b/src/main/java/baritone/process/elytra/IElytraPathFinder.java @@ -15,12 +15,12 @@ * along with Baritone. If not, see . */ -package baritone.api.pathing.elytra; +package baritone.process.elytra; -import baritone.api.utils.IPlayerContext; +import net.minecraft.core.BlockPos; -import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; -public interface IElytraContextFactory { - IElytraPathfinderContext create(IPlayerContext ctx, Path cacheDir); +public interface IElytraPathFinder { + CompletableFuture pathFindAsync(final BlockPos src, final BlockPos dst); } diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index db52ad3c4..351b50648 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -19,8 +19,6 @@ import baritone.Baritone; import baritone.api.event.events.BlockChangeEvent; -import baritone.api.pathing.elytra.IElytraPathfinderContext; -import baritone.api.pathing.elytra.UnpackedSegment; import baritone.utils.accessor.IPalettedContainer; import dev.babbaj.pathfinder.NetherPathfinder; import dev.babbaj.pathfinder.Octree; @@ -53,7 +51,7 @@ /** * @author Brady */ -public final class NetherPathfinderContext implements IElytraPathfinderContext { +public final class NetherPathfinderContext implements IElytraPathFinder { private static final Unsafe UNSAFE; static { @@ -356,4 +354,11 @@ private static void writeChunkData(LevelChunk chunk, long chunkPtr) { public static boolean isSupported() { return NetherPathfinder.isThisSystemSupported(); } + + public static final class Visibility { + public static final int ALL = 0; + public static final int NONE = 1; + public static final int ANY = 2; + private Visibility() {} + } } diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java b/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java deleted file mode 100644 index f3c1b9e1f..000000000 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContextFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.process.elytra; - -import baritone.Baritone; -import baritone.api.pathing.elytra.IElytraContextFactory; -import baritone.api.utils.IPlayerContext; - -import java.nio.file.Path; - -public class NetherPathfinderContextFactory implements IElytraContextFactory { - - @Override - public NetherPathfinderContext create(IPlayerContext ctx, Path cacheDir) { - return new NetherPathfinderContext( - Baritone.settings().elytraNetherSeed.value, - Baritone.settings().elytraUseCache.value ? cacheDir : null, - ctx.world() - ); - } - - @Override - public String toString() { - return "NetherPathfinderContextFactory{}"; - } - -} diff --git a/src/main/java/baritone/process/elytra/NullElytraProcess.java b/src/main/java/baritone/process/elytra/NullElytraProcess.java index 11c48ee31..10891fdee 100644 --- a/src/main/java/baritone/process/elytra/NullElytraProcess.java +++ b/src/main/java/baritone/process/elytra/NullElytraProcess.java @@ -18,7 +18,6 @@ package baritone.process.elytra; import baritone.Baritone; -import baritone.api.pathing.elytra.IElytraContextFactory; import baritone.api.pathing.goals.Goal; import baritone.api.process.IElytraProcess; import baritone.api.process.PathingCommand; @@ -84,16 +83,6 @@ public boolean isLoaded() { return false; } - @Override - public IElytraContextFactory getContextFactory() { - return null; - } - - @Override - public void setContextFactory(IElytraContextFactory factory) { - - } - @Override public boolean isSafeToCancel() { return true; diff --git a/src/main/java/baritone/process/elytra/SkyPathfinderContextFactory.java b/src/main/java/baritone/process/elytra/SkyPathfinderContextFactory.java deleted file mode 100644 index 4da4bc753..000000000 --- a/src/main/java/baritone/process/elytra/SkyPathfinderContextFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.process.elytra; - -import baritone.api.pathing.elytra.IElytraContextFactory; -import baritone.api.utils.IPlayerContext; - -import java.nio.file.Path; - -public class SkyPathfinderContextFactory implements IElytraContextFactory { - - @Override - public SkyPathfinderContext create(IPlayerContext ctx, Path cacheDir) { - return new SkyPathfinderContext(ctx, cacheDir); - } - - @Override - public String toString() { - return "CustomPathfinderContextFactory{}"; - } - -} diff --git a/src/api/java/baritone/api/pathing/elytra/UnpackedSegment.java b/src/main/java/baritone/process/elytra/UnpackedSegment.java similarity index 98% rename from src/api/java/baritone/api/pathing/elytra/UnpackedSegment.java rename to src/main/java/baritone/process/elytra/UnpackedSegment.java index 63a936741..e50ab3235 100644 --- a/src/api/java/baritone/api/pathing/elytra/UnpackedSegment.java +++ b/src/main/java/baritone/process/elytra/UnpackedSegment.java @@ -15,7 +15,7 @@ * along with Baritone. If not, see . */ -package baritone.api.pathing.elytra; +package baritone.process.elytra; import baritone.api.utils.BetterBlockPos; import dev.babbaj.pathfinder.PathSegment; From 1fb8716b5ff634a0572e00938f70fc3e67fa55d1 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Tue, 16 Sep 2025 13:09:12 -0600 Subject: [PATCH 36/48] Resolve Codacy style issues --- .../java/baritone/process/ElytraProcess.java | 22 +++++++++---------- .../process/elytra/BuildLimitPathFinder.java | 12 +++++----- .../process/elytra/ElytraBehavior.java | 12 +++++----- .../elytra/NetherPathfinderContext.java | 4 ++-- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index e618ced5b..3f3b3e44e 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -75,6 +75,11 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private boolean allowAboveRoof; private final Semaphore behaviorSema = new Semaphore(1); + private static final int SHORT_LANDING_COLUMN_HEIGHT = 15; + private static final int LONG_LANDING_COLUMN_HEIGHT = 39; + private int landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT; + private Set badLandingSpots = new HashSet<>(); + @Override public void onLostControl() { this.state = State.START_FLYING; // TODO: null state? @@ -215,11 +220,11 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { behavior.landingMode = this.state == State.LANDING; this.goal = null; baritone.getInputOverrideHandler().clearAllKeys(); - this.behavior.npfContext.RLock(); + this.behavior.npfContext.acquireReadLock(); try { behavior.tick(); } finally { - this.behavior.npfContext.RUnlock(); + this.behavior.npfContext.releaseReadLock(); } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } else if (this.state == State.LANDING) { @@ -612,20 +617,15 @@ private BetterBlockPos checkLandingSpot(BlockPos pos, LongOpenHashSet checkedSpo return null; // void } - private static final int SHORT_LANDING_COLUMN_HEIGHT = 15; - private static final int LONG_LANDING_COLUMN_HEIGHT = 39; - private int landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT; - private Set badLandingSpots = new HashSet<>(); - private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) { if(ctx.player().getY() > ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, start.getX(), start.getZ())) { - return findSafeLandingSpot_heightmap(start); + return heightmapLandingSpot(start); } else { - return findSafeLandingSpot_underground(start); + return undergroundLandingSpot(start); } } - private BetterBlockPos findSafeLandingSpot_underground(BetterBlockPos start) { + private BetterBlockPos undergroundLandingSpot(BetterBlockPos start) { Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y)); Set visited = new HashSet<>(); LongOpenHashSet checkedPositions = new LongOpenHashSet(); @@ -652,7 +652,7 @@ private BetterBlockPos findSafeLandingSpot_underground(BetterBlockPos start) { return null; } - private BetterBlockPos findSafeLandingSpot_heightmap(BetterBlockPos start) { + private BetterBlockPos heightmapLandingSpot(BetterBlockPos start) { Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y)); Set visited = new HashSet<>(); LongOpenHashSet checkedPositions = new LongOpenHashSet(); diff --git a/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java b/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java index 5bb2507ae..786241518 100644 --- a/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java +++ b/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java @@ -66,15 +66,15 @@ public Tuple, Boolean> generateDirectPath(BetterBlockPos st final LinkedList path = new LinkedList<>(); final int stepDistance = 32; - start = start.y == flightLevel ? start : new BetterBlockPos(start.getX(), flightLevel, start.getZ()); - destination = destination.y == flightLevel ? destination : new BetterBlockPos(destination.getX(), flightLevel, destination.getZ()); + final BetterBlockPos startFixed = start.y == flightLevel ? start : new BetterBlockPos(start.getX(), flightLevel, start.getZ()); + final BetterBlockPos destinationFixed = destination.y == flightLevel ? destination : new BetterBlockPos(destination.getX(), flightLevel, destination.getZ()); - BetterBlockPos cur = start; + BetterBlockPos cur = startFixed; path.add(cur); while (path.size() < maxPathSize) { - double deltaX = destination.getX() - cur.getX(); - double deltaZ = destination.getZ() - cur.getZ(); + double deltaX = destinationFixed.getX() - cur.getX(); + double deltaZ = destinationFixed.getZ() - cur.getZ(); double remainingDistance = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ); double remainingDistanceSq = deltaX * deltaX + deltaZ * deltaZ; @@ -82,7 +82,7 @@ public Tuple, Boolean> generateDirectPath(BetterBlockPos st // We are within the buffer distance, so we can stop here return new Tuple<>(path, true); } else if (remainingDistance <= stepDistance) { - path.add(destination); + path.add(destinationFixed); return new Tuple<>(path, true); } diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 896a09725..048f87d76 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -175,12 +175,12 @@ public void tick() { int minY = ctx.world().dimensionType().minY(); int y = ctx.playerFeet().y; - npfContext.RLock(); + npfContext.acquireReadLock(); try { // Obstacles are more important than an incomplete path, handle those first. this.pathfindAroundObstacles(); } finally { - npfContext.RUnlock(); + npfContext.releaseReadLock(); } this.attemptNextSegment(); @@ -533,11 +533,11 @@ public void repackChunks() { } public void onTick() { - npfContext.RLock(); + npfContext.acquireReadLock(); try { this.onTick0(); } finally { - npfContext.RUnlock(); + npfContext.releaseReadLock(); } final long now = System.currentTimeMillis(); if ((now - this.timeLastCacheCull) / 1000 > Baritone.settings().elytraTimeBetweenCacheCullSecs.value) { @@ -665,11 +665,11 @@ public void onPostTick(TickEvent event) { final SolverContext context = this.new SolverContext(true); this.solver = this.solverExecutor.submit(() -> { - npfContext.RLock(); + npfContext.acquireReadLock(); try { return this.solveAngles(context); } finally { - npfContext.RUnlock(); + npfContext.releaseReadLock(); } }); this.solveNextTick = false; diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 351b50648..92d223abe 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -280,11 +280,11 @@ public long getSeed() { return this.seed; } - public void RLock() { + public void acquireReadLock() { this.readLock.lock(); } - public void RUnlock() { + public void releaseReadLock() { this.readLock.unlock(); } From 86138732cc5a87a5f6e8e917333d3df9fb9dc627 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Fri, 19 Sep 2025 19:51:49 -0600 Subject: [PATCH 37/48] Only land if there is a set landing spot --- src/main/java/baritone/process/ElytraProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 3f3b3e44e..de50fc382 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -194,7 +194,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { reachedGoal = true; // we are goingToLandingSpot and we are in the last node of the path - if (this.goingToLandingSpot) { + if (this.goingToLandingSpot && landingSpot != null) { this.state = State.LANDING; logDirect("Above the landing spot, landing..."); } From cd0846ce6c202e260f4e8fde735d30cf0cd9b702 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Wed, 8 Oct 2025 17:50:02 -0600 Subject: [PATCH 38/48] Persist NetherPathfinderContext through new goals --- .../java/baritone/process/ElytraProcess.java | 67 ++++++++++++++++--- .../process/elytra/ElytraBehavior.java | 33 +-------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index de50fc382..024f2b8f1 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -53,6 +53,8 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.phys.Vec3; @@ -69,11 +71,12 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private boolean reachedGoal; // this basically just prevents potential notification spam private Goal goal; private ElytraBehavior behavior; + private NetherPathfinderContext npfContext; private boolean predictingTerrain; private boolean allowTight; private boolean allowAboveBuildLimit; private boolean allowAboveRoof; - private final Semaphore behaviorSema = new Semaphore(1); + private final Semaphore npfSema = new Semaphore(1); private static final int SHORT_LANDING_COLUMN_HEIGHT = 15; private static final int LONG_LANDING_COLUMN_HEIGHT = 39; @@ -82,12 +85,19 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro @Override public void onLostControl() { + onLostControl(true); + } + + public void onLostControl(boolean destroyNpf) { this.state = State.START_FLYING; // TODO: null state? this.goingToLandingSpot = false; this.landingSpot = null; this.reachedGoal = false; this.goal = null; destroyBehaviorAsync(); + if (destroyNpf) { + destroyNpfContextAsync(); + } } private ElytraProcess(Baritone baritone) { @@ -328,7 +338,6 @@ private void destroyBehaviorAsync() { this.behavior = null; Baritone.getExecutor().execute(() -> { behavior.destroy(); - behaviorSema.release(); }); } } @@ -345,8 +354,27 @@ public String displayName0() { @Override public void repackChunks() { - if (this.behavior != null) { - this.behavior.repackChunks(); + if (this.npfContext == null) return; + + ChunkSource chunkProvider = ctx.world().getChunkSource(); + BetterBlockPos playerPos = ctx.playerFeet(); + + int playerChunkX = playerPos.getX() >> 4; + int playerChunkZ = playerPos.getZ() >> 4; + + int minX = playerChunkX - 40; + int minZ = playerChunkZ - 40; + int maxX = playerChunkX + 40; + int maxZ = playerChunkZ + 40; + + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + LevelChunk chunk = chunkProvider.getChunk(x, z, false); + + if (chunk != null && !chunk.isEmpty()) { + npfContext.queueForPacking(chunk); + } + } } } @@ -372,17 +400,15 @@ private void pathTo0(BlockPos destination, boolean appendDestination) { if (ctx.player() == null) { return; } - this.onLostControl(); + this.onLostControl(false); this.predictingTerrain = ctx.player().level.dimension() == Level.NETHER && Baritone.settings().elytraPredictTerrain.value; this.allowTight = Baritone.settings().elytraAllowTightSpaces.value; this.allowAboveBuildLimit = Baritone.settings().elytraAllowAboveBuildLimit.value; this.allowAboveRoof = Baritone.settings().elytraAllowAboveRoof.value; - - this.behaviorSema.acquireUninterruptibly(); - this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination); + this.behavior = new ElytraBehavior(this.baritone, this, getNpfContext(), destination, appendDestination); if (ctx.world() != null) { - this.behavior.repackChunks(); + this.repackChunks(); } this.behavior.pathTo(); } @@ -681,4 +707,27 @@ private BetterBlockPos heightmapLandingSpot(BetterBlockPos start) { } return null; } + + private NetherPathfinderContext getNpfContext() { + if(this.npfContext == null) { + npfSema.acquireUninterruptibly(); + this.npfContext = new NetherPathfinderContext( + Baritone.settings().elytraNetherSeed.value, + Baritone.settings().elytraUseCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, + ctx.world() + ); + } + return this.npfContext; + } + + private void destroyNpfContextAsync() { + NetherPathfinderContext npf = this.npfContext; + if (npf != null) { + this.npfContext = null; + Baritone.getExecutor().execute(() -> { + npf.destroy(); + npfSema.release(); + }); + } + } } diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 048f87d76..e12d74750 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -118,7 +118,7 @@ public final class ElytraBehavior implements Helper { private int invTickCountdown = 0; private final Queue invTransactionQueue = new LinkedList<>(); - public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destination, boolean appendDestination) { + public ElytraBehavior(Baritone baritone, ElytraProcess process, NetherPathfinderContext npf, BlockPos destination, boolean appendDestination) { this.baritone = baritone; this.ctx = baritone.getPlayerContext(); this.clearLines = new CopyOnWriteArrayList<>(); @@ -130,11 +130,7 @@ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destina this.solverExecutor = Executors.newSingleThreadExecutor(); this.nextTickBoostCounter = new int[2]; - this.npfContext = new NetherPathfinderContext( - Baritone.settings().elytraNetherSeed.value, - Baritone.settings().elytraUseCache.value ? baritone.getWorldProvider().getCurrentWorld().directory.resolve("cache") : null, - ctx.world() - ); + this.npfContext = npf; if(ctx.world().dimension() == Level.NETHER) { this.pathFinder = Baritone.settings().elytraAllowAboveRoof.value && Baritone.settings().elytraAllowAboveBuildLimit.value @@ -505,31 +501,6 @@ public void destroy() { } catch (InterruptedException e) { e.printStackTrace(); } - this.npfContext.destroy(); - } - - public void repackChunks() { - ChunkSource chunkProvider = ctx.world().getChunkSource(); - - BetterBlockPos playerPos = ctx.playerFeet(); - - int playerChunkX = playerPos.getX() >> 4; - int playerChunkZ = playerPos.getZ() >> 4; - - int minX = playerChunkX - 40; - int minZ = playerChunkZ - 40; - int maxX = playerChunkX + 40; - int maxZ = playerChunkZ + 40; - - for (int x = minX; x <= maxX; x++) { - for (int z = minZ; z <= maxZ; z++) { - LevelChunk chunk = chunkProvider.getChunk(x, z, false); - - if (chunk != null && !chunk.isEmpty()) { - npfContext.queueForPacking(chunk); - } - } - } } public void onTick() { From 4113c1635e38864ed6a36c6bfff65aaff7d75768 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Thu, 9 Oct 2025 18:08:17 -0600 Subject: [PATCH 39/48] Unwrap PathCalculationException so it can be handled more efficiently --- .../process/elytra/BuildLimitPathFinder.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java b/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java index 786241518..65ee7ca70 100644 --- a/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java +++ b/src/main/java/baritone/process/elytra/BuildLimitPathFinder.java @@ -240,13 +240,9 @@ public CompletableFuture pathFindAsync(BlockPos src, BlockPos d } return CompletableFuture.supplyAsync(() -> { - try { - var np = netherCtx.pathFindAsync(path.get(path.size() - 1), dst).get(); - path.addAll(np.collect()); - return new UnpackedSegment(path.stream(), np.isFinished()); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } + var np = blockingPathFind(path.get(path.size() - 1), dst); + path.addAll(np.collect()); + return new UnpackedSegment(path.stream(), np.isFinished()); }); } @@ -277,12 +273,7 @@ public CompletableFuture pathFindAsync(BlockPos src, BlockPos d */ private CompletableFuture incompletePathfind(BlockPos src, BlockPos dst) { return CompletableFuture.supplyAsync(() -> { - UnpackedSegment packed; - try { - packed = netherCtx.pathFindAsync(src, dst).get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } + UnpackedSegment packed = blockingPathFind(src, dst); return new UnpackedSegment( packed.collect().stream(), false @@ -290,4 +281,19 @@ private CompletableFuture incompletePathfind(BlockPos src, Bloc }); } + private UnpackedSegment blockingPathFind(BlockPos src, BlockPos dst) { + try { + return netherCtx.pathFindAsync(src, dst).get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (ExecutionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof PathCalculationException) { + throw (PathCalculationException) cause; + } + throw new RuntimeException(e); + } + } + } From 2aea9a368a4e00b0fa3d6e15a22b46597c7ac688 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Fri, 17 Oct 2025 11:20:08 -0600 Subject: [PATCH 40/48] Don't consider a position unloaded if y is beyond the build limit when searching for a landing position --- src/main/java/baritone/process/ElytraProcess.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 024f2b8f1..9c34500f2 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -651,6 +651,10 @@ private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) { } } + private boolean isChunkLoaded(BetterBlockPos pos) { + return ctx.world().getChunkSource().hasChunk(pos.x >> 4, pos.z >> 4); + } + private BetterBlockPos undergroundLandingSpot(BetterBlockPos start) { Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y)); Set visited = new HashSet<>(); @@ -659,7 +663,7 @@ private BetterBlockPos undergroundLandingSpot(BetterBlockPos start) { while (!queue.isEmpty()) { BetterBlockPos pos = queue.poll(); - if (ctx.world().isLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { + if (isChunkLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions); if(actualLandingSpot != null) { landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT; @@ -686,7 +690,8 @@ private BetterBlockPos heightmapLandingSpot(BetterBlockPos start) { while (!queue.isEmpty()) { BetterBlockPos qPos = queue.poll(); - if (!ctx.world().isLoaded(qPos)) continue; + + if (!isChunkLoaded(qPos)) continue; var height = ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, qPos.getX(), qPos.getZ()); var pos = new BetterBlockPos(qPos.getX(), height+1, qPos.getZ()); From 901995afa6ffb3bf248331dacbfd7bf1de35f1ed Mon Sep 17 00:00:00 2001 From: LHM <64268144@qq.com> Date: Tue, 31 Mar 2026 22:35:00 +0800 Subject: [PATCH 41/48] Update FEATURES.md in a more suitable format --- FEATURES.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 11f356316..612f700ab 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -1,4 +1,5 @@ # Pathing features + - **Long distance pathing and splicing** Baritone calculates paths in segments, and precalculates the next segment when the current one is about to end, so that it's moving towards the goal at all times. - **Chunk caching** Baritone simplifies chunks to a compacted internal 2-bit representation (AIR, SOLID, WATER, AVOID) and stores them in RAM for better very-long-distance pathing. There is also an option to save these cached chunks to disk. Example - **Block breaking** Baritone considers breaking blocks as part of its path. It also takes into account your current tool set and hot bar. For example, if you have a Eff V diamond pick, it may choose to mine through a stone barrier, while if you only had a wood pick it might be faster to climb over it. @@ -14,7 +15,8 @@ - **Pigs** It can sort of control pigs. I wouldn't rely on it though. # Pathing method -Baritone uses A*, with some modifications: + +Baritone uses A*, with some modifications: - **Segmented calculation** Traditional A* calculates until the most promising node is in the goal, however in the environment of Minecraft with a limited render distance, we don't know the environment all the way to our goal. Baritone has three possible ways for path calculation to end: finding a path all the way to the goal, running out of time, or getting to the render distance. In the latter two scenarios, the selection of which segment to actually execute falls to the next item (incremental cost backoff). Whenever the path calculation thread finds that the best / most promising node is at the edge of loaded chunks, it increments a counter. If this happens more than 50 times (configurable), path calculation exits early. This happens with very low render distances. Otherwise, calculation continues until the timeout is hit (also configurable) or we find a path all the way to the goal. - **Incremental cost backoff** When path calculation exits early without getting all the way to the goal, Baritone it needs to select a segment to execute first (assuming it will calculate the next segment at the end of this one). It uses incremental cost backoff to select the best node by varying metrics, then paths to that node. This is unchanged from MineBot and I made a write-up that still applies. In essence, it keeps track of the best node by various increasing coefficients, then picks the node with the least coefficient that goes at least 5 blocks from the starting position. @@ -27,7 +29,9 @@ Baritone uses A*, with some modifications: - [Baritone chat control usage](USAGE.md) # Goals + The pathing goal can be set to any of these options: + - **GoalBlock** one specific block that the player should stand inside at foot level - **GoalXZ** an X and a Z coordinate, used for long distance pathing - **GoalYLevel** a Y coordinate @@ -38,14 +42,16 @@ The pathing goal can be set to any of these options: And finally `GoalComposite`. `GoalComposite` is a list of other goals, any one of which satisfies the goal. For example, `mine diamond_ore` creates a `GoalComposite` of `GoalTwoBlocks`s for every diamond ore location it knows of. - # Future features + Things it doesn't have yet + - Trapdoors - Sprint jumping in a 1x2 corridor See issues for more. Things it may not ever have, from most likely to least likely =( + - Boats - Horses (2x3 path instead of 1x2) From a951efaf264de46cd4e993794a5177b70f8f8e65 Mon Sep 17 00:00:00 2001 From: LHM <64268144@qq.com> Date: Tue, 31 Mar 2026 22:44:14 +0800 Subject: [PATCH 42/48] Fix formatting in README.md Formatting in the README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a8f3b1f49..65cbf9321 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Baritone +

GitHub All Releases

@@ -111,7 +112,7 @@ Here are some links to help to get started: # API The API is heavily documented, you can find the Javadocs for the latest release [here](https://baritone.leijurv.com/). -Please note that usage of anything located outside of the ``baritone.api`` package is not supported by the API release +Please note that usage of anything located outside of the `baritone.api` package is not supported by the API release jar. Below is an example of basic usage for changing some settings, and then pathing to an X/Z goal. @@ -119,7 +120,6 @@ Below is an example of basic usage for changing some settings, and then pathing ```java BaritoneAPI.getSettings().allowSprint.value = true; BaritoneAPI.getSettings().primaryTimeoutMS.value = 2000L; - BaritoneAPI.getProvider().getPrimaryBaritone().getCustomGoalProcess().setGoalAndPath(new GoalXZ(10000, 20000)); ``` From 91b807d3b3cb85965e7da89306881d0d662da208 Mon Sep 17 00:00:00 2001 From: LHM <64268144@qq.com> Date: Tue, 31 Mar 2026 22:48:36 +0800 Subject: [PATCH 43/48] Update SETUP.md Use more appropriate line breaks and remove excess spaces. --- SETUP.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/SETUP.md b/SETUP.md index 96991cea6..88340b0c9 100644 --- a/SETUP.md +++ b/SETUP.md @@ -6,20 +6,21 @@ The easiest way to install Baritone is to install it as Forge/Neoforge/Fabric mo Once Baritone is installed, look [here](USAGE.md) for instructions on how to use it. ## Prebuilt official releases + Releases are made rarely and are not always up to date with the latest features and bug fixes. Link to the releases page: [Releases](https://github.com/cabaletta/baritone/releases) The mapping between Minecraft versions and major Baritone versions is as follows + | Minecraft version | 1.12 | 1.13 | 1.14 | 1.15 | 1.16 | 1.17 | 1.18 | 1.19 | 1.20 | 1.21 | 1.21.4 | 1.21.5 | 1.21.6 - 1.21.8 | |-------------------|------|------|------|------|------|------|------|------|-------|-------|--------|--------|------------------| | Baritone version | v1.2 | v1.3 | v1.4 | v1.5 | v1.6 | v1.7 | v1.8 | v1.9 | v1.10 | v1.11 | v1.13 | v1.14 | v1.15 | -Any official release will be GPG signed by leijurv (44A3EA646EADAC6A). Please verify that the hash of the file you download is in `checksums.txt` and that `checksums_signed.asc` is a valid signature by that public keys of `checksums.txt`. +Any official release will be GPG signed by leijurv (44A3EA646EADAC6A). Please verify that the hash of the file you download is in `checksums.txt` and that `checksums_signed.asc` is a valid signature by that public keys of `checksums.txt`. The build is fully deterministic and reproducible, and you can verify that by running `docker build --no-cache -t cabaletta/baritone .` yourself and comparing the shasum. This works identically on Travis, Mac, and Linux (if you have docker on Windows, I'd be grateful if you could let me know if it works there too). - ## Artifacts Building Baritone will create the final artifacts in the ``dist`` directory. These are the same as the artifacts created in the [releases](https://github.com/cabaletta/baritone/releases). @@ -31,6 +32,7 @@ If you want to report a bug and spare us some effort, you want `baritone-unoptim Otherwise, you want `baritone-standalone-*-VERSION.jar` Here's what the various qualifiers mean + - **API**: Only the non-api packages are obfuscated. This should be used in environments where other mods would like to use Baritone's features. - **Standalone**: Everything is obfuscated. Other mods cannot use Baritone, but you get a bit of extra performance. - **Unoptimized**: Nothing is obfuscated. This shouldn't be used in production, but is really helpful for crash reports. @@ -41,6 +43,7 @@ Here's what the various qualifiers mean If you build from source you will also find mapping files in the `dist` directory. These contain the renamings done by ProGuard and are useful if you want to read obfuscated stack traces. ## Build it yourself + - Clone or download Baritone ![Image](https://i.imgur.com/kbqBtoN.png) @@ -48,9 +51,11 @@ If you build from source you will also find mapping files in the `dist` director - Follow one of the instruction sets below, based on your preference ## Command Line + On Mac OSX and Linux, use `./gradlew` instead of `gradlew`. The recommended Java versions by Minecraft version are + | Minecraft version | Java version | |-------------------------------|---------------| | 1.12.2 - 1.16.5 | 8 | @@ -58,7 +63,7 @@ The recommended Java versions by Minecraft version are | 1.18.2 - 1.20.4 | 17 | | 1.20.5 - 1.21.8 | 21 | -Download java: https://adoptium.net/ +Download java: To check which java version you are using do `java -version` in a command prompt or terminal. @@ -75,11 +80,13 @@ and `gradlew build -Pbaritone.forge_build` / `gradlew build -Pbaritone.fabric_bu for Forge/Fabric instead. And you might have to run `setupDecompWorkspace` first. ## IntelliJ + - Open the project in IntelliJ as a Gradle project - Refresh the Gradle project (or, to be safe, just restart IntelliJ) - Depending on the minecraft version, you may need to run `setupDecompWorkspace` or `genIntellijRuns` in order to get everything working ## Github Actions + Most branches have a CI workflow at `.github/workflows/gradle_build.yml`. If you fork this repository and enable actions for your fork you can push a dummy commit to trigger it and have GitHub build Baritone for you. From b164d072e35aa952e2e5aba239c9f9629b55b8d7 Mon Sep 17 00:00:00 2001 From: LHM <64268144@qq.com> Date: Tue, 31 Mar 2026 22:51:52 +0800 Subject: [PATCH 44/48] Fix formatting issues in USAGE.md --- USAGE.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/USAGE.md b/USAGE.md index 46241e3fe..fe95dad9a 100644 --- a/USAGE.md +++ b/USAGE.md @@ -16,7 +16,7 @@ Try `#help` I promise it won't just send you back here =) "wtf where is cleararea" -> look at `#help sel` -"wtf where is goto death, goto waypoint" -> look at `#help wp` +"wtf where is goto death, goto waypoint" -> look at `#help wp` just look at `#help` lmao @@ -33,6 +33,7 @@ Watch this [showcase video](https://youtu.be/CZkLXWo4Fg4)! To toggle a boolean setting, just say its name in chat (for example saying `allowBreak` toggles whether Baritone will consider breaking blocks). For a numeric setting, say its name then the new value (like `primaryTimeoutMS 250`). It's case insensitive. To reset a setting to its default value, say `acceptableThrowawayItems reset`. To reset all settings, say `reset`. To see all settings that have been modified from their default values, say `modified`. Commands in Baritone: + - `thisway 1000` then `path` to go in the direction you're facing for a thousand blocks - `goal x y z` or `goal x z` or `goal y`, then `path` to set a goal to a certain coordinate then path to it - `goto x y z` or `goto x z` or `goto y` to go to a certain coordinate (in a single step, starts going immediately) @@ -47,7 +48,7 @@ Commands in Baritone: - `build` to build a schematic. `build blah.schematic` will load `schematics/blah.schematic` and build it with the origin being your player feet. `build blah.schematic x y z` to set the origin. Any of those can be relative to your player (`~ 69 ~-420` would build at x=player x, y=69, z=player z-420). - `schematica` to build the schematic that is currently open in schematica - `tunnel` to dig and make a tunnel, 1x2. It will only deviate from the straight line if necessary such as to avoid lava. For a dumber tunnel that is really just cleararea, you can `tunnel 3 2 100`, to clear an area 3 high, 2 wide, and 100 deep. -- `farm` to automatically harvest, replant, or bone meal crops. Use `farm ` or `farm ` to limit the max distance from the starting point or a waypoint. +- `farm` to automatically harvest, replant, or bone meal crops. Use `farm ` or `farm ` to limit the max distance from the starting point or a waypoint. - `axis` to go to an axis or diagonal axis at y=120 (`axisHeight` is a configurable setting, defaults to 120). - `explore x z` to explore the world from the origin of x,z. Leave out x and z to default to player feet. This will continually path towards the closest chunk to the origin that it's never seen before. `explorefilter filter.json` with optional invert can be used to load in a list of chunks to load. - `invert` to invert the current goal and path. This gets as far away from it as possible, instead of as close as possible. For example, do `goal` then `invert` to run as far as possible from where you're standing at the start. @@ -67,6 +68,7 @@ Commands in Baritone: All the settings and documentation are here. If you find HTML easier to read than Javadoc, you can look here. There are about a hundred settings, but here are some fun / interesting / important ones that you might want to look at changing in normal usage of Baritone. The documentation for each can be found at the above links. + - `allowBreak` - `allowSprint` - `allowPlace` @@ -86,12 +88,10 @@ There are about a hundred settings, but here are some fun / interesting / import - `mineScanDroppedItems` - `allowDiagonalAscend` - - - # Troubleshooting / common issues ## Why doesn't Baritone respond to any of my chat commands? + This could be one of many things. First, make sure it's actually installed. An easy way to check is seeing if it created the folder `baritone` in your Minecraft folder. @@ -101,7 +101,7 @@ Second, make sure that you're using the prefix properly, and that chat control i For example, Impact disables direct chat control. (i.e. anything typed in chat without a prefix will be ignored and sent publicly). **This is a saved setting**, so if you run Impact once, `chatControl` will be off from then on, **even in other clients**. So you'll need to use the `#` prefix or edit `baritone/settings.txt` in your Minecraft folder to undo that (specifically, remove the line `chatControl false` then restart your client). - ## Why can I do `.goto x z` in Impact but nowhere else? Why can I do `-path to x z` in KAMI but nowhere else? + These are custom commands that they added; those aren't from Baritone. The equivalent you're looking for is `goto x z`. From 76d6a98d08486fec0eba9d64e1c35ec47aa79ff9 Mon Sep 17 00:00:00 2001 From: mankool0 <8884398+mankool0@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:46:55 -0700 Subject: [PATCH 45/48] Fix BetterBlockPos leaking into block entity map --- src/api/java/baritone/api/utils/RotationUtils.java | 4 ++++ src/api/java/baritone/api/utils/VecUtils.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/api/java/baritone/api/utils/RotationUtils.java b/src/api/java/baritone/api/utils/RotationUtils.java index bfab3fa87..2c2ee9147 100644 --- a/src/api/java/baritone/api/utils/RotationUtils.java +++ b/src/api/java/baritone/api/utils/RotationUtils.java @@ -175,6 +175,10 @@ public static Optional reachable(IPlayerContext ctx, BlockPos pos, dou } public static Optional reachable(IPlayerContext ctx, BlockPos pos, double blockReachDistance, boolean wouldSneak) { + // Prevent BetterBlockPos from leaking into Minecraft's block entity map + if (pos instanceof BetterBlockPos) { + pos = new BlockPos(pos.getX(), pos.getY(), pos.getZ()); + } if (BaritoneAPI.getSettings().remainWithExistingLookDirection.value && ctx.isLookingAt(pos)) { /* * why add 0.0001? diff --git a/src/api/java/baritone/api/utils/VecUtils.java b/src/api/java/baritone/api/utils/VecUtils.java index 4ea94b95a..7037afcfc 100644 --- a/src/api/java/baritone/api/utils/VecUtils.java +++ b/src/api/java/baritone/api/utils/VecUtils.java @@ -43,6 +43,10 @@ private VecUtils() {} * @see #getBlockPosCenter(BlockPos) */ public static Vec3 calculateBlockCenter(Level world, BlockPos pos) { + // Prevent BetterBlockPos from leaking into Minecraft's block entity map + if (pos instanceof BetterBlockPos) { + pos = new BlockPos(pos.getX(), pos.getY(), pos.getZ()); + } BlockState b = world.getBlockState(pos); VoxelShape shape = b.getCollisionShape(world, pos); if (shape.isEmpty()) { From f0e87ab1aec5ae4afe8a3c96229a858da7a16a94 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Mon, 27 Apr 2026 08:19:05 -0600 Subject: [PATCH 46/48] Avoid blocking the thread if the npf write lock is held --- src/main/java/baritone/process/ElytraProcess.java | 11 ++++++----- .../java/baritone/process/elytra/ElytraBehavior.java | 11 ++++++----- .../process/elytra/NetherPathfinderContext.java | 4 ++++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 9c34500f2..466529794 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -230,11 +230,12 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { behavior.landingMode = this.state == State.LANDING; this.goal = null; baritone.getInputOverrideHandler().clearAllKeys(); - this.behavior.npfContext.acquireReadLock(); - try { - behavior.tick(); - } finally { - this.behavior.npfContext.releaseReadLock(); + if (this.behavior.npfContext.tryAcquireReadLock()) { + try { + behavior.tick(); + } finally { + this.behavior.npfContext.releaseReadLock(); + } } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } else if (this.state == State.LANDING) { diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index e12d74750..65419b822 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -504,11 +504,12 @@ public void destroy() { } public void onTick() { - npfContext.acquireReadLock(); - try { - this.onTick0(); - } finally { - npfContext.releaseReadLock(); + if (npfContext.tryAcquireReadLock()) { + try { + this.onTick0(); + } finally { + npfContext.releaseReadLock(); + } } final long now = System.currentTimeMillis(); if ((now - this.timeLastCacheCull) / 1000 > Baritone.settings().elytraTimeBetweenCacheCullSecs.value) { diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java index 92d223abe..69e0a181b 100644 --- a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java +++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java @@ -284,6 +284,10 @@ public void acquireReadLock() { this.readLock.lock(); } + public boolean tryAcquireReadLock() { + return this.readLock.tryLock(); + } + public void releaseReadLock() { this.readLock.unlock(); } From 7750a1a356ffa53e107104cc98ed440d51f48ca2 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Mon, 27 Apr 2026 08:43:56 -0600 Subject: [PATCH 47/48] Allow landing spot search to take place across multiple ticks --- .../java/baritone/process/ElytraProcess.java | 137 ++++++++++++------ 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java index 466529794..b75b6063f 100644 --- a/src/main/java/baritone/process/ElytraProcess.java +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -61,6 +61,7 @@ import java.util.*; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; import static baritone.api.pathing.movement.ActionCosts.COST_INF; @@ -80,8 +81,10 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro private static final int SHORT_LANDING_COLUMN_HEIGHT = 15; private static final int LONG_LANDING_COLUMN_HEIGHT = 39; + private static final long LANDING_SEARCH_BUDGET_NANOS = TimeUnit.MILLISECONDS.toNanos(25); // half a tick private int landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT; private Set badLandingSpots = new HashSet<>(); + private LandingSearchState landingSearchState; @Override public void onLostControl() { @@ -92,6 +95,7 @@ public void onLostControl(boolean destroyNpf) { this.state = State.START_FLYING; // TODO: null state? this.goingToLandingSpot = false; this.landingSpot = null; + this.landingSearchState = null; this.reachedGoal = false; this.goal = null; destroyBehaviorAsync(); @@ -181,14 +185,19 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { if (ctx.player().isFallFlying() && this.state != State.LANDING && (this.behavior.pathManager.isComplete() || safetyLanding)) { final BetterBlockPos last = this.behavior.pathManager.path.getLast(); if (last != null && (ctx.player().position().distanceToSqr(last.getCenter()) < (48 * 48) || safetyLanding) && (!goingToLandingSpot || (safetyLanding && this.landingSpot == null))) { - logDirect("Path complete, picking a nearby safe landing spot..."); + if (this.landingSearchState == null) { + logDirect("Path complete, searching for safe landing spot..."); + } BetterBlockPos landingSpot = findSafeLandingSpot(ctx.playerFeet()); // if this fails we will just keep orbiting the last node until we run out of rockets or the user intervenes if (landingSpot != null) { + logDirect("Found potential landing spot."); this.pathTo0(landingSpot, true); this.landingSpot = landingSpot; + this.goingToLandingSpot = true; + } else { + this.goingToLandingSpot = false; } - this.goingToLandingSpot = true; } if (last != null && ctx.player().position().distanceToSqr(last.getCenter()) < 1) { @@ -330,6 +339,7 @@ public void landingSpotIsBad(BetterBlockPos endPos) { badLandingSpots.add(endPos); goingToLandingSpot = false; this.landingSpot = null; + this.landingSearchState = null; this.state = State.FLYING; } @@ -645,73 +655,108 @@ private BetterBlockPos checkLandingSpot(BlockPos pos, LongOpenHashSet checkedSpo } private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) { - if(ctx.player().getY() > ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, start.getX(), start.getZ())) { - return heightmapLandingSpot(start); + final boolean useHeightmap = ctx.player().getY() > ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, start.getX(), start.getZ()); + if (this.landingSearchState == null || !this.landingSearchState.isCompatible(start, useHeightmap)) { + this.landingSearchState = new LandingSearchState(start, this.behavior.destination, useHeightmap); } else { - return undergroundLandingSpot(start); + this.landingSearchState.updateStartPosition(start); + } + + BetterBlockPos landingSpot = this.landingSearchState.advance(); + if (landingSpot != null || this.landingSearchState.exhausted) { + this.landingSearchState = null; } + return landingSpot; } private boolean isChunkLoaded(BetterBlockPos pos) { return ctx.world().getChunkSource().hasChunk(pos.x >> 4, pos.z >> 4); } - private BetterBlockPos undergroundLandingSpot(BetterBlockPos start) { - Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y)); - Set visited = new HashSet<>(); - LongOpenHashSet checkedPositions = new LongOpenHashSet(); - queue.add(start); + private final class LandingSearchState { + private final BetterBlockPos origin; + private final boolean useHeightmap; + private final Queue queue; + private final Set visited = new HashSet<>(); + private final LongOpenHashSet checkedPositions = new LongOpenHashSet(); + private boolean exhausted; + + private LandingSearchState(BetterBlockPos origin, BetterBlockPos dest, boolean useHeightmap) { + this.origin = origin; + this.useHeightmap = useHeightmap; + + final BetterBlockPos target = isChunkLoaded(dest) ? dest : origin; + this.queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - target.x) * (pos.x - target.x) + (pos.z - target.z) * (pos.z - target.z)).thenComparingInt(pos -> -pos.y)); + this.queue.add(target); + } - while (!queue.isEmpty()) { - BetterBlockPos pos = queue.poll(); - if (isChunkLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { - BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions); - if(actualLandingSpot != null) { + private boolean isCompatible(BetterBlockPos start, boolean useHeightmap) { + // Restart if we've moved more than a chunk so the priority adjusts and newly loaded chunks get revisited + return this.useHeightmap == useHeightmap && this.origin.distanceSq(start) <= (16 * 16); + } + + private void updateStartPosition(BetterBlockPos start) { + if (this.visited.add(start)) { + this.queue.add(start); + } + } + + private BetterBlockPos advance() { + final long deadline = System.nanoTime() + LANDING_SEARCH_BUDGET_NANOS; + while (!this.queue.isEmpty()) { + if (System.nanoTime() >= deadline) { + return null; + } + BetterBlockPos qPos = this.queue.poll(); + if (!isChunkLoaded(qPos)) { + continue; + } + BetterBlockPos landing = this.useHeightmap ? this.advanceHeightmap(qPos) : this.advanceUnderground(qPos); + if (landing != null) { + return landing; + } + } + this.exhausted = true; + return null; + } + + private BetterBlockPos advanceUnderground(BetterBlockPos pos) { + if (isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { + BetterBlockPos actualLandingSpot = checkLandingSpot(pos, this.checkedPositions); + if (actualLandingSpot != null) { landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT; - if (isColumnAir(actualLandingSpot, this.landingColumnHeight) && hasAirBubble(actualLandingSpot.above(this.landingColumnHeight)) && !badLandingSpots.contains(actualLandingSpot.above(this.landingColumnHeight))) { - return actualLandingSpot.above(this.landingColumnHeight); + if (isColumnAir(actualLandingSpot, landingColumnHeight) && hasAirBubble(actualLandingSpot.above(landingColumnHeight)) && !badLandingSpots.contains(actualLandingSpot.above(landingColumnHeight))) { + return actualLandingSpot.above(landingColumnHeight); } } - if (visited.add(pos.north())) queue.add(pos.north()); - if (visited.add(pos.east())) queue.add(pos.east()); - if (visited.add(pos.south())) queue.add(pos.south()); - if (visited.add(pos.west())) queue.add(pos.west()); - if (visited.add(pos.above())) queue.add(pos.above()); - if (visited.add(pos.below())) queue.add(pos.below()); + if (this.visited.add(pos.north())) this.queue.add(pos.north()); + if (this.visited.add(pos.east())) this.queue.add(pos.east()); + if (this.visited.add(pos.south())) this.queue.add(pos.south()); + if (this.visited.add(pos.west())) this.queue.add(pos.west()); + if (this.visited.add(pos.above())) this.queue.add(pos.above()); + if (this.visited.add(pos.below())) this.queue.add(pos.below()); } + return null; } - return null; - } - - private BetterBlockPos heightmapLandingSpot(BetterBlockPos start) { - Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y)); - Set visited = new HashSet<>(); - LongOpenHashSet checkedPositions = new LongOpenHashSet(); - queue.add(start); - while (!queue.isEmpty()) { - BetterBlockPos qPos = queue.poll(); - - if (!isChunkLoaded(qPos)) continue; - - var height = ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, qPos.getX(), qPos.getZ()); - var pos = new BetterBlockPos(qPos.getX(), height+1, qPos.getZ()); + private BetterBlockPos advanceHeightmap(BetterBlockPos qPos) { + int height = ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, qPos.getX(), qPos.getZ()); + BetterBlockPos pos = new BetterBlockPos(qPos.getX(), height + 1, qPos.getZ()); if (isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) { - BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions); - if(actualLandingSpot != null) { + BetterBlockPos actualLandingSpot = checkLandingSpot(pos, this.checkedPositions); + if (actualLandingSpot != null) { landingColumnHeight = ctx.playerFeet().y - actualLandingSpot.y < LONG_LANDING_COLUMN_HEIGHT ? SHORT_LANDING_COLUMN_HEIGHT : LONG_LANDING_COLUMN_HEIGHT; if (hasAirBubble(actualLandingSpot.above(landingColumnHeight)) && !badLandingSpots.contains(actualLandingSpot.above(landingColumnHeight))) { return actualLandingSpot.above(landingColumnHeight); } } - - if (visited.add(pos.north())) queue.add(pos.north()); - if (visited.add(pos.east())) queue.add(pos.east()); - if (visited.add(pos.south())) queue.add(pos.south()); - if (visited.add(pos.west())) queue.add(pos.west()); + if (this.visited.add(pos.north())) this.queue.add(pos.north()); + if (this.visited.add(pos.east())) this.queue.add(pos.east()); + if (this.visited.add(pos.south())) this.queue.add(pos.south()); + if (this.visited.add(pos.west())) this.queue.add(pos.west()); } + return null; } - return null; } private NetherPathfinderContext getNpfContext() { From 62d4b25ad7ac1256945329c64a486cdb7632e2e7 Mon Sep 17 00:00:00 2001 From: underscore-zi Date: Mon, 27 Apr 2026 13:36:18 -0600 Subject: [PATCH 48/48] Don't block while attempting to solve angles --- .../process/elytra/ElytraBehavior.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 65419b822..9f69a6b4d 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -522,11 +522,17 @@ private void onTick0() { // Fetch the previous solution, regardless of if it's going to be used this.pendingSolution = null; if (this.solver != null) { - try { - this.pendingSolution = this.solver.get(50 * 5, TimeUnit.MILLISECONDS); - } catch (Exception ignored) { - // it doesn't matter if get() fails since the solution can just be recalculated synchronously - } finally { + if (this.solver.isDone()) { + try { + this.pendingSolution = this.solver.get(); + } catch (Exception ignored) { + // it doesn't matter if get() fails since the solution can just be recalculated synchronously + } finally { + this.solver = null; + } + } else { + // avoid wasting more cycles on a hard solution, we'll do the work synchronously + this.solver.cancel(true); this.solver = null; } } @@ -591,10 +597,10 @@ public void tick() { // If there's no previously calculated solution to use, or the context used at the end of last tick doesn't match this tick final Solution solution; - if (this.pendingSolution == null || !this.pendingSolution.context.equals(solverContext)) { - solution = this.solveAngles(solverContext); - } else { + if (this.pendingSolution != null && this.pendingSolution.context.equals(solverContext)) { solution = this.pendingSolution; + } else { + solution = this.solveAngles(solverContext); } if (this.deployedFireworkLastTick) { @@ -662,6 +668,7 @@ private Solution solveAngles(final SolverContext context) { int minStep = playerNear; for (int i = Math.min(playerNear + 20, path.size() - 1); i >= minStep; i--) { + if (Thread.interrupted()) return null; // cancelled by the game thread final List> candidates = new ArrayList<>(); for (int dy : heights) { if (relaxation == 0 || i == minStep) {