diff --git a/src/main/java/baritone/pathing/movement/Moves.java b/src/main/java/baritone/pathing/movement/Moves.java
index da278436f..6da1a3642 100644
--- a/src/main/java/baritone/pathing/movement/Moves.java
+++ b/src/main/java/baritone/pathing/movement/Moves.java
@@ -276,6 +276,102 @@ public void apply(CalculationContext context, int x, int y, int z, MutableMoveRe
}
},
+ KNIGHT_NORTH_EAST(+1, 0, -2) {
+ @Override
+ public Movement apply0(CalculationContext context, BetterBlockPos src) {
+ return new MovementKnight(context.getBaritone(), src, EnumFacing.NORTH, EnumFacing.EAST);
+ }
+
+ @Override
+ public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
+ MovementKnight.cost(context, x, y, z, x + xOffset, z + zOffset, result);
+ }
+ },
+
+ KNIGHT_NORTH_WEST(-1, 0, -2) {
+ @Override
+ public Movement apply0(CalculationContext context, BetterBlockPos src) {
+ return new MovementKnight(context.getBaritone(), src, EnumFacing.NORTH, EnumFacing.WEST);
+ }
+
+ @Override
+ public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
+ MovementKnight.cost(context, x, y, z, x + xOffset, z + zOffset, result);
+ }
+ },
+
+ KNIGHT_SOUTH_EAST(+1, 0, +2) {
+ @Override
+ public Movement apply0(CalculationContext context, BetterBlockPos src) {
+ return new MovementKnight(context.getBaritone(), src, EnumFacing.SOUTH, EnumFacing.EAST);
+ }
+
+ @Override
+ public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
+ MovementKnight.cost(context, x, y, z, x + xOffset, z + zOffset, result);
+ }
+ },
+
+ KNIGHT_SOUTH_WEST(-1, 0, +2) {
+ @Override
+ public Movement apply0(CalculationContext context, BetterBlockPos src) {
+ return new MovementKnight(context.getBaritone(), src, EnumFacing.SOUTH, EnumFacing.WEST);
+ }
+
+ @Override
+ public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
+ MovementKnight.cost(context, x, y, z, x + xOffset, z + zOffset, result);
+ }
+ },
+
+ KNIGHT_EAST_NORTH(+2, 0, -1) {
+ @Override
+ public Movement apply0(CalculationContext context, BetterBlockPos src) {
+ return new MovementKnight(context.getBaritone(), src, EnumFacing.EAST, EnumFacing.NORTH);
+ }
+
+ @Override
+ public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
+ MovementKnight.cost(context, x, y, z, x + xOffset, z + zOffset, result);
+ }
+ },
+
+ KNIGHT_WEST_NORTH(-2, 0, -1) {
+ @Override
+ public Movement apply0(CalculationContext context, BetterBlockPos src) {
+ return new MovementKnight(context.getBaritone(), src, EnumFacing.WEST, EnumFacing.NORTH);
+ }
+
+ @Override
+ public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
+ MovementKnight.cost(context, x, y, z, x + xOffset, z + zOffset, result);
+ }
+ },
+
+ KNIGHT_EAST_SOUTH(+2, 0, +1) {
+ @Override
+ public Movement apply0(CalculationContext context, BetterBlockPos src) {
+ return new MovementKnight(context.getBaritone(), src, EnumFacing.EAST, EnumFacing.SOUTH);
+ }
+
+ @Override
+ public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
+ MovementKnight.cost(context, x, y, z, x + xOffset, z + zOffset, result);
+ }
+ },
+
+ KNIGHT_WEST_SOUTH(-2, 0, +1) {
+ @Override
+ public Movement apply0(CalculationContext context, BetterBlockPos src) {
+ return new MovementKnight(context.getBaritone(), src, EnumFacing.WEST, EnumFacing.SOUTH);
+ }
+
+ @Override
+ public void apply(CalculationContext context, int x, int y, int z, MutableMoveResult result) {
+ MovementKnight.cost(context, x, y, z, x + xOffset, z + zOffset, result);
+ }
+ },
+
PARKOUR_NORTH(0, 0, -4, true, true) {
@Override
public Movement apply0(CalculationContext context, BetterBlockPos src) {
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementKnight.java b/src/main/java/baritone/pathing/movement/movements/MovementKnight.java
new file mode 100644
index 000000000..0990e21d3
--- /dev/null
+++ b/src/main/java/baritone/pathing/movement/movements/MovementKnight.java
@@ -0,0 +1,262 @@
+/*
+ * 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.pathing.movement.movements;
+
+import baritone.Baritone;
+import baritone.api.IBaritone;
+import baritone.api.pathing.movement.MovementStatus;
+import baritone.api.utils.BetterBlockPos;
+import baritone.api.utils.input.Input;
+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.pathing.MutableMoveResult;
+import net.minecraft.block.Block;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.init.Blocks;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+// Alternate of MovementDiagonal, better optimised for large and flat surfaces to sprint more efficiently without
+// switching between straight<->diagonal constantly.
+//
+// "Knight": In chess, a Knight can move 2 up and 1 to the side.
+public class MovementKnight extends Movement {
+
+ private static final double SQRT_5 = Math.sqrt(5);
+
+ private final KnightDirection direction;
+
+ public MovementKnight(IBaritone baritone, BetterBlockPos start, EnumFacing longSide, EnumFacing shortSide) {
+ this(baritone, start, start.offset(longSide, 2).offset(shortSide));
+ }
+
+ private MovementKnight(IBaritone baritone, BetterBlockPos start, BetterBlockPos end) {
+ this(baritone, start, end, new KnightDirection(start, end));
+ }
+
+ private MovementKnight(IBaritone baritone, BetterBlockPos start, BetterBlockPos end, KnightDirection dir) {
+ super(baritone, start, end, new BetterBlockPos[]{});
+ direction = dir;
+ }
+
+ static class KnightDirection {
+ private final int dX;
+ private final int dZ;
+
+ public KnightDirection(BetterBlockPos start, BetterBlockPos end) {
+ this(end.x - start.x, end.z - start.z);
+ }
+
+ public KnightDirection(int dX, int dZ) {
+ if (dX == 0 || dZ == 0
+ || dX > 2 || dZ > 2 || dX < -2 || dZ < -2
+ || (Math.abs(dX) == Math.abs(dZ))
+ ) {
+ throw new IllegalArgumentException("Illegal direction; dX: " + dX + " dZ: " + dZ);
+ }
+
+ this.dX = dX;
+ this.dZ = dZ;
+ }
+
+ private boolean isXLongest() {
+ return Math.abs(dX) > Math.abs(dZ);
+ }
+
+ public BetterBlockPos[] allBlocks(int x, int y, int z) {
+ return new BetterBlockPos[]{
+ new BetterBlockPos(x, y, z), // start
+ new BetterBlockPos(x + dX, y, z + dZ), // end
+ new BetterBlockPos(x + dX, y, z), // extreme X
+ new BetterBlockPos(x, y, z + dZ), // extreme z
+ longMiddle(x, y, z),
+ sideMiddle(x, y, z),
+ };
+ }
+
+ public BetterBlockPos longFarCorner(int x, int y, int z) {
+ boolean xIsLongest = isXLongest();
+
+ return new BetterBlockPos(x + (xIsLongest ? dX : 0), y, z + (!xIsLongest ? dZ : 0));
+ }
+
+ public BetterBlockPos sideNearCorner(int x, int y, int z) {
+ boolean xIsShortest = !isXLongest();
+
+ return new BetterBlockPos(x + (xIsShortest ? dX : 0), y, z + (!xIsShortest ? dZ : 0));
+ }
+
+ public BetterBlockPos longMiddle(int x, int y, int z) {
+ boolean xIsLongest = isXLongest();
+
+ return new BetterBlockPos(x + (xIsLongest ? (dX > 0 ? 1 : -1) : 0), y, z + (!xIsLongest ? (dZ > 0 ? 1 : -1) : 0));
+ }
+
+ public BetterBlockPos sideMiddle(int x, int y, int z) {
+ return new BetterBlockPos(x + (dX > 0 ? 1 : -1), y, z + (dZ > 0 ? 1 : -1));
+ }
+ }
+
+ public static void cost(CalculationContext context, int srcX, int y, int srcZ, int dstX, int dstZ, MutableMoveResult res) {
+ if (!MovementHelper.canWalkThrough(context.bsi, dstX, y, dstZ)
+ || !MovementHelper.canWalkThrough(context.bsi, dstX, y + 1, dstZ)
+ || !MovementHelper.canWalkOn(context.bsi, dstX, y - 1, dstZ)) {
+ return;
+ }
+
+ KnightDirection dir = new KnightDirection(dstX - srcX, dstZ - srcZ);
+ BetterBlockPos longFarCorner = dir.longFarCorner(srcX, y, srcZ);
+ BetterBlockPos sideNearCorner = dir.sideNearCorner(srcX, y, srcZ);
+ BetterBlockPos longMiddle = dir.longMiddle(srcX, y, srcZ);
+ BetterBlockPos sideMiddle = dir.sideMiddle(srcX, y, srcZ);
+
+ // check middles
+
+ if (testFullyWalkable(context, longMiddle) || testFullyWalkable(context, sideMiddle)) return;
+
+ // check safety of corner blocks
+
+ if (!safeCorner(context, longFarCorner) || !safeCorner(context, sideNearCorner)) return;
+
+ double multiplier = WALK_ONE_BLOCK_COST;
+ boolean water = false;
+
+ if (MovementHelper.isWater(context.getBlock(srcX, y, srcZ))
+ || MovementHelper.isWater(context.getBlock(dstX, y, dstZ))
+ || MovementHelper.isWater(context.get(longMiddle).getBlock())
+ || MovementHelper.isWater(context.get(sideMiddle).getBlock())) {
+
+ multiplier = context.waterWalkSpeed;
+ water = true;
+ }
+
+ if (context.canSprint && !water) {
+ // If we aren't edging around anything, and we aren't in water
+ // We can sprint =D
+ // Don't check for soul sand, since we can sprint on that too
+ multiplier *= SPRINT_MULTIPLIER;
+ }
+
+ res.cost = multiplier * SQRT_5;
+ res.y = y;
+ res.x = dstX;
+ res.z = dstZ;
+ }
+
+ private static boolean testFullyWalkable(CalculationContext context, BetterBlockPos pos) {
+ return !MovementHelper.canWalkThrough(context.bsi, pos.x, pos.y, pos.z)
+ || !MovementHelper.canWalkThrough(context.bsi, pos.x, pos.y + 1, pos.z)
+ || !MovementHelper.canWalkOn(context.bsi, pos.x, pos.y - 1, pos.z);
+ }
+
+ private static boolean safeCorner(CalculationContext context, BetterBlockPos pos) {
+ Block under = context.get(pos.down()).getBlock();
+
+ if (under == Blocks.MAGMA
+ // todo: maybe we dont need to check for lava on the corners? could be walkable with momentum, no bueno
+ || MovementHelper.isLava(under)) {
+ return false;
+ }
+
+ Block posBlk = context.get(pos).getBlock();
+ Block posUpBlk = context.get(pos.up()).getBlock();
+
+ return (!MovementHelper.avoidWalkingInto(posBlk) || posBlk == Blocks.WATER)
+ && (!MovementHelper.avoidWalkingInto(posUpBlk));
+ }
+
+ @Override
+ public MovementState updateState(MovementState state) {
+ super.updateState(state);
+ if (state.getStatus() != MovementStatus.RUNNING) {
+ return state;
+ }
+
+ if (ctx.playerFeet().equals(dest)) {
+ return state.setStatus(MovementStatus.SUCCESS);
+ } else if (!playerInValidPosition() && !(MovementHelper.isLiquid(ctx, src) && getValidPositions().contains(ctx.playerFeet().up()))) {
+ return state.setStatus(MovementStatus.UNREACHABLE);
+ }
+ if (sprint()) {
+ state.setInput(Input.SPRINT, true);
+ }
+ MovementHelper.moveTowards(ctx, state, dest);
+ return state;
+ }
+
+ private boolean sprint() {
+ return !MovementHelper.isLiquid(ctx, ctx.playerFeet()) || Baritone.settings().sprintInWater.value;
+ }
+
+ @Override
+ public double calculateCost(CalculationContext context) {
+ MutableMoveResult result = new MutableMoveResult();
+ cost(context, src.x, src.y, src.z, dest.x, dest.z, result);
+ if (result.y != dest.y) {
+ return COST_INF;
+ }
+ return result.cost;
+ }
+
+ @Override
+ protected boolean prepared(MovementState state) {
+ return true;
+ }
+
+ @Override
+ protected Set calculateValidPositions() {
+ return Arrays.stream(direction.allBlocks(src.x, src.y, src.z)).collect(Collectors.toSet());
+ }
+
+ @Override
+ public List toWalkInto(BlockStateInterface bsi) {
+ if (toWalkIntoCached == null) {
+ toWalkIntoCached = new ArrayList<>();
+
+ BetterBlockPos lfc = direction.longFarCorner(src.x, src.y, src.z);
+
+ if (!MovementHelper.canWalkThrough(bsi, lfc.x, lfc.y, lfc.z)) {
+ toWalkIntoCached.add(lfc);
+ }
+
+ if (!MovementHelper.canWalkThrough(bsi, lfc.x, lfc.y + 1, lfc.z)) {
+ toWalkIntoCached.add(lfc);
+ }
+
+ BetterBlockPos snc = direction.sideNearCorner(src.x, src.y, src.z);
+
+ if (!MovementHelper.canWalkThrough(bsi, snc.x, snc.y, snc.z)) {
+ toWalkIntoCached.add(snc);
+ }
+
+ if (!MovementHelper.canWalkThrough(bsi, snc.x, snc.y + 1, snc.z)) {
+ toWalkIntoCached.add(snc);
+ }
+ }
+ return toWalkIntoCached;
+ }
+}