From 292eaef25e8949411b6f5399ff4a72fc0d59b592 Mon Sep 17 00:00:00 2001 From: scorbett123 <50634068+scorbett123@users.noreply.github.com> Date: Tue, 22 Aug 2023 16:52:53 +0100 Subject: [PATCH 1/9] Add support for nether vines --- .../movement/movements/MovementPillar.java | 15 ++++++++++++--- .../movement/movements/MovementTraverse.java | 8 ++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java index a11086d5b..6256c2260 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java @@ -65,7 +65,7 @@ protected Set calculateValidPositions() { public static double cost(CalculationContext context, int x, int y, int z) { BlockState fromState = context.get(x, y, z); Block from = fromState.getBlock(); - boolean ladder = from == Blocks.LADDER || from == Blocks.VINE; + boolean ladder = from == Blocks.LADDER || from == Blocks.VINE || from == Blocks.WEEPING_VINES || from == Blocks.TWISTING_VINES || from == Blocks.WEEPING_VINES_PLANT || from == Blocks.TWISTING_VINES_PLANT; BlockState fromDown = context.get(x, y - 1, z); if (!ladder) { if (fromDown.getBlock() == Blocks.LADDER || fromDown.getBlock() == Blocks.VINE) { @@ -116,7 +116,7 @@ public static double cost(CalculationContext context, int x, int y, int z) { return COST_INF; } if (hardness != 0) { - if (toBreakBlock == Blocks.LADDER || toBreakBlock == Blocks.VINE) { + if (toBreakBlock == Blocks.LADDER || toBreakBlock == Blocks.VINE || toBreakBlock == Blocks.WEEPING_VINES || toBreakBlock == Blocks.TWISTING_VINES) { hardness = 0; // we won't actually need to break the ladder / vine because we're going to use it } else { BlockState check = context.get(x, y + 3, z); // the block on top of the one we're going to break, could it fall on us? @@ -194,10 +194,12 @@ public MovementState updateState(MovementState state) { } boolean ladder = fromDown.getBlock() == Blocks.LADDER || fromDown.getBlock() == Blocks.VINE; boolean vine = fromDown.getBlock() == Blocks.VINE; + boolean nether_vine = fromDown.getBlock() == Blocks.WEEPING_VINES || fromDown.getBlock() == Blocks.TWISTING_VINES || fromDown.getBlock() == Blocks.WEEPING_VINES_PLANT || fromDown.getBlock() == Blocks.TWISTING_VINES_PLANT; + Rotation rotation = RotationUtils.calcRotationFromVec3d(ctx.playerHead(), VecUtils.getBlockPosCenter(positionToPlace), ctx.playerRotations()); - if (!ladder) { + if (!(ladder || nether_vine)) { state.setTarget(new MovementState.MovementTarget(ctx.playerRotations().withPitch(rotation.getPitch()), true)); } @@ -223,6 +225,13 @@ public MovementState updateState(MovementState state) { MovementHelper.moveTowards(ctx, state, against); return state; + } else if (nether_vine) { + if (ctx.playerFeet().equals(dest)) { + return state.setStatus(MovementStatus.SUCCESS); + } + MovementHelper.moveTowards(ctx, state, dest); + state.setInput(Input.JUMP, true); + return state; } else { // Get ready to place a throwaway block if (!((Baritone) baritone).getInventoryBehavior().selectThrowawayForLocation(true, src.x, src.y, src.z)) { diff --git a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java index cec3ca39a..3a2d25171 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java @@ -114,13 +114,13 @@ public static double cost(CalculationContext context, int x, int y, int z, int d } return WC; } - if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE) { + if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE || srcDownBlock == Blocks.WEEPING_VINES || srcDownBlock == Blocks.TWISTING_VINES || srcDownBlock == Blocks.WEEPING_VINES_PLANT || srcDownBlock == Blocks.TWISTING_VINES_PLANT) { hardness1 *= 5; hardness2 *= 5; } return WC + hardness1 + hardness2; } else {//this is a bridge, so we need to place a block - if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE) { + if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE || srcDownBlock == Blocks.WEEPING_VINES || srcDownBlock == Blocks.TWISTING_VINES || srcDownBlock == Blocks.WEEPING_VINES_PLANT || srcDownBlock == Blocks.TWISTING_VINES_PLANT) { return COST_INF; } if (MovementHelper.isReplaceable(destX, y - 1, destZ, destOn, context.bsi)) { @@ -217,7 +217,7 @@ public MovementState updateState(MovementState state) { state.setInput(Input.SNEAK, false); Block fd = BlockStateInterface.get(ctx, src.below()).getBlock(); - boolean ladder = fd == Blocks.LADDER || fd == Blocks.VINE; + boolean ladder = fd == Blocks.LADDER || fd == Blocks.VINE || fd == Blocks.WEEPING_VINES || fd == Blocks.TWISTING_VINES || fd == Blocks.WEEPING_VINES_PLANT || fd == Blocks.TWISTING_VINES_PLANT; if (pb0.getBlock() instanceof DoorBlock || pb1.getBlock() instanceof DoorBlock) { boolean notPassable = pb0.getBlock() instanceof DoorBlock && !MovementHelper.isDoorPassable(ctx, src, dest) || pb1.getBlock() instanceof DoorBlock && !MovementHelper.isDoorPassable(ctx, dest, src); @@ -370,7 +370,7 @@ public boolean safeToCancel(MovementState state) { protected boolean prepared(MovementState state) { if (ctx.playerFeet().equals(src) || ctx.playerFeet().equals(src.below())) { Block block = BlockStateInterface.getBlock(ctx, src.below()); - if (block == Blocks.LADDER || block == Blocks.VINE) { + if (block == Blocks.LADDER || block == Blocks.VINE || block == Blocks.WEEPING_VINES || block == Blocks.TWISTING_VINES || block == Blocks.WEEPING_VINES_PLANT || block == Blocks.TWISTING_VINES_PLANT) { state.setInput(Input.SNEAK, true); } } From 35862c8d121578f4b7e9352cd8d7acf199bced8a Mon Sep 17 00:00:00 2001 From: CompileRider Date: Wed, 10 Jun 2026 15:29:02 -0400 Subject: [PATCH 2/9] fix: use expandTowards for elytra collision swept volume Replace hitbox.inflate(motion.x, motion.y, motion.z) with hitbox.expandTowards(motion.x, motion.y, motion.z) in ElytraBehavior.simulate() to create a directional swept volume that prevents tunneling through 1-block-thick obstacles at high flight speeds. expandTowards handles negative motion vectors correctly (unlike inflate which collapses the AABB) and provides full swept volume coverage (unlike move which only checks the destination hitbox). Fixes #5049 Regression test for #5047 --- .../process/elytra/ElytraBehavior.java | 5 +- .../process/elytra/ElytraHitboxTest.java | 155 ++++++++++++++++++ 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/test/java/baritone/process/elytra/ElytraHitboxTest.java diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java index 6e78f4889..89729c9dd 100644 --- a/src/main/java/baritone/process/elytra/ElytraBehavior.java +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -1204,7 +1204,10 @@ private List simulate(final SolverContext context, final Vec3 goalDelta, f delta = delta.subtract(motion); // Collision box while the player is in motion, with additional padding for safety - final AABB inMotion = hitbox.move(motion.x, motion.y, motion.z).inflate(0.01); + // Use expandTowards for directional swept volume (fixes #5049) + // expandTowards handles negative vectors correctly (unlike inflate) + // and provides full swept volume coverage (unlike move) + final AABB inMotion = hitbox.expandTowards(motion.x, motion.y, motion.z).inflate(0.01); int xmin = fastFloor(inMotion.minX); int xmax = fastCeil(inMotion.maxX); diff --git a/src/test/java/baritone/process/elytra/ElytraHitboxTest.java b/src/test/java/baritone/process/elytra/ElytraHitboxTest.java new file mode 100644 index 000000000..83f8dbf2d --- /dev/null +++ b/src/test/java/baritone/process/elytra/ElytraHitboxTest.java @@ -0,0 +1,155 @@ +/* + * 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 net.minecraft.world.phys.AABB; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * Tests for AABB collision volume behavior in elytra flight simulation. + * Verifies that expandTowards produces correct swept volumes for all + * motion vector directions, preventing both tunneling (#5049) and + * hitbox collapse (#5047). + */ +public class ElytraHitboxTest { + + /** + * Test swept volume with positive motion vectors. + * expandTowards(2, 0, 3) should extend the AABB in +X and +Z directions. + */ + @Test + public void testPositiveMotionVectors() { + AABB hitbox = new AABB(0, 0, 0, 0.6, 1.8, 0.6); + AABB expanded = hitbox.expandTowards(2, 0, 3); + + // Expanded box should cover start and end positions + assertTrue("minX should be at or below start", expanded.minX <= hitbox.minX); + assertTrue("maxX should be at or above end", expanded.maxX >= hitbox.maxX + 2); + assertTrue("minZ should be at or below start", expanded.minZ <= hitbox.minZ); + assertTrue("maxZ should be at or above end", expanded.maxZ >= hitbox.maxZ + 3); + + // Y should remain unchanged (zero motion) + assertTrue("minY should be unchanged", expanded.minY == hitbox.minY); + assertTrue("maxY should be unchanged", expanded.maxY == hitbox.maxY); + + // AABB invariants must hold + assertTrue("minX < maxX", expanded.minX < expanded.maxX); + assertTrue("minY < maxY", expanded.minY < expanded.maxY); + assertTrue("minZ < maxZ", expanded.minZ < expanded.maxZ); + } + + /** + * Test swept volume with zero motion on one axis. + * expandTowards(2, 0, 0) should only extend X, not Y or Z. + */ + @Test + public void testZeroMotionAxis() { + AABB hitbox = new AABB(0, 0, 0, 0.6, 1.8, 0.6); + AABB expanded = hitbox.expandTowards(2, 0, 0); + + // X should extend + assertTrue("maxX should extend", expanded.maxX >= hitbox.maxX + 2); + + // Y and Z should remain unchanged + assertTrue("minY unchanged", expanded.minY == hitbox.minY); + assertTrue("maxY unchanged", expanded.maxY == hitbox.maxY); + assertTrue("minZ unchanged", expanded.minZ == hitbox.minZ); + assertTrue("maxZ unchanged", expanded.maxZ == hitbox.maxZ); + + // AABB invariants + assertTrue("minX < maxX", expanded.minX < expanded.maxX); + assertTrue("minY < maxY", expanded.minY < expanded.maxY); + assertTrue("minZ < maxZ", expanded.minZ < expanded.maxZ); + } + + /** + * Test swept volume with negative motion vectors. + * expandTowards(-2, -1, -3) should extend toward negative coordinates + * without collapsing. This is the regression test for #5047. + */ + @Test + public void testNegativeMotionVectors() { + AABB hitbox = new AABB(5, 10, 5, 5.6, 11.8, 5.6); + AABB expanded = hitbox.expandTowards(-2, -1, -3); + + // Should extend toward negative + assertTrue("minX should decrease", expanded.minX < hitbox.minX); + assertTrue("minY should decrease", expanded.minY < hitbox.minY); + assertTrue("minZ should decrease", expanded.minZ < hitbox.minZ); + + // AABB invariants MUST hold — this is the critical regression test + assertTrue("minX < maxX (no collapse)", expanded.minX < expanded.maxX); + assertTrue("minY < maxY (no collapse)", expanded.minY < expanded.maxY); + assertTrue("minZ < maxZ (no collapse)", expanded.minZ < expanded.maxZ); + } + + /** + * Test swept volume with mixed positive/negative motion. + * expandTowards(2, -1, 0) should handle mixed axes correctly. + */ + @Test + public void testMixedMotionVectors() { + AABB hitbox = new AABB(0, 5, 0, 0.6, 6.8, 0.6); + AABB expanded = hitbox.expandTowards(2, -1, 0); + + // X extends positive + assertTrue("maxX extends positive", expanded.maxX >= hitbox.maxX + 2); + + // Y extends negative + assertTrue("minY extends negative", expanded.minY < hitbox.minY); + + // Z unchanged + assertTrue("minZ unchanged", expanded.minZ == hitbox.minZ); + assertTrue("maxZ unchanged", expanded.maxZ == hitbox.maxZ); + + // AABB invariants + assertTrue("minX < maxX", expanded.minX < expanded.maxX); + assertTrue("minY < maxY", expanded.minY < expanded.maxY); + assertTrue("minZ < maxZ", expanded.minZ < expanded.maxZ); + } + + /** + * Test that expandTowards with the inflate(0.01) safety padding + * produces valid AABBs for all motion directions. + * This matches the actual usage in ElytraBehavior.simulate(). + */ + @Test + public void testExpandTowardsWithSafetyPadding() { + AABB hitbox = new AABB(0, 0, 0, 0.6, 1.8, 0.6); + + // Positive motion + AABB posMotion = hitbox.expandTowards(2, 0, 3).inflate(0.01); + assertTrue("Positive: minX < maxX", posMotion.minX < posMotion.maxX); + assertTrue("Positive: minY < maxY", posMotion.minY < posMotion.maxY); + assertTrue("Positive: minZ < maxZ", posMotion.minZ < posMotion.maxZ); + + // Negative motion + AABB negMotion = hitbox.expandTowards(-2, -1, -3).inflate(0.01); + assertTrue("Negative: minX < maxX", negMotion.minX < negMotion.maxX); + assertTrue("Negative: minY < maxY", negMotion.minY < negMotion.maxY); + assertTrue("Negative: minZ < maxZ", negMotion.minZ < negMotion.maxZ); + + // Zero motion + AABB zeroMotion = hitbox.expandTowards(0, 0, 0).inflate(0.01); + assertTrue("Zero: minX < maxX", zeroMotion.minX < zeroMotion.maxX); + assertTrue("Zero: minY < maxY", zeroMotion.minY < zeroMotion.maxY); + assertTrue("Zero: minZ < maxZ", zeroMotion.minZ < zeroMotion.maxZ); + } +} From e386ca562d3ecac42829dfaa9f85f0a2e5e5647f Mon Sep 17 00:00:00 2001 From: ZacSharp <68165024+ZacSharp@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:15:31 +0200 Subject: [PATCH 3/9] Climb ladders and vines by pressing space --- .../movement/movements/MovementPillar.java | 35 ++----------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java index 6256c2260..1e707d9c0 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java @@ -75,9 +75,6 @@ public static double cost(CalculationContext context, int x, int y, int z) { return COST_INF; // can't pillar up from a bottom slab onto a non ladder } } - if (from == Blocks.VINE && !hasAgainst(context, x, y, z)) { // TODO this vine can't be climbed, but we could place a pillar still since vines are replacable, no? perhaps the pillar jump would be impossible because of the slowdown actually. - return COST_INF; - } BlockState toBreak = context.get(x, y + 2, z); Block toBreakBlock = toBreak.getBlock(); if (toBreakBlock instanceof FenceGateBlock) { // see issue #172 @@ -145,13 +142,6 @@ public static double cost(CalculationContext context, int x, int y, int z) { } } - public static boolean hasAgainst(CalculationContext context, int x, int y, int z) { - return MovementHelper.isBlockNormalCube(context.get(x + 1, y, z)) || - MovementHelper.isBlockNormalCube(context.get(x - 1, y, z)) || - MovementHelper.isBlockNormalCube(context.get(x, y, z + 1)) || - MovementHelper.isBlockNormalCube(context.get(x, y, z - 1)); - } - public static BlockPos getAgainst(CalculationContext context, BetterBlockPos vine) { if (MovementHelper.isBlockNormalCube(context.get(vine.north()))) { return vine.north(); @@ -193,7 +183,6 @@ public MovementState updateState(MovementState state) { return state; } boolean ladder = fromDown.getBlock() == Blocks.LADDER || fromDown.getBlock() == Blocks.VINE; - boolean vine = fromDown.getBlock() == Blocks.VINE; boolean nether_vine = fromDown.getBlock() == Blocks.WEEPING_VINES || fromDown.getBlock() == Blocks.TWISTING_VINES || fromDown.getBlock() == Blocks.WEEPING_VINES_PLANT || fromDown.getBlock() == Blocks.TWISTING_VINES_PLANT; Rotation rotation = RotationUtils.calcRotationFromVec3d(ctx.playerHead(), @@ -204,31 +193,11 @@ public MovementState updateState(MovementState state) { } boolean blockIsThere = MovementHelper.canWalkOn(ctx, src) || ladder; - if (ladder) { - BlockPos against = vine ? getAgainst(new CalculationContext(baritone), src) : src.relative(fromDown.getValue(LadderBlock.FACING).getOpposite()); - if (against == null) { - logDirect("Unable to climb vines. Consider disabling allowVines."); - return state.setStatus(MovementStatus.UNREACHABLE); - } - - if (ctx.playerFeet().equals(against.above()) || ctx.playerFeet().equals(dest)) { - return state.setStatus(MovementStatus.SUCCESS); - } - if (MovementHelper.isBottomSlab(BlockStateInterface.get(ctx, src.below()))) { - state.setInput(Input.JUMP, true); - } - /* - if (thePlayer.getPosition0().getX() != from.getX() || thePlayer.getPosition0().getZ() != from.getZ()) { - Baritone.moveTowardsBlock(from); - } - */ - - MovementHelper.moveTowards(ctx, state, against); - return state; - } else if (nether_vine) { + if (ladder || nether_vine) { if (ctx.playerFeet().equals(dest)) { return state.setStatus(MovementStatus.SUCCESS); } + MovementHelper.moveTowards(ctx, state, dest); state.setInput(Input.JUMP, true); return state; From 694edaeb1543ee03f2b728d5ed81f1b641f992aa Mon Sep 17 00:00:00 2001 From: ZacSharp <68165024+ZacSharp@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:17:51 +0200 Subject: [PATCH 4/9] Remove unused function --- src/main/java/baritone/pathing/movement/MovementHelper.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index 1d97a79c5..a6b36de31 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -330,11 +330,6 @@ static boolean isReplaceable(int x, int y, int z, BlockState state, BlockStateIn return state.getMaterial().isReplaceable(); } - @Deprecated - static boolean isReplacable(int x, int y, int z, BlockState state, BlockStateInterface bsi) { - return isReplaceable(x, y, z, state, bsi); - } - static boolean isDoorPassable(IPlayerContext ctx, BlockPos doorPos, BlockPos playerPos) { if (playerPos.equals(doorPos)) { return false; From ba2d0abe0a6c60d9d637090d3a6f38bc6bd332f9 Mon Sep 17 00:00:00 2001 From: ZacSharp <68165024+ZacSharp@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:58:28 +0200 Subject: [PATCH 5/9] New helper function to detect vines/ladders as such --- .../baritone/pathing/movement/MovementHelper.java | 12 ++++++++++++ .../pathing/movement/movements/MovementPillar.java | 13 ++++++------- .../movement/movements/MovementTraverse.java | 8 ++++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index a6b36de31..d39724424 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -581,6 +581,18 @@ static boolean canPlaceAgainst(BlockStateInterface bsi, int x, int y, int z, Blo return isBlockNormalCube(state) || state.getBlock() == Blocks.GLASS || state.getBlock() instanceof StainedGlassBlock; } + /** + * Can we climb up this block by pressing space while inside it? + */ + static boolean isClimbable(Block block) { + return block == Blocks.LADDER + || block == Blocks.VINE + || block == Blocks.WEEPING_VINES + || block == Blocks.WEEPING_VINES_PLANT + || block == Blocks.TWISTING_VINES + || block == Blocks.TWISTING_VINES_PLANT; + } + static double getMiningDurationTicks(CalculationContext context, int x, int y, int z, boolean includeFalling) { return getMiningDurationTicks(context, x, y, z, context.get(x, y, z), includeFalling); } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java index 1e707d9c0..2be1a4492 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java @@ -65,10 +65,10 @@ protected Set calculateValidPositions() { public static double cost(CalculationContext context, int x, int y, int z) { BlockState fromState = context.get(x, y, z); Block from = fromState.getBlock(); - boolean ladder = from == Blocks.LADDER || from == Blocks.VINE || from == Blocks.WEEPING_VINES || from == Blocks.TWISTING_VINES || from == Blocks.WEEPING_VINES_PLANT || from == Blocks.TWISTING_VINES_PLANT; + boolean ladder = MovementHelper.isClimbable(from); BlockState fromDown = context.get(x, y - 1, z); if (!ladder) { - if (fromDown.getBlock() == Blocks.LADDER || fromDown.getBlock() == Blocks.VINE) { + if (MovementHelper.isClimbable(fromDown.getBlock())) { return COST_INF; // can't pillar from a ladder or vine onto something that isn't also climbable } if (fromDown.getBlock() instanceof SlabBlock && fromDown.getValue(SlabBlock.TYPE) == SlabType.BOTTOM) { @@ -113,7 +113,7 @@ public static double cost(CalculationContext context, int x, int y, int z) { return COST_INF; } if (hardness != 0) { - if (toBreakBlock == Blocks.LADDER || toBreakBlock == Blocks.VINE || toBreakBlock == Blocks.WEEPING_VINES || toBreakBlock == Blocks.TWISTING_VINES) { + if (MovementHelper.isClimbable(toBreakBlock)) { hardness = 0; // we won't actually need to break the ladder / vine because we're going to use it } else { BlockState check = context.get(x, y + 3, z); // the block on top of the one we're going to break, could it fall on us? @@ -182,18 +182,17 @@ public MovementState updateState(MovementState state) { } return state; } - boolean ladder = fromDown.getBlock() == Blocks.LADDER || fromDown.getBlock() == Blocks.VINE; - boolean nether_vine = fromDown.getBlock() == Blocks.WEEPING_VINES || fromDown.getBlock() == Blocks.TWISTING_VINES || fromDown.getBlock() == Blocks.WEEPING_VINES_PLANT || fromDown.getBlock() == Blocks.TWISTING_VINES_PLANT; + boolean ladder = MovementHelper.isClimbable(fromDown.getBlock()); Rotation rotation = RotationUtils.calcRotationFromVec3d(ctx.playerHead(), VecUtils.getBlockPosCenter(positionToPlace), ctx.playerRotations()); - if (!(ladder || nether_vine)) { + if (!ladder) { state.setTarget(new MovementState.MovementTarget(ctx.playerRotations().withPitch(rotation.getPitch()), true)); } boolean blockIsThere = MovementHelper.canWalkOn(ctx, src) || ladder; - if (ladder || nether_vine) { + if (ladder) { if (ctx.playerFeet().equals(dest)) { return state.setStatus(MovementStatus.SUCCESS); } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java index 3a2d25171..461a6ee0c 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java @@ -114,13 +114,13 @@ public static double cost(CalculationContext context, int x, int y, int z, int d } return WC; } - if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE || srcDownBlock == Blocks.WEEPING_VINES || srcDownBlock == Blocks.TWISTING_VINES || srcDownBlock == Blocks.WEEPING_VINES_PLANT || srcDownBlock == Blocks.TWISTING_VINES_PLANT) { + if (MovementHelper.isClimbable(srcDownBlock)) { hardness1 *= 5; hardness2 *= 5; } return WC + hardness1 + hardness2; } else {//this is a bridge, so we need to place a block - if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE || srcDownBlock == Blocks.WEEPING_VINES || srcDownBlock == Blocks.TWISTING_VINES || srcDownBlock == Blocks.WEEPING_VINES_PLANT || srcDownBlock == Blocks.TWISTING_VINES_PLANT) { + if (MovementHelper.isClimbable(srcDownBlock)) { return COST_INF; } if (MovementHelper.isReplaceable(destX, y - 1, destZ, destOn, context.bsi)) { @@ -217,7 +217,7 @@ public MovementState updateState(MovementState state) { state.setInput(Input.SNEAK, false); Block fd = BlockStateInterface.get(ctx, src.below()).getBlock(); - boolean ladder = fd == Blocks.LADDER || fd == Blocks.VINE || fd == Blocks.WEEPING_VINES || fd == Blocks.TWISTING_VINES || fd == Blocks.WEEPING_VINES_PLANT || fd == Blocks.TWISTING_VINES_PLANT; + boolean ladder = MovementHelper.isClimbable(fd); if (pb0.getBlock() instanceof DoorBlock || pb1.getBlock() instanceof DoorBlock) { boolean notPassable = pb0.getBlock() instanceof DoorBlock && !MovementHelper.isDoorPassable(ctx, src, dest) || pb1.getBlock() instanceof DoorBlock && !MovementHelper.isDoorPassable(ctx, dest, src); @@ -370,7 +370,7 @@ public boolean safeToCancel(MovementState state) { protected boolean prepared(MovementState state) { if (ctx.playerFeet().equals(src) || ctx.playerFeet().equals(src.below())) { Block block = BlockStateInterface.getBlock(ctx, src.below()); - if (block == Blocks.LADDER || block == Blocks.VINE || block == Blocks.WEEPING_VINES || block == Blocks.TWISTING_VINES || block == Blocks.WEEPING_VINES_PLANT || block == Blocks.TWISTING_VINES_PLANT) { + if (MovementHelper.isClimbable(block)) { state.setInput(Input.SNEAK, true); } } From 94ce367a12e1ee3a1ada3eba94d453f3790b85e9 Mon Sep 17 00:00:00 2001 From: ZacSharp <68165024+ZacSharp@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:04:02 +0200 Subject: [PATCH 6/9] Make more things aware of nether vines --- src/main/java/baritone/pathing/movement/MovementHelper.java | 4 +++- .../baritone/pathing/movement/movements/MovementAscend.java | 2 +- .../baritone/pathing/movement/movements/MovementDescend.java | 2 +- .../baritone/pathing/movement/movements/MovementDiagonal.java | 4 ++-- .../baritone/pathing/movement/movements/MovementParkour.java | 2 +- .../baritone/pathing/movement/movements/MovementPillar.java | 2 +- .../baritone/pathing/movement/movements/MovementTraverse.java | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index d39724424..41e6f09f3 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -522,7 +522,7 @@ static boolean canUseFrostWalker(IPlayerContext ctx, BlockPos pos) { */ static boolean mustBeSolidToWalkOn(CalculationContext context, int x, int y, int z, BlockState state) { Block block = state.getBlock(); - if (block == Blocks.LADDER || block == Blocks.VINE) { + if (isClimbable(block)) { return false; } if (!state.getFluidState().isEmpty()) { @@ -583,6 +583,8 @@ static boolean canPlaceAgainst(BlockStateInterface bsi, int x, int y, int z, Blo /** * Can we climb up this block by pressing space while inside it? + * Also doubles as "If I start a movement on this, can weird things happen?" + * because movements can end/start on these blocks despite them not being canWalkOn. */ static boolean isClimbable(Block block) { return block == Blocks.LADDER diff --git a/src/main/java/baritone/pathing/movement/movements/MovementAscend.java b/src/main/java/baritone/pathing/movement/movements/MovementAscend.java index 717cd2e70..c8f2a57f8 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementAscend.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementAscend.java @@ -112,7 +112,7 @@ public static double cost(CalculationContext context, int x, int y, int z, int d // and in that scenario, when we arrive and break srcUp2, that lets srcUp3 fall on us and suffocate us } BlockState srcDown = context.get(x, y - 1, z); - if (srcDown.getBlock() == Blocks.LADDER || srcDown.getBlock() == Blocks.VINE) { + if (MovementHelper.isClimbable(srcDown.getBlock())) { return COST_INF; } // we can jump from soul sand, but not from a bottom slab diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java index 07d6d7d01..cba098cbf 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java @@ -93,7 +93,7 @@ public static void cost(CalculationContext context, int x, int y, int z, int des } Block fromDown = context.get(x, y - 1, z).getBlock(); - if (fromDown == Blocks.LADDER || fromDown == Blocks.VINE) { + if (MovementHelper.isClimbable(fromDown)) { return; } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java b/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java index cf387d5c9..c5acce62f 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java @@ -148,7 +148,7 @@ public static void cost(CalculationContext context, int x, int y, int z, int des multiplier += context.walkOnWaterOnePenalty * SQRT_2; } Block fromDownBlock = fromDown.getBlock(); - if (fromDownBlock == Blocks.LADDER || fromDownBlock == Blocks.VINE) { + if (MovementHelper.isClimbable(fromDownBlock)) { return; } if (fromDownBlock == Blocks.SOUL_SAND) { @@ -228,7 +228,7 @@ public static void cost(CalculationContext context, int x, int y, int z, int des } if (optionA != 0 || optionB != 0) { multiplier *= SQRT_2 - 0.001; // TODO tune - if (startIn == Blocks.LADDER || startIn == Blocks.VINE) { + if (MovementHelper.isClimbable(startIn)) { // edging around doesn't work if doing so would climb a ladder or vine instead of moving sideways return; } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java index c46cf68bd..1ee987884 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java @@ -92,7 +92,7 @@ public static void cost(CalculationContext context, int x, int y, int z, Directi return; } BlockState standingOn = context.get(x, y - 1, z); - if (standingOn.getBlock() == Blocks.VINE || standingOn.getBlock() == Blocks.LADDER || standingOn.getBlock() instanceof StairBlock || MovementHelper.isBottomSlab(standingOn)) { + if (MovementHelper.isClimbable(standingOn.getBlock()) || standingOn.getBlock() instanceof StairBlock || MovementHelper.isBottomSlab(standingOn)) { return; } // we can't jump from (frozen) water with assumeWalkOnWater because we can't be sure it will be frozen diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java index 2be1a4492..2df243371 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java @@ -258,7 +258,7 @@ public MovementState updateState(MovementState state) { protected boolean prepared(MovementState state) { if (ctx.playerFeet().equals(src) || ctx.playerFeet().equals(src.below())) { Block block = BlockStateInterface.getBlock(ctx, src.below()); - if (block == Blocks.LADDER || block == Blocks.VINE) { + if (MovementHelper.isClimbable(block)) { state.setInput(Input.SNEAK, true); } } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java index 461a6ee0c..7bd94a42a 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java @@ -261,7 +261,7 @@ public MovementState updateState(MovementState state) { } Block low = BlockStateInterface.get(ctx, src).getBlock(); Block high = BlockStateInterface.get(ctx, src.above()).getBlock(); - if (ctx.player().position().y > src.y + 0.1D && !ctx.player().isOnGround() && (low == Blocks.VINE || low == Blocks.LADDER || high == Blocks.VINE || high == Blocks.LADDER)) { + if (ctx.player().position().y > src.y + 0.1D && !ctx.player().isOnGround() && (MovementHelper.isClimbable(low) || MovementHelper.isClimbable(high))) { // hitting W could cause us to climb the ladder instead of going forward // wait until we're on the ground return state; From 47710e0a08f2918a470b9edc5aa8bcb6136268c6 Mon Sep 17 00:00:00 2001 From: ZacSharp <68165024+ZacSharp@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:12:32 +0200 Subject: [PATCH 7/9] Allow falling through nether vines --- .../baritone/pathing/movement/movements/MovementDescend.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java index cba098cbf..4a658df38 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java @@ -185,7 +185,7 @@ public static boolean dynamicFallCost(CalculationContext context, int x, int y, res.cost = tentativeCost; return false; } - if (unprotectedFallHeight <= 11 && (ontoBlock.getBlock() == Blocks.VINE || ontoBlock.getBlock() == Blocks.LADDER)) { + if (unprotectedFallHeight <= 11 && MovementHelper.isClimbable(ontoBlock.getBlock())) { // if fall height is greater than or equal to 11, we don't actually grab on to vines or ladders. the more you know // this effectively "resets" our falling speed costSoFar += FALL_N_BLOCKS_COST[unprotectedFallHeight - 1];// we fall until the top of this block (not including this block) From 5e222971e9d5f543261dec9e2735cea2e8a807fa Mon Sep 17 00:00:00 2001 From: ZacSharp <68165024+ZacSharp@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:25:24 +0200 Subject: [PATCH 8/9] Traverse ladders and vines by pressing space --- .../movement/movements/MovementPillar.java | 16 ---------------- .../movement/movements/MovementTraverse.java | 11 +++-------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java index 2df243371..883027141 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java @@ -142,22 +142,6 @@ public static double cost(CalculationContext context, int x, int y, int z) { } } - public static BlockPos getAgainst(CalculationContext context, BetterBlockPos vine) { - if (MovementHelper.isBlockNormalCube(context.get(vine.north()))) { - return vine.north(); - } - if (MovementHelper.isBlockNormalCube(context.get(vine.south()))) { - return vine.south(); - } - if (MovementHelper.isBlockNormalCube(context.get(vine.east()))) { - return vine.east(); - } - if (MovementHelper.isBlockNormalCube(context.get(vine.west()))) { - return vine.west(); - } - return null; - } - @Override public MovementState updateState(MovementState state) { super.updateState(state); diff --git a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java index 7bd94a42a..28c7b3a2a 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java @@ -274,15 +274,10 @@ public MovementState updateState(MovementState state) { } BlockState destDown = BlockStateInterface.get(ctx, dest.below()); - BlockPos against = positionsToBreak[0]; - if (feet.getY() != dest.getY() && ladder && (destDown.getBlock() == Blocks.VINE || destDown.getBlock() == Blocks.LADDER)) { - against = destDown.getBlock() == Blocks.VINE ? MovementPillar.getAgainst(new CalculationContext(baritone), dest.below()) : dest.relative(destDown.getValue(LadderBlock.FACING).getOpposite()); - if (against == null) { - logDirect("Unable to climb vines. Consider disabling allowVines."); - return state.setStatus(MovementStatus.UNREACHABLE); - } + if (feet.getY() != dest.getY() && ladder && MovementHelper.isClimbable(destDown.getBlock())) { + state.setInput(Input.JUMP, true); } - MovementHelper.moveTowards(ctx, state, against); + MovementHelper.moveTowards(ctx, state, positionsToBreak[0]); return state; } else { wasTheBridgeBlockAlwaysThere = false; From e286150e832e635427c0320bc682232be4aab547 Mon Sep 17 00:00:00 2001 From: ZacSharp <68165024+ZacSharp@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:27:56 +0200 Subject: [PATCH 9/9] apply `allowVines` to nether vines as well --- src/main/java/baritone/pathing/movement/MovementHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index 41e6f09f3..08477ec76 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -421,7 +421,7 @@ static Ternary canWalkOnBlockState(BlockState state) { if (block instanceof AzaleaBlock) { return YES; } - if (block == Blocks.LADDER || (block == Blocks.VINE && Baritone.settings().allowVines.value)) { // TODO reconsider this + if (block == Blocks.LADDER || (isClimbable(block) && Baritone.settings().allowVines.value)) { // TODO reconsider this return YES; } if (block == Blocks.FARMLAND || block == Blocks.DIRT_PATH) {