Merge pull request #3775 from ZacSharp/pr/frostwalker

Allow pathing to use frostwalker
This commit is contained in:
leijurv 2023-01-12 14:51:35 -08:00 committed by GitHub
commit bc8c823045
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 127 additions and 15 deletions

View File

@ -123,6 +123,8 @@ public final class Settings {
/** /**
* Allow Baritone to assume it can walk on still water just like any other block. * 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. * This functionality is assumed to be provided by a separate library that might have imported Baritone.
* <p>
* Note: This will prevent some usage of the frostwalker enchantment, like pillaring up from water.
*/ */
public final Setting<Boolean> assumeWalkOnWater = new Setting<>(false); public final Setting<Boolean> assumeWalkOnWater = new Setting<>(false);

View File

@ -30,6 +30,7 @@ import net.minecraft.block.state.IBlockState;
import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.init.Enchantments;
import net.minecraft.init.Items; import net.minecraft.init.Items;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
@ -65,6 +66,7 @@ public class CalculationContext {
public final boolean allowJumpAt256; public final boolean allowJumpAt256;
public final boolean allowParkourAscend; public final boolean allowParkourAscend;
public final boolean assumeWalkOnWater; public final boolean assumeWalkOnWater;
public final int frostWalker;
public final boolean allowDiagonalDescend; public final boolean allowDiagonalDescend;
public final boolean allowDiagonalAscend; public final boolean allowDiagonalAscend;
public final boolean allowDownward; public final boolean allowDownward;
@ -103,6 +105,7 @@ public class CalculationContext {
this.allowJumpAt256 = Baritone.settings().allowJumpAt256.value; this.allowJumpAt256 = Baritone.settings().allowJumpAt256.value;
this.allowParkourAscend = Baritone.settings().allowParkourAscend.value; this.allowParkourAscend = Baritone.settings().allowParkourAscend.value;
this.assumeWalkOnWater = Baritone.settings().assumeWalkOnWater.value; this.assumeWalkOnWater = Baritone.settings().assumeWalkOnWater.value;
this.frostWalker = EnchantmentHelper.getMaxEnchantmentLevel(Enchantments.FROST_WALKER, baritone.getPlayerContext().player());
this.allowDiagonalDescend = Baritone.settings().allowDiagonalDescend.value; this.allowDiagonalDescend = Baritone.settings().allowDiagonalDescend.value;
this.allowDiagonalAscend = Baritone.settings().allowDiagonalAscend.value; this.allowDiagonalAscend = Baritone.settings().allowDiagonalAscend.value;
this.allowDownward = Baritone.settings().allowDownward.value; this.allowDownward = Baritone.settings().allowDownward.value;

View File

@ -31,6 +31,7 @@ import baritone.utils.ToolSet;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.init.Blocks; import net.minecraft.init.Blocks;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos; 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)); 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) { static boolean canPlaceAgainst(BlockStateInterface bsi, int x, int y, int z) {
return canPlaceAgainst(bsi, x, y, z, bsi.get0(x, y, z)); return canPlaceAgainst(bsi, x, y, z, bsi.get0(x, y, z));
} }

View File

