diff --git a/FEATURES.md b/FEATURES.md
index 998b546a..98bae9e2 100644
--- a/FEATURES.md
+++ b/FEATURES.md
@@ -9,6 +9,7 @@
- **Slabs and stairs**
- **Falling blocks** Baritone understands the costs of breaking blocks with falling blocks on top, and includes all of their break costs. Additionally, since it avoids breaking any blocks touching a liquid, it won't break the bottom of a gravel stack below a lava lake (anymore).
- **Avoiding dangerous blocks** Obviously, it knows not to walk through fire or on magma, not to corner over lava (that deals some damage), not to break any blocks touching a liquid (it might drown), etc.
+- **Parkour** Sprint jumping over 1, 2, or 3 block gaps
# Pathing method
Baritone uses a modified version of A*.
@@ -41,8 +42,7 @@ Things it doesn't have yet
See issues for more.
Things it may not ever have, from most likely to least likely =(
-- Parkour (jumping over gaps of any length)
-- Boats
- Pigs
+- Boats
- Horses (2x3 path instead of 1x2)
- Elytra
diff --git a/src/main/java/baritone/Settings.java b/src/main/java/baritone/Settings.java
index 1eed4333..5bb57549 100644
--- a/src/main/java/baritone/Settings.java
+++ b/src/main/java/baritone/Settings.java
@@ -83,6 +83,11 @@ public class Settings {
*/
public Setting allowWalkOnBottomSlab = new Setting<>(true);
+ /**
+ * You know what it is
+ */
+ public Setting allowParkour = new Setting<>(true); // disable in release because its sketchy af lol
+
/**
* For example, if you have Mining Fatigue or Haste, adjust the costs of breaking blocks accordingly.
*/
diff --git a/src/main/java/baritone/pathing/calc/AStarPathFinder.java b/src/main/java/baritone/pathing/calc/AStarPathFinder.java
index 73442998..893d9686 100644
--- a/src/main/java/baritone/pathing/calc/AStarPathFinder.java
+++ b/src/main/java/baritone/pathing/calc/AStarPathFinder.java
@@ -233,7 +233,11 @@ public class AStarPathFinder extends AbstractNodeCostSearch implements Helper {
new MovementDiagonal(pos, EnumFacing.NORTH, EnumFacing.EAST),
new MovementDiagonal(pos, EnumFacing.SOUTH, EnumFacing.WEST),
new MovementDiagonal(pos, EnumFacing.SOUTH, EnumFacing.EAST),
- new MovementPillar(pos, new BetterBlockPos(x, y + 1, z))
+ new MovementPillar(pos, new BetterBlockPos(x, y + 1, z)),
+ MovementParkour.calculate(pos, EnumFacing.NORTH),
+ MovementParkour.calculate(pos, EnumFacing.SOUTH),
+ MovementParkour.calculate(pos, EnumFacing.EAST),
+ MovementParkour.calculate(pos, EnumFacing.WEST),
};
}
diff --git a/src/main/java/baritone/pathing/movement/Movement.java b/src/main/java/baritone/pathing/movement/Movement.java
index 5eba39ab..76871934 100644
--- a/src/main/java/baritone/pathing/movement/Movement.java
+++ b/src/main/java/baritone/pathing/movement/Movement.java
@@ -84,6 +84,10 @@ public abstract class Movement implements Helper, MovementHelper {
return getCost(null);
}
+ protected void override(double cost) {
+ this.cost = cost;
+ }
+
public double calculateCostWithoutCaching() {
return calculateCost(new CalculationContext());
}
diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java
index 6897d5a4..7c9a8c36 100644
--- a/src/main/java/baritone/pathing/movement/MovementHelper.java
+++ b/src/main/java/baritone/pathing/movement/MovementHelper.java
@@ -70,6 +70,9 @@ public interface MovementHelper extends ActionCosts, Helper {
static boolean canWalkThrough(BlockPos pos, IBlockState state) {
Block block = state.getBlock();
+ if (block == Blocks.AIR) {
+ return true;
+ }
if (block instanceof BlockFire
|| block instanceof BlockTripWire
|| block instanceof BlockWeb
@@ -106,6 +109,37 @@ public interface MovementHelper extends ActionCosts, Helper {
return block.isPassable(mc.world, pos);
}
+ /**
+ * canWalkThrough but also won't impede movement at all. so not including doors or fence gates (we'd have to right click),
+ * not including water, and not including ladders or vines or cobwebs (they slow us down)
+ *
+ * @return
+ */
+ static boolean fullyPassable(BlockPos pos) {
+ return fullyPassable(pos, BlockStateInterface.get(pos));
+ }
+
+ static boolean fullyPassable(BlockPos pos, IBlockState state) {
+ Block block = state.getBlock();
+ if (block == Blocks.AIR) {
+ return true;
+ }
+ if (block == Blocks.FIRE
+ || block == Blocks.TRIPWIRE
+ || block == Blocks.WEB
+ || block == Blocks.VINE
+ || block == Blocks.LADDER
+ || block instanceof BlockDoor
+ || block instanceof BlockFenceGate
+ || block instanceof BlockSnow
+ || block instanceof BlockLiquid
+ || block instanceof BlockTrapDoor
+ || block instanceof BlockEndPortal) {
+ return false;
+ }
+ return block.isPassable(mc.world, pos);
+ }
+
static boolean isReplacable(BlockPos pos, IBlockState state) {
// for MovementTraverse and MovementAscend
// block double plant defaults to true when the block doesn't match, so don't need to check that case
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java
new file mode 100644
index 00000000..3749e6ec
--- /dev/null
+++ b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java
@@ -0,0 +1,151 @@
+/*
+ * This file is part of Baritone.
+ *
+ * Baritone is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Baritone. If not, see .
+ */
+
+package baritone.pathing.movement.movements;
+
+import baritone.Baritone;
+import baritone.pathing.movement.CalculationContext;
+import baritone.pathing.movement.Movement;
+import baritone.pathing.movement.MovementHelper;
+import baritone.pathing.movement.MovementState;
+import baritone.utils.BlockStateInterface;
+import baritone.utils.InputOverrideHandler;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.init.Blocks;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
+
+public class MovementParkour extends Movement {
+
+ final EnumFacing direction;
+ final int dist;
+
+ private MovementParkour(BlockPos src, int dist, EnumFacing dir) {
+ super(src, src.offset(dir, dist), new BlockPos[]{});
+ this.direction = dir;
+ this.dist = dist;
+ super.override(costFromJumpDistance(dist));
+ }
+
+ public static MovementParkour calculate(BlockPos src, EnumFacing dir) {
+ if (!Baritone.settings().allowParkour.get()) {
+ return null;
+ }
+ IBlockState standingOn = BlockStateInterface.get(src.down());
+ if (standingOn.getBlock() == Blocks.VINE || standingOn.getBlock() == Blocks.LADDER || MovementHelper.isBottomSlab(standingOn)) {
+ return null;
+ }
+ BlockPos adjBlock = src.down().offset(dir);
+ IBlockState adj = BlockStateInterface.get(adjBlock);
+ if (MovementHelper.avoidWalkingInto(adj.getBlock())) { // magma sucks
+ return null;
+ }
+ if (MovementHelper.canWalkOn(adjBlock, adj)) { // don't parkour if we could just traverse (for now)
+ return null;
+ }
+
+ if (!MovementHelper.fullyPassable(src.offset(dir))) {
+ return null;
+ }
+ if (!MovementHelper.fullyPassable(src.up().offset(dir))) {
+ return null;
+ }
+ for (int i = 2; i <= 4; i++) {
+ BlockPos dest = src.offset(dir, i);
+ if (!MovementHelper.fullyPassable(dest)) {
+ return null;
+ }
+ if (!MovementHelper.fullyPassable(dest.up())) {
+ return null;
+ }
+ if (MovementHelper.canWalkOn(dest.down())) {
+ return new MovementParkour(src, i, dir);
+ }
+ }
+ return null;
+ }
+
+ private static double costFromJumpDistance(int dist) {
+ switch (dist) {
+ case 2:
+ return WALK_ONE_BLOCK_COST * 2; // IDK LOL
+ case 3:
+ return WALK_ONE_BLOCK_COST * 3;
+ case 4:
+ return SPRINT_ONE_BLOCK_COST * 3;
+ }
+ throw new IllegalStateException("LOL");
+ }
+
+
+ @Override
+ protected double calculateCost(CalculationContext context) {
+ if (!MovementHelper.canWalkOn(dest.down())) {
+ return COST_INF;
+ }
+ if (MovementHelper.avoidWalkingInto(BlockStateInterface.get(src.down().offset(direction)).getBlock())) {
+ return COST_INF;
+ }
+ for (int i = 1; i <= 4; i++) {
+ BlockPos d = src.offset(direction, i);
+ if (!MovementHelper.fullyPassable(d)) {
+ return COST_INF;
+ }
+ if (!MovementHelper.fullyPassable(d.up())) {
+ return COST_INF;
+ }
+ if (d.equals(dest)) {
+ return costFromJumpDistance(i);
+ }
+ }
+ throw new IllegalStateException("invalid jump distance?");
+ }
+
+ @Override
+ public MovementState updateState(MovementState state) {
+ super.updateState(state);
+ switch (state.getStatus()) {
+ case WAITING:
+ state.setStatus(MovementState.MovementStatus.RUNNING);
+ case RUNNING:
+ break;
+ default:
+ return state;
+ }
+ if (dist >= 4) {
+ state.setInput(InputOverrideHandler.Input.SPRINT, true);
+ }
+ MovementHelper.moveTowards(state, dest);
+ if (playerFeet().equals(dest)) {
+ if (player().posY - playerFeet().getY() < 0.01) {
+ state.setStatus(MovementState.MovementStatus.SUCCESS);
+ }
+ } else if (!playerFeet().equals(src)) {
+ if (playerFeet().equals(src.offset(direction)) || player().posY - playerFeet().getY() > 0.0001) {
+ state.setInput(InputOverrideHandler.Input.JUMP, true);
+ } else {
+ state.setInput(InputOverrideHandler.Input.SPRINT, false);
+ if (playerFeet().equals(src.offset(direction, -1))) {
+ MovementHelper.moveTowards(state, src);
+ } else {
+ MovementHelper.moveTowards(state, src.offset(direction, -1));
+ }
+ }
+ }
+ return state;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/baritone/utils/pathing/BetterBlockPos.java b/src/main/java/baritone/utils/pathing/BetterBlockPos.java
index ec8891f4..6a05c109 100644
--- a/src/main/java/baritone/utils/pathing/BetterBlockPos.java
+++ b/src/main/java/baritone/utils/pathing/BetterBlockPos.java
@@ -119,4 +119,10 @@ public final class BetterBlockPos extends BlockPos {
Vec3i vec = dir.getDirectionVec();
return new BetterBlockPos(x + vec.getX(), y + vec.getY(), z + vec.getZ());
}
+
+ @Override
+ public BlockPos offset(EnumFacing dir, int dist) {
+ Vec3i vec = dir.getDirectionVec();
+ return new BetterBlockPos(x + vec.getX() * dist, y + vec.getY() * dist, z + vec.getZ() * dist);
+ }
}