diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java
index 31f1f61f..9e3e6a38 100644
--- a/src/api/java/baritone/api/Settings.java
+++ b/src/api/java/baritone/api/Settings.java
@@ -123,6 +123,8 @@ public final class Settings {
/**
* Allow Baritone to assume it can walk on still water just like any other block.
* This functionality is assumed to be provided by a separate library that might have imported Baritone.
+ *
+ * Note: This will prevent some usage of the frostwalker enchantment, like pillaring up from water.
*/
public final Setting assumeWalkOnWater = new Setting<>(false);
diff --git a/src/main/java/baritone/pathing/movement/CalculationContext.java b/src/main/java/baritone/pathing/movement/CalculationContext.java
index 5a8d9839..177435ef 100644
--- a/src/main/java/baritone/pathing/movement/CalculationContext.java
+++ b/src/main/java/baritone/pathing/movement/CalculationContext.java
@@ -30,6 +30,7 @@ import net.minecraft.block.state.IBlockState;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.init.Enchantments;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
@@ -65,6 +66,7 @@ public class CalculationContext {
public final boolean allowJumpAt256;
public final boolean allowParkourAscend;
public final boolean assumeWalkOnWater;
+ public final int frostWalker;
public final boolean allowDiagonalDescend;
public final boolean allowDiagonalAscend;
public final boolean allowDownward;
@@ -103,6 +105,7 @@ public class CalculationContext {
this.allowJumpAt256 = Baritone.settings().allowJumpAt256.value;
this.allowParkourAscend = Baritone.settings().allowParkourAscend.value;
this.assumeWalkOnWater = Baritone.settings().assumeWalkOnWater.value;
+ this.frostWalker = EnchantmentHelper.getMaxEnchantmentLevel(Enchantments.FROST_WALKER, baritone.getPlayerContext().player());
this.allowDiagonalDescend = Baritone.settings().allowDiagonalDescend.value;
this.allowDiagonalAscend = Baritone.settings().allowDiagonalAscend.value;
this.allowDownward = Baritone.settings().allowDownward.value;
diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java
index f0015e08..c32cde60 100644
--- a/src/main/java/baritone/pathing/movement/MovementHelper.java
+++ b/src/main/java/baritone/pathing/movement/MovementHelper.java
@@ -31,6 +31,7 @@ import baritone.utils.ToolSet;
import net.minecraft.block.*;
import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.state.IBlockState;
+import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.init.Blocks;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
@@ -462,6 +463,39 @@ public interface MovementHelper extends ActionCosts, Helper {
return canWalkOn(bsi, x, y, z, bsi.get0(x, y, z));
}
+ static boolean canUseFrostWalker(CalculationContext context, IBlockState state) {
+ return context.frostWalker != 0
+ && (state.getBlock() == Blocks.WATER || state.getBlock() == Blocks.FLOWING_WATER)
+ && ((Integer) state.getValue(BlockLiquid.LEVEL)) == 0;
+ }
+
+ static boolean canUseFrostWalker(IPlayerContext ctx, BlockPos pos) {
+ IBlockState state = BlockStateInterface.get(ctx, pos);
+ return EnchantmentHelper.hasFrostWalkerEnchantment(ctx.player())
+ && (state.getBlock() == Blocks.WATER || state.getBlock() == Blocks.FLOWING_WATER)
+ && ((Integer) state.getValue(BlockLiquid.LEVEL)) == 0;
+ }
+
+ /**
+ * If movements make us stand/walk on this block, will it have a top to walk on?
+ */
+ static boolean mustBeSolidToWalkOn(CalculationContext context, int x, int y, int z, IBlockState state) {
+ Block block = state.getBlock();
+ if (block == Blocks.LADDER || block == Blocks.VINE) {
+ return false;
+ }
+ if (block instanceof BlockLiquid) {
+ if (context.assumeWalkOnWater) {
+ return false;
+ }
+ Block blockAbove = context.getBlock(x, y+1, z);
+ if (blockAbove instanceof BlockLiquid) {
+ return false;
+ }
+ }
+ return true;
+ }
+
static boolean canPlaceAgainst(BlockStateInterface bsi, int x, int y, int z) {
return canPlaceAgainst(bsi, x, y, z, bsi.get0(x, y, z));
}
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java
index 8675fa04..d36843cd 100644
--- a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java
+++ b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java
@@ -43,6 +43,7 @@ import java.util.Set;
public class MovementDescend extends Movement {
private int numTicks = 0;
+ public boolean forceSafeMode = false;
public MovementDescend(IBaritone baritone, BetterBlockPos start, BetterBlockPos end) {
super(baritone, start, end, new BetterBlockPos[]{end.up(2), end.up(), end}, end.down());
@@ -52,6 +53,14 @@ public class MovementDescend extends Movement {
public void reset() {
super.reset();
numTicks = 0;
+ forceSafeMode = false;
+ }
+
+ /**
+ * Called by PathExecutor if needing safeMode can only be detected with knowledge about the next movement
+ */
+ public void forceSafeMode() {
+ forceSafeMode = true;
}
@Override
@@ -109,6 +118,9 @@ public class MovementDescend extends Movement {
if (destDown.getBlock() == Blocks.LADDER || destDown.getBlock() == Blocks.VINE) {
return;
}
+ if (MovementHelper.canUseFrostWalker(context, destDown)) { // no need to check assumeWalkOnWater
+ return; // the water will freeze when we try to walk into it
+ }
// we walk half the block plus 0.3 to get to the edge, then we walk the other 0.2 while simultaneously falling (math.max because of how it's in parallel)
double walk = WALK_OFF_BLOCK_COST;
@@ -248,6 +260,9 @@ public class MovementDescend extends Movement {
}
public boolean safeMode() {
+ if (forceSafeMode) {
+ return true;
+ }
// (dest - src) + dest is offset 1 more in the same direction
// so it's the block we'd need to worry about running into if we decide to sprint straight through this descend
BlockPos into = dest.subtract(src.down()).add(dest);
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java b/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java
index 48b4ed23..f56babc2 100644
--- a/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java
+++ b/src/main/java/baritone/pathing/movement/movements/MovementDiagonal.java
@@ -114,36 +114,45 @@ public class MovementDiagonal extends Movement {
return;
}
IBlockState destInto = context.get(destX, y, destZ);
+ IBlockState fromDown;
boolean ascend = false;
IBlockState destWalkOn;
boolean descend = false;
+ boolean frostWalker = false;
if (!MovementHelper.canWalkThrough(context, destX, y, destZ, destInto)) {
ascend = true;
if (!context.allowDiagonalAscend || !MovementHelper.canWalkThrough(context, x, y + 2, z) || !MovementHelper.canWalkOn(context, destX, y, destZ, destInto) || !MovementHelper.canWalkThrough(context, destX, y + 2, destZ)) {
return;
}
destWalkOn = destInto;
+ fromDown = context.get(x, y - 1, z);
} else {
destWalkOn = context.get(destX, y - 1, destZ);
- if (!MovementHelper.canWalkOn(context, destX, y - 1, destZ, destWalkOn)) {
+ fromDown = context.get(x, y - 1, z);
+ boolean standingOnABlock = MovementHelper.mustBeSolidToWalkOn(context, x, y - 1, z, fromDown);
+ frostWalker = standingOnABlock && MovementHelper.canUseFrostWalker(context, destWalkOn);
+ if (!frostWalker && !MovementHelper.canWalkOn(context, destX, y - 1, destZ, destWalkOn)) {
descend = true;
if (!context.allowDiagonalDescend || !MovementHelper.canWalkOn(context, destX, y - 2, destZ) || !MovementHelper.canWalkThrough(context, destX, y - 1, destZ, destWalkOn)) {
return;
}
}
+ frostWalker &= !context.assumeWalkOnWater; // do this after checking for descends because jesus can't prevent the water from freezing, it just prevents us from relying on the water freezing
}
double multiplier = WALK_ONE_BLOCK_COST;
// For either possible soul sand, that affects half of our walking
if (destWalkOn.getBlock() == Blocks.SOUL_SAND) {
multiplier += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2;
+ } else if (frostWalker) {
+ // frostwalker lets us walk on water without the penalty
} else if (destWalkOn.getBlock() == Blocks.WATER) {
multiplier += context.walkOnWaterOnePenalty * SQRT_2;
}
- Block fromDown = context.get(x, y - 1, z).getBlock();
- if (fromDown == Blocks.LADDER || fromDown == Blocks.VINE) {
+ Block fromDownBlock = fromDown.getBlock();
+ if (fromDownBlock == Blocks.LADDER || fromDownBlock == Blocks.VINE) {
return;
}
- if (fromDown == Blocks.SOUL_SAND) {
+ if (fromDownBlock == Blocks.SOUL_SAND) {
multiplier += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2;
}
Block cuttingOver1 = context.get(x, y - 1, destZ).getBlock();
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java
index 557f3026..76b17f0f 100644
--- a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java
+++ b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java
@@ -17,6 +17,7 @@
package baritone.pathing.movement.movements;
+import baritone.Baritone;
import baritone.api.IBaritone;
import baritone.api.pathing.movement.MovementStatus;
import baritone.api.utils.BetterBlockPos;
@@ -91,9 +92,16 @@ public class MovementParkour extends Movement {
return;
}
IBlockState standingOn = context.get(x, y - 1, z);
- if (standingOn.getBlock() == Blocks.VINE || standingOn.getBlock() == Blocks.LADDER || standingOn.getBlock() instanceof BlockStairs || MovementHelper.isBottomSlab(standingOn) || standingOn.getBlock() instanceof BlockLiquid) {
+ if (standingOn.getBlock() == Blocks.VINE || standingOn.getBlock() == Blocks.LADDER || standingOn.getBlock() instanceof BlockStairs || MovementHelper.isBottomSlab(standingOn)) {
return;
}
+ // we can't jump from (frozen) water with assumeWalkOnWater because we can't be sure it will be frozen
+ if (context.assumeWalkOnWater && standingOn.getBlock() instanceof BlockLiquid) {
+ return;
+ }
+ if (context.getBlock(x, y, z) instanceof BlockLiquid) {
+ return; // can't jump out of water
+ }
int maxJump;
if (standingOn.getBlock() == Blocks.SOUL_SAND) {
maxJump = 2; // 1 block gap
@@ -135,7 +143,10 @@ public class MovementParkour extends Movement {
// check for flat landing position
IBlockState landingOn = context.bsi.get0(destX, y - 1, destZ);
// farmland needs to be canWalkOn otherwise farm can never work at all, but we want to specifically disallow ending a jump on farmland haha
- if (landingOn.getBlock() != Blocks.FARMLAND && MovementHelper.canWalkOn(context, destX, y - 1, destZ, landingOn)) {
+ // frostwalker works here because we can't jump from possibly unfrozen water
+ if ((landingOn.getBlock() != Blocks.FARMLAND && MovementHelper.canWalkOn(context, destX, y - 1, destZ, landingOn))
+ || (Math.min(16, context.frostWalker + 2) >= i && MovementHelper.canUseFrostWalker(context, landingOn))
+ ) {
if (checkOvershootSafety(context.bsi, destX + xDiff, y, destZ + zDiff)) {
res.x = destX;
res.y = y;
@@ -265,7 +276,12 @@ public class MovementParkour extends Movement {
}
} else if (!ctx.playerFeet().equals(src)) {
if (ctx.playerFeet().equals(src.offset(direction)) || ctx.player().posY - src.y > 0.0001) {
- if (!MovementHelper.canWalkOn(ctx, dest.down()) && !ctx.player().onGround && MovementHelper.attemptToPlaceABlock(state, baritone, dest.down(), true, false) == PlaceResult.READY_TO_PLACE) {
+ if (Baritone.settings().allowPlace.value
+ && ((Baritone) baritone).getInventoryBehavior().hasGenericThrowaway()
+ && !MovementHelper.canWalkOn(ctx, dest.down())
+ && !ctx.player().onGround
+ && MovementHelper.attemptToPlaceABlock(state, baritone, dest.down(), true, false) == PlaceResult.READY_TO_PLACE
+ ) {
// go in the opposite order to check DOWN before all horizontals -- down is preferable because you don't have to look to the side while in midair, which could mess up the trajectory
state.setInput(Input.CLICK_RIGHT, true);
}
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java
index 73731aac..cfa9d926 100644
--- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java
+++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java
@@ -101,6 +101,10 @@ public class MovementPillar extends Movement {
// if we're standing on water and assumeWalkOnWater is false, we must have ascended to here, or sneak backplaced, so it is possible to pillar again
return COST_INF;
}
+ if ((from == Blocks.WATERLILY || from == Blocks.CARPET) && fromDown.getBlock() instanceof BlockLiquid) {
+ // to ascend here we'd have to break the block we are standing on
+ return COST_INF;
+ }
double hardness = MovementHelper.getMiningDurationTicks(context, x, y + 2, z, toBreak, true);
if (hardness >= COST_INF) {
return COST_INF;
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java
index d932c5f8..c8a3528e 100644
--- a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java
+++ b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java
@@ -71,8 +71,11 @@ public class MovementTraverse extends Movement {
IBlockState pb0 = context.get(destX, y + 1, destZ);
IBlockState pb1 = context.get(destX, y, destZ);
IBlockState destOn = context.get(destX, y - 1, destZ);
- Block srcDown = context.getBlock(x, y - 1, z);
- if (MovementHelper.canWalkOn(context, destX, y - 1, destZ, destOn)) {//this is a walk, not a bridge
+ IBlockState srcDown = context.get(x, y - 1, z);
+ Block srcDownBlock = srcDown.getBlock();
+ boolean standingOnABlock = MovementHelper.mustBeSolidToWalkOn(context, x, y-1, z, srcDown);
+ boolean frostWalker = standingOnABlock && !context.assumeWalkOnWater && MovementHelper.canUseFrostWalker(context, destOn);
+ if (frostWalker || MovementHelper.canWalkOn(context, destX, y - 1, destZ, destOn)) { //this is a walk, not a bridge
double WC = WALK_ONE_BLOCK_COST;
boolean water = false;
if (MovementHelper.isWater(pb0.getBlock()) || MovementHelper.isWater(pb1.getBlock())) {
@@ -81,10 +84,12 @@ public class MovementTraverse extends Movement {
} else {
if (destOn.getBlock() == Blocks.SOUL_SAND) {
WC += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2;
+ } else if (frostWalker) {
+ // with frostwalker we can walk on water without the penalty, if we are sure we won't be using jesus
} else if (destOn.getBlock() == Blocks.WATER) {
WC += context.walkOnWaterOnePenalty;
}
- if (srcDown == Blocks.SOUL_SAND) {
+ if (srcDownBlock == Blocks.SOUL_SAND) {
WC += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2;
}
}
@@ -102,13 +107,13 @@ public class MovementTraverse extends Movement {
}
return WC;
}
- if (srcDown == Blocks.LADDER || srcDown == Blocks.VINE) {
+ if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE) {
hardness1 *= 5;
hardness2 *= 5;
}
return WC + hardness1 + hardness2;
} else {//this is a bridge, so we need to place a block
- if (srcDown == Blocks.LADDER || srcDown == Blocks.VINE) {
+ if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE) {
return COST_INF;
}
if (MovementHelper.isReplaceable(destX, y - 1, destZ, destOn, context.bsi)) {
@@ -139,12 +144,16 @@ public class MovementTraverse extends Movement {
}
}
// now that we've checked all possible directions to side place, we actually need to backplace
- if (srcDown == Blocks.SOUL_SAND || (srcDown instanceof BlockSlab && !((BlockSlab) srcDown).isDouble())) {
+ if (srcDownBlock == Blocks.SOUL_SAND || (srcDownBlock instanceof BlockSlab && !((BlockSlab) srcDownBlock).isDouble())) {
return COST_INF; // can't sneak and backplace against soul sand or half slabs (regardless of whether it's top half or bottom half) =/
}
- if (srcDown == Blocks.FLOWING_WATER || srcDown == Blocks.WATER) {
+ if (!standingOnABlock) { // standing on water / swimming
return COST_INF; // this is obviously impossible
}
+ Block blockSrc = context.getBlock(x, y, z);
+ if ((blockSrc == Blocks.WATERLILY || blockSrc == Blocks.CARPET) && srcDownBlock instanceof BlockLiquid) {
+ return COST_INF; // we can stand on these but can't place against them
+ }
WC = WC * (SNEAK_ONE_BLOCK_COST / WALK_ONE_BLOCK_COST);//since we are sneak backplacing, we are sneaking lol
return WC + placeCost + hardness1 + hardness2;
}
@@ -226,7 +235,7 @@ public class MovementTraverse extends Movement {
}
}
- boolean isTheBridgeBlockThere = MovementHelper.canWalkOn(ctx, positionToPlace) || ladder;
+ boolean isTheBridgeBlockThere = MovementHelper.canWalkOn(ctx, positionToPlace) || ladder || MovementHelper.canUseFrostWalker(ctx, positionToPlace);
BlockPos feet = ctx.playerFeet();
if (feet.getY() != dest.getY() && !ladder) {
logDebug("Wrong Y coordinate");
diff --git a/src/main/java/baritone/pathing/path/PathExecutor.java b/src/main/java/baritone/pathing/path/PathExecutor.java
index ded25cdd..e68de480 100644
--- a/src/main/java/baritone/pathing/path/PathExecutor.java
+++ b/src/main/java/baritone/pathing/path/PathExecutor.java
@@ -380,6 +380,26 @@ public class PathExecutor implements IPathExecutor, Helper {
// however, descend and ascend don't request sprinting, because they don't know the context of what movement comes after it
if (current instanceof MovementDescend) {
+ if (pathPosition < path.length() - 2) {
+ // keep this out of onTick, even if that means a tick of delay before it has an effect
+ IMovement next = path.movements().get(pathPosition + 1);
+ if (MovementHelper.canUseFrostWalker(ctx, next.getDest().down())) {
+ // frostwalker only works if you cross the edge of the block on ground so in some cases we may not overshoot
+ // Since MovementDescend can't know the next movement we have to tell it
+ if (next instanceof MovementTraverse || next instanceof MovementParkour) {
+ boolean couldPlaceInstead = Baritone.settings().allowPlace.value && behavior.baritone.getInventoryBehavior().hasGenericThrowaway() && next instanceof MovementParkour; // traverse doesn't react fast enough
+ // this is true if the next movement does not ascend or descends and goes into the same cardinal direction (N-NE-E-SE-S-SW-W-NW) as the descend
+ // in that case current.getDirection() is e.g. (0, -1, 1) and next.getDirection() is e.g. (0, 0, 3) so the cross product of (0, 0, 1) and (0, 0, 3) is taken, which is (0, 0, 0) because the vectors are colinear (don't form a plane)
+ // since movements in exactly the opposite direction (e.g. descend (0, -1, 1) and traverse (0, 0, -1)) would also pass this check we also have to rule out that case
+ // we can do that by adding the directions because traverse is always 1 long like descend and parkour can't jump through current.getSrc().down()
+ boolean sameFlatDirection = !current.getDirection().up().add(next.getDirection()).equals(BlockPos.ORIGIN)
+ && current.getDirection().up().crossProduct(next.getDirection()).equals(BlockPos.ORIGIN); // here's why you learn maths in school
+ if (sameFlatDirection && !couldPlaceInstead) {
+ ((MovementDescend) current).forceSafeMode();
+ }
+ }
+ }
+ }
if (((MovementDescend) current).safeMode() && !((MovementDescend) current).skipToAscend()) {
logDebug("Sprinting would be unsafe");
return false;