@ -43,6 +43,7 @@ import java.util.Set;
public class MovementDescend extends Movement { public class MovementDescend extends Movement {
private int numTicks = 0; private int numTicks = 0;
public boolean forceSafeMode = false;
public MovementDescend(IBaritone baritone, BetterBlockPos start, BetterBlockPos end) { public MovementDescend(IBaritone baritone, BetterBlockPos start, BetterBlockPos end) {
super(baritone, start, end, new BetterBlockPos[]{end.up(2), end.up(), end}, end.down()); 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() { public void reset() {
super.reset(); super.reset();
numTicks = 0; 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 @Override
@ -109,6 +118,9 @@ public class MovementDescend extends Movement {
if (destDown.getBlock() == Blocks.LADDER || destDown.getBlock() == Blocks.VINE) { if (destDown.getBlock() == Blocks.LADDER || destDown.getBlock() == Blocks.VINE) {
return; 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) // 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; double walk = WALK_OFF_BLOCK_COST;
@ -248,6 +260,9 @@ public class MovementDescend extends Movement {
} }
public boolean safeMode() { public boolean safeMode() {
if (forceSafeMode) {
return true;
}
// (dest - src) + dest is offset 1 more in the same direction // (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 // 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); BlockPos into = dest.subtract(src.down()).add(dest);

View File

@ -114,36 +114,45 @@ public class MovementDiagonal extends Movement {
return; return;
} }
IBlockState destInto = context.get(destX, y, destZ); IBlockState destInto = context.get(destX, y, destZ);
IBlockState fromDown;
boolean ascend = false; boolean ascend = false;
IBlockState destWalkOn; IBlockState destWalkOn;
boolean descend = false; boolean descend = false;
boolean frostWalker = false;
if (!MovementHelper.canWalkThrough(context, destX, y, destZ, destInto)) { if (!MovementHelper.canWalkThrough(context, destX, y, destZ, destInto)) {
ascend = true; 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)) { 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; return;
} }
destWalkOn = destInto; destWalkOn = destInto;
fromDown = context.get(x, y - 1, z);
} else { } else {
destWalkOn = context.get(destX, y - 1, destZ); 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; descend = true;
if (!context.allowDiagonalDescend || !MovementHelper.canWalkOn(context, destX, y - 2, destZ) || !MovementHelper.canWalkThrough(context, destX, y - 1, destZ, destWalkOn)) { if (!context.allowDiagonalDescend || !MovementHelper.canWalkOn(context, destX, y - 2, destZ) || !MovementHelper.canWalkThrough(context, destX, y - 1, destZ, destWalkOn)) {
return; 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; double multiplier = WALK_ONE_BLOCK_COST;
// For either possible soul sand, that affects half of our walking // For either possible soul sand, that affects half of our walking
if (destWalkOn.getBlock() == Blocks.SOUL_SAND) { if (destWalkOn.getBlock() == Blocks.SOUL_SAND) {
multiplier += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2; 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) { } else if (destWalkOn.getBlock() == Blocks.WATER) {
multiplier += context.walkOnWaterOnePenalty * SQRT_2; multiplier += context.walkOnWaterOnePenalty * SQRT_2;
} }
Block fromDown = context.get(x, y - 1, z).getBlock(); Block fromDownBlock = fromDown.getBlock();
if (fromDown == Blocks.LADDER || fromDown == Blocks.VINE) { if (fromDownBlock == Blocks.LADDER || fromDownBlock == Blocks.VINE) {
return; return;
} }
if (fromDown == Blocks.SOUL_SAND) { if (fromDownBlock == Blocks.SOUL_SAND) {
multiplier += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2; multiplier += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2;
} }
Block cuttingOver1 = context.get(x, y - 1, destZ).getBlock(); Block cuttingOver1 = context.get(x, y - 1, destZ).getBlock();

View File

@ -17,6 +17,7 @@
package baritone.pathing.movement.movements; package baritone.pathing.movement.movements;
import baritone.Baritone;
import baritone.api.IBaritone; import baritone.api.IBaritone;
import baritone.api.pathing.movement.MovementStatus; import baritone.api.pathing.movement.MovementStatus;
import baritone.api.utils.BetterBlockPos; import baritone.api.utils.BetterBlockPos;
@ -91,9 +92,16 @@ public class MovementParkour extends Movement {
return; return;
} }
IBlockState standingOn = context.get(x, y - 1, z); 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; 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; int maxJump;
if (standingOn.getBlock() == Blocks.SOUL_SAND) { if (standingOn.getBlock() == Blocks.SOUL_SAND) {
maxJump = 2; // 1 block gap maxJump = 2; // 1 block gap
@ -135,7 +143,10 @@ public class MovementParkour extends Movement {
// check for flat landing position // check for flat landing position
IBlockState landingOn = context.bsi.get0(destX, y - 1, destZ); 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 // 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)) { if (checkOvershootSafety(context.bsi, destX + xDiff, y, destZ + zDiff)) {
res.x = destX; res.x = destX;
res.y = y; res.y = y;
@ -265,7 +276,12 @@ public class MovementParkour extends Movement {
} }
} else if (!ctx.playerFeet().equals(src)) { } else if (!ctx.playerFeet().equals(src)) {
if (ctx.playerFeet().equals(src.offset(direction)) || ctx.player().posY - src.y > 0.0001) { 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 // 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); state.setInput(Input.CLICK_RIGHT, true);
} }

View File

@ -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 // 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; 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); double hardness = MovementHelper.getMiningDurationTicks(context, x, y + 2, z, toBreak, true);
if (hardness >= COST_INF) { if (hardness >= COST_INF) {
return COST_INF; return COST_INF;

View File

@ -71,8 +71,11 @@ public class MovementTraverse extends Movement {
IBlockState pb0 = context.get(destX, y + 1, destZ); IBlockState pb0 = context.get(destX, y + 1, destZ);
IBlockState pb1 = context.get(destX, y, destZ); IBlockState pb1 = context.get(destX, y, destZ);
IBlockState destOn = context.get(destX, y - 1, destZ); IBlockState destOn = context.get(destX, y - 1, destZ);
Block srcDown = context.getBlock(x, y - 1, z); IBlockState srcDown = context.get(x, y - 1, z);
if (MovementHelper.canWalkOn(context, destX, y - 1, destZ, destOn)) {//this is a walk, not a bridge 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; double WC = WALK_ONE_BLOCK_COST;
boolean water = false; boolean water = false;
if (MovementHelper.isWater(pb0.getBlock()) || MovementHelper.isWater(pb1.getBlock())) { if (MovementHelper.isWater(pb0.getBlock()) || MovementHelper.isWater(pb1.getBlock())) {
@ -81,10 +84,12 @@ public class MovementTraverse extends Movement {
} else { } else {
if (destOn.getBlock() == Blocks.SOUL_SAND) { if (destOn.getBlock() == Blocks.SOUL_SAND) {
WC += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2; 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) { } else if (destOn.getBlock() == Blocks.WATER) {
WC += context.walkOnWaterOnePenalty; 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; WC += (WALK_ONE_OVER_SOUL_SAND_COST - WALK_ONE_BLOCK_COST) / 2;
} }
} }
@ -102,13 +107,13 @@ public class MovementTraverse extends Movement {
} }
return WC; return WC;
} }
if (srcDown == Blocks.LADDER || srcDown == Blocks.VINE) { if (srcDownBlock == Blocks.LADDER || srcDownBlock == Blocks.VINE) {
hardness1 *= 5; hardness1 *= 5;
hardness2 *= 5; hardness2 *= 5;
} }
return WC + hardness1 + hardness2; return WC + hardness1 + hardness2;
} else {//this is a bridge, so we need to place a block } 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; return COST_INF;
} }
if (MovementHelper.isReplaceable(destX, y - 1, destZ, destOn, context.bsi)) { 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 // 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) =/ 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 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 WC = WC * (SNEAK_ONE_BLOCK_COST / WALK_ONE_BLOCK_COST);//since we are sneak backplacing, we are sneaking lol
return WC + placeCost + hardness1 + hardness2; 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(); BlockPos feet = ctx.playerFeet();
if (feet.getY() != dest.getY() && !ladder) { if (feet.getY() != dest.getY() && !ladder) {
logDebug("Wrong Y coordinate"); logDebug("Wrong Y coordinate");

View File

@ -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 // 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 (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()) { if (((MovementDescend) current).safeMode() && !((MovementDescend) current).skipToAscend()) {
logDebug("Sprinting would be unsafe"); logDebug("Sprinting would be unsafe");
return false; return false;