From 0fd91edb9749d7bd70f9cf1e29bff2df57719982 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 1 Sep 2021 16:22:59 -0700 Subject: [PATCH] column system, sneaking nodes, connected up raytrace system, misc refactors --- .../baritone/api/utils/BetterBlockPos.java | 18 +- src/main/java/baritone/builder/Column.java | 102 +++++++++++ src/main/java/baritone/builder/Face.java | 9 +- .../java/baritone/builder/GreedySolver.java | 164 +++++++++++------- src/main/java/baritone/builder/Main.java | 13 +- src/main/java/baritone/builder/Node.java | 48 ++++- .../java/baritone/builder/PlayerPhysics.java | 84 ++++++--- .../java/baritone/builder/WorldState.java | 3 + 8 files changed, 344 insertions(+), 97 deletions(-) create mode 100644 src/main/java/baritone/builder/Column.java diff --git a/src/api/java/baritone/api/utils/BetterBlockPos.java b/src/api/java/baritone/api/utils/BetterBlockPos.java index 8f9e51edd..bea49f3a8 100644 --- a/src/api/java/baritone/api/utils/BetterBlockPos.java +++ b/src/api/java/baritone/api/utils/BetterBlockPos.java @@ -80,19 +80,19 @@ public final class BetterBlockPos extends BlockPos { return longHash(pos.x, pos.y, pos.z); } - private static final int NUM_X_BITS = 26; - private static final int NUM_Z_BITS = NUM_X_BITS; - private static final int NUM_Y_BITS = 9; // note: even though Y goes from 0 to 255, that doesn't mean 8 bits will "just work" because the deserializer assumes signed. i could change it for just Y to assume unsigned and leave X and Z as signed, however, we know that in 1.17 they plan to add negative Y. for that reason, the better approach is to give the extra bits to Y and leave it as signed. + public static final int NUM_X_BITS = 26; + public static final int NUM_Z_BITS = NUM_X_BITS; + public static final int NUM_Y_BITS = 9; // note: even though Y goes from 0 to 255, that doesn't mean 8 bits will "just work" because the deserializer assumes signed. i could change it for just Y to assume unsigned and leave X and Z as signed, however, we know that in 1.17 they plan to add negative Y. for that reason, the better approach is to give the extra bits to Y and leave it as signed. // also, if 1.17 sticks with the current plan which is -64 to +320, we could have 9 bits for Y and a constant offset of -64 to change it to -128 to +256. // that would result in the packed long representation of any valid coordinate still being a positive integer // i like that property, so i will keep num_y_bits at 9 and plan for an offset in 1.17 // it also gives 1 bit of wiggle room in case anything else happens in the future, so we are only using 63 out of 64 bits at the moment - private static final int Z_SHIFT = 0; - private static final int Y_SHIFT = Z_SHIFT + NUM_Z_BITS + 1; // 1 padding bit to make twos complement not overflow - private static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS + 1; // and also here too - private static final long X_MASK = (1L << NUM_X_BITS) - 1L; // X doesn't need padding as the overflow carry bit is just discarded, like a normal long (-1) + (1) = 0 - private static final long Y_MASK = (1L << NUM_Y_BITS) - 1L; - private static final long Z_MASK = (1L << NUM_Z_BITS) - 1L; + public static final int Z_SHIFT = 0; + public static final int Y_SHIFT = Z_SHIFT + NUM_Z_BITS + 1; // 1 padding bit to make twos complement not overflow + public static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS + 1; // and also here too + public static final long X_MASK = (1L << NUM_X_BITS) - 1L; // X doesn't need padding as the overflow carry bit is just discarded, like a normal long (-1) + (1) = 0 + public static final long Y_MASK = (1L << NUM_Y_BITS) - 1L; + public static final long Z_MASK = (1L << NUM_Z_BITS) - 1L; public static final long POST_ADDITION_MASK = X_MASK << X_SHIFT | Y_MASK << Y_SHIFT | Z_MASK << Z_SHIFT; // required to "manually inline" toLong(-1, -1, -1) here so that javac inserts proper ldc2_w instructions at usage points instead of getstatic // what's this ^ mask for? diff --git a/src/main/java/baritone/builder/Column.java b/src/main/java/baritone/builder/Column.java new file mode 100644 index 000000000..6b3dd74c0 --- /dev/null +++ b/src/main/java/baritone/builder/Column.java @@ -0,0 +1,102 @@ +/* + * 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.builder; + +import baritone.api.utils.BetterBlockPos; + +import java.util.stream.IntStream; + +import static baritone.api.utils.BetterBlockPos.Y_MASK; +import static baritone.api.utils.BetterBlockPos.Y_SHIFT; + +/** + * A mutable class representing a 1x5x1 column of blocks + *

+ * Mutable because allocations are not on the table for the core solver loop + */ +public class Column { + + public long pos; + public BlockStateCachedData underneath; + public BlockStateCachedData feet; + public BlockStateCachedData head; + public BlockStateCachedData above; + public BlockStateCachedData aboveAbove; + public PlayerPhysics.VoxelResidency voxelResidency; + public Integer feetBlips; + + public void initFrom(long pos, WorldState worldState, SolverEngineInput engineInput) { + this.pos = pos; + this.underneath = engineInput.at((pos + DOWN_1) & BetterBlockPos.POST_ADDITION_MASK, worldState); + this.feet = engineInput.at(pos, worldState); + this.head = engineInput.at((pos + UP_1) & BetterBlockPos.POST_ADDITION_MASK, worldState); + this.above = engineInput.at((pos + UP_2) & BetterBlockPos.POST_ADDITION_MASK, worldState); + this.aboveAbove = engineInput.at((pos + UP_3) & BetterBlockPos.POST_ADDITION_MASK, worldState); + this.voxelResidency = PlayerPhysics.canPlayerStand(underneath, feet); + this.feetBlips = boxNullable(PlayerPhysics.determinePlayerRealSupportLevel(underneath, feet, voxelResidency)); + } + + public boolean playerCanExistAtFootBlip(int blipWithinFeet) { + if (head.collidesWithPlayer) { + return false; + } + if (PlayerPhysics.protrudesIntoThirdBlock(blipWithinFeet) && above.collidesWithPlayer) { + return false; + } + return true; + } + + public boolean okToSneakIntoHereAtHeight(int blips) { + return playerCanExistAtFootBlip(blips) // no collision at head level + && PlayerPhysics.highestCollision(underneath, feet) < blips; // and at foot level, we only collide strictly below where the feet will be + } + + public boolean standing() { + return feetBlips != null; + } + + private static final long DOWN_1 = Y_MASK << Y_SHIFT; + private static final long UP_1 = 1L << Y_SHIFT; + private static final long UP_2 = 2L << Y_SHIFT; + private static final long UP_3 = 3L << Y_SHIFT; + + static { + if (DOWN_1 != BetterBlockPos.toLong(0, -1, 0)) { + throw new IllegalStateException(); + } + if (UP_1 != BetterBlockPos.toLong(0, 1, 0)) { + throw new IllegalStateException(); + } + if (UP_2 != BetterBlockPos.toLong(0, 2, 0)) { + throw new IllegalStateException(); + } + if (UP_3 != BetterBlockPos.toLong(0, 4, 0)) { + throw new IllegalStateException(); + } + } + + private static final Integer[] BLIPS = IntStream.range(-1, Blip.PER_BLOCK).boxed().toArray(Integer[]::new); + + static { + BLIPS[0] = null; + } + + private static Integer boxNullable(int blips) { + return BLIPS[blips + 1]; // map -1 to [0] which is null + } +} diff --git a/src/main/java/baritone/builder/Face.java b/src/main/java/baritone/builder/Face.java index a5de31a48..418c837b4 100644 --- a/src/main/java/baritone/builder/Face.java +++ b/src/main/java/baritone/builder/Face.java @@ -40,9 +40,10 @@ public enum Face { public final long offset = BetterBlockPos.toLong(x, y, z); public final int[] vec = new int[]{x, y, z}; public final boolean vertical = y != 0; + public final int horizontalIndex = x & 1 | (x | z) & 2; public static final int NUM_FACES = 6; public static final Face[] VALUES = new Face[NUM_FACES]; - public static final Face[] HORIZONTALS; + public static final Face[] HORIZONTALS = new Face[4]; public static final List> OPTS; public static final long[] OFFSETS = new long[NUM_FACES]; @@ -52,10 +53,10 @@ public enum Face { VALUES[face.index] = face; OFFSETS[face.index] = face.offset; lst.add(Optional.of(face)); + HORIZONTALS[face.horizontalIndex] = face; } lst.add(Optional.empty()); OPTS = Collections.unmodifiableList(lst); - HORIZONTALS = new Face[]{Face.SOUTH, Face.WEST, Face.NORTH, Face.EAST}; } public final EnumFacing toMC() { @@ -81,4 +82,8 @@ public enum Face { public static int opposite(int face) { return face ^ 1; } + + public static int oppositeHorizontal(int horizontalIndex) { + return horizontalIndex ^ 2; + } } diff --git a/src/main/java/baritone/builder/GreedySolver.java b/src/main/java/baritone/builder/GreedySolver.java index b57c58b46..02e6d4fb3 100644 --- a/src/main/java/baritone/builder/GreedySolver.java +++ b/src/main/java/baritone/builder/GreedySolver.java @@ -27,6 +27,9 @@ public class GreedySolver { private final Long2ObjectOpenHashMap nodes = new Long2ObjectOpenHashMap<>(); final Long2ObjectOpenHashMap zobristWorldStateCache = new Long2ObjectOpenHashMap<>(); private final Bounds bounds; + private Column scratchpadExpandNode1 = new Column(); + private Column scratchpadExpandNode2 = new Column(); + private Column scratchpadExpandNode3 = new Column(); public GreedySolver(SolverEngineInput input) { this.engineInput = input; @@ -34,7 +37,7 @@ public class GreedySolver { } synchronized SolverEngineOutput search() { - Node root = new Node(engineInput.player, 0L, -1L, this, 0); + Node root = new Node(engineInput.player, null, 0L, -1L, this, 0); nodes.put(root.nodeMapKey(), root); heap.insert(root); zobristWorldStateCache.put(0L, new WorldState.WorldStateWrappedSubstrate(engineInput)); @@ -44,80 +47,120 @@ public class GreedySolver { throw new UnsupportedOperationException(); } + private boolean wantToPlaceAt(long blockGoesAt, Node vantage, int blipsWithinVoxel, WorldState worldState) { + if (worldState.blockExists(blockGoesAt)) { + return false; + } + long vpos = vantage.pos(); + int relativeX = BetterBlockPos.XfromLong(vpos) - BetterBlockPos.XfromLong(blockGoesAt); + int relativeY = blipsWithinVoxel + Blip.FULL_BLOCK * (BetterBlockPos.YfromLong(vpos) - BetterBlockPos.YfromLong(blockGoesAt)); + int relativeZ = BetterBlockPos.ZfromLong(vpos) - BetterBlockPos.ZfromLong(blockGoesAt); + BlockStateCachedData blockBeingPlaced = engineInput.graph.data(blockGoesAt); + for (BlockStatePlacementOption option : blockBeingPlaced.options) { + long maybePlaceAgainst = option.against.offset(blockGoesAt); + if (!bounds.inRangePos(maybePlaceAgainst)) { + continue; + } + if (!worldState.blockExists(maybePlaceAgainst)) { + continue; + } + BlockStateCachedData placingAgainst = engineInput.graph.data(maybePlaceAgainst); + PlaceAgainstData againstData = placingAgainst.againstMe(option); + traces: + for (Raytracer.Raytrace trace : option.computeTraceOptions(againstData, relativeX, relativeY, relativeZ, PlayerVantage.LOOSE_CENTER, blockReachDistance())) { + for (long l : trace.passedThrough) { + if (worldState.blockExists(l)) { + continue traces; + } + } + return true; + } + } + return false; + } + private void expandNode(Node node) { WorldState worldState = node.coalesceState(this); long pos = node.pos(); - BlockStateCachedData aboveAbove = at(BetterBlockPos.offsetBy(pos, 0, 3, 0), worldState); - BlockStateCachedData above = at(BetterBlockPos.offsetBy(pos, 0, 2, 0), worldState); - BlockStateCachedData head = at(Face.UP.offset(pos), worldState); - if (Main.DEBUG && head.collidesWithPlayer) { // needed because PlayerPhysics doesn't get this - throw new IllegalStateException(); - } - BlockStateCachedData feet = at(pos, worldState); - BlockStateCachedData underneath = at(Face.DOWN.offset(pos), worldState); + Column within = scratchpadExpandNode1; + within.initFrom(pos, worldState, engineInput); - PlayerPhysics.VoxelResidency residency = PlayerPhysics.canPlayerStand(underneath, feet); - if (!PlayerPhysics.valid(residency)) { - throw new UnsupportedOperationException("sneaking off the edge of a block is not yet supported"); + Column supportedBy; + boolean sneaking = node.sneaking(); + if (sneaking) { + supportedBy = scratchpadExpandNode3; + long supportedFeetVoxel = node.sneakDirectionFromPlayerToSupportingBlock().offset(pos); + supportedBy.initFrom(supportedFeetVoxel, worldState, engineInput); + if (Main.DEBUG && !within.okToSneakIntoHereAtHeight(supportedBy.feetBlips)) { + throw new IllegalStateException(); + } + } else { + supportedBy = within; } - boolean standingWithinCollidableVoxel = residency == PlayerPhysics.VoxelResidency.STANDARD_WITHIN_SUPPORT; - long playerIsActuallySupportedBy = standingWithinCollidableVoxel ? pos : Face.DOWN.offset(pos); - int blipsWithinBlock = standingWithinCollidableVoxel ? feet.collisionHeightBlips() : underneath.collisionHeightBlips() - Blip.FULL_BLOCK; - if (blipsWithinBlock < 0) { + + int playerFeet = supportedBy.feetBlips; + if (Main.DEBUG && !supportedBy.playerCanExistAtFootBlip(playerFeet)) { throw new IllegalStateException(); } - // pillar up + // ------------------------------------------------------------------------------------------------------------- + // place block beneath feet voxel + block_beneath_feet: { - long maybePlaceAt = Face.UP.offset(playerIsActuallySupportedBy); - if (!worldState.blockExists(maybePlaceAt)) { - engineInput.graph.data(maybePlaceAt); - } else { - if (Main.DEBUG && at(maybePlaceAt, worldState).collidesWithPlayer) { - throw new IllegalStateException(); + // this is the common case for sneak bridging with full blocks + long maybePlaceAt = Face.DOWN.offset(pos); + BlockStateCachedData wouldBePlaced = engineInput.graph.data(maybePlaceAt); + int cost = 0; + int playerFeetWouldBeAt = playerFeet; + if (wouldBePlaced.collidesWithPlayer) { + int heightRelativeToCurrentVoxel = wouldBePlaced.collisionHeightBlips() - Blip.FULL_BLOCK; + if (heightRelativeToCurrentVoxel > playerFeet) { + // we would need to jump in order to do this + cost += jumpCost(); + playerFeetWouldBeAt = heightRelativeToCurrentVoxel; // because we'd have to jump, and could only place the block once we had cleared the collision box for it + if (!within.playerCanExistAtFootBlip(heightRelativeToCurrentVoxel) || !supportedBy.playerCanExistAtFootBlip(heightRelativeToCurrentVoxel)) { + break block_beneath_feet; + } } } + if (wantToPlaceAt(maybePlaceAt, node, playerFeetWouldBeAt, worldState)) { + upsertEdge(node, worldState, pos, null, maybePlaceAt, cost); + } } + // ------------------------------------------------------------------------------------------------------------- + if (sneaking) { + // we can walk back to where we were + upsertEdge(node, worldState, supportedBy.pos, null, -1, flatCost()); + // this will probably rarely be used. i can only imagine rare scenarios such as needing the extra perspective in order to place a block a bit more efficiently. like, this could avoid unnecessary ancillary scaffolding i suppose. + // ---- + // also let's try just letting ourselves fall off the edge of the block + int descendBy = PlayerPhysics.playerFalls(pos, worldState, engineInput); + if (descendBy != -1) { + upsertEdge(node, worldState, BetterBlockPos.offsetBy(pos, 0, -descendBy, 0), null, -1, fallCost(descendBy)); + } + return; + } + // ------------------------------------------------------------------------------------------------------------- // walk sideways and either stay level, ascend, or descend + Column into = scratchpadExpandNode2; for (Face travel : Face.HORIZONTALS) { long newPos = travel.offset(pos); - switch (PlayerPhysics.playerTravelCollides(blipsWithinBlock, underneath, feet, above, aboveAbove, newPos, worldState, engineInput)) { + into.initFrom(newPos, worldState, engineInput); + PlayerPhysics.Collision collision = PlayerPhysics.playerTravelCollides(within, into); + switch (collision) { case BLOCKED: { continue; } case FALL: { - int descendBy = PlayerPhysics.playerFalls(newPos, worldState, engineInput); - if (descendBy != -1) { - if (Main.DEBUG && descendBy <= 0) { - throw new IllegalStateException(); - } - upsertEdge(node, worldState, BetterBlockPos.offsetBy(newPos, 0, -descendBy, 0), -1, fallCost(descendBy)); - } + upsertEdge(node, worldState, newPos, travel, -1, flatCost()); // sneak off edge of block break; } - case VOXEL_LEVEL: { - upsertEdge(node, worldState, newPos, -1, flatCost()); + default: { + long realNewPos = BetterBlockPos.offsetBy(newPos, 0, collision.voxelVerticalOffset(), 0); + upsertEdge(node, worldState, realNewPos, null, -1, collision.requiresJump() ? jumpCost() : flatCost()); break; } - case JUMP_TO_VOXEL_LEVEL: { - upsertEdge(node, worldState, newPos, -1, jumpCost()); - break; - } - case VOXEL_UP: { - upsertEdge(node, worldState, Face.UP.offset(newPos), -1, flatCost()); - break; - } - case JUMP_TO_VOXEL_UP: { - upsertEdge(node, worldState, Face.UP.offset(newPos), -1, jumpCost()); - break; - } - case JUMP_TO_VOXEL_TWO_UP: { - upsertEdge(node, worldState, BetterBlockPos.offsetBy(newPos, 0, 2, 0), -1, jumpCost()); - break; - } - default: - throw new IllegalStateException(); } } } @@ -137,11 +180,12 @@ public class GreedySolver { throw new UnsupportedOperationException(); } - private void upsertEdge(Node node, WorldState worldState, long newPlayerPosition, long blockPlacement, int edgeCost) { - if (Main.DEBUG && blockPlacement == -1 && PlayerPhysics.determinePlayerRealSupportLevel(at(Face.DOWN.offset(newPlayerPosition), worldState), at(newPlayerPosition, worldState)) < 0) { - throw new IllegalStateException(); - } - Node neighbor = getNode(newPlayerPosition, node, worldState, blockPlacement); + private double blockReachDistance() { + throw new UnsupportedOperationException(); + } + + private void upsertEdge(Node node, WorldState worldState, long newPlayerPosition, Face sneakingTowards, long blockPlacement, int edgeCost) { + Node neighbor = getNode(newPlayerPosition, sneakingTowards, node, worldState, blockPlacement); if (Main.SLOW_DEBUG && blockPlacement != -1 && !neighbor.coalesceState(this).blockExists(blockPlacement)) { // only in slow_debug because this force-allocates a WorldState for every neighbor of every node! throw new IllegalStateException(); } @@ -189,15 +233,15 @@ public class GreedySolver { } } - private Node getNode(long playerPosition, Node prev, WorldState prevWorld, long blockPlacement) { - if (Main.DEBUG && blockPlacement != -1 && prev.coalesceState(this).blockExists(blockPlacement)) { + private Node getNode(long playerPosition, Face sneakingTowards, Node prev, WorldState prevWorld, long blockPlacement) { + if (Main.DEBUG && blockPlacement != -1 && prevWorld.blockExists(blockPlacement)) { throw new IllegalStateException(); } long worldStateZobristHash = prev.worldStateZobristHash; if (blockPlacement != -1) { worldStateZobristHash = WorldState.updateZobrist(worldStateZobristHash, blockPlacement); } - long code = playerPosition ^ worldStateZobristHash; + long code = Node.encode(playerPosition, sneakingTowards) ^ worldStateZobristHash; Node existing = nodes.get(code); if (existing != null) { return existing; @@ -206,7 +250,7 @@ public class GreedySolver { if (blockPlacement != -1) { newHeuristic += calculateHeuristicModifier(prevWorld, blockPlacement); } - Node node = new Node(playerPosition, worldStateZobristHash, blockPlacement, this, newHeuristic); + Node node = new Node(playerPosition, null, worldStateZobristHash, blockPlacement, this, newHeuristic); if (Main.DEBUG && node.nodeMapKey() != code) { throw new IllegalStateException(); } diff --git a/src/main/java/baritone/builder/Main.java b/src/main/java/baritone/builder/Main.java index 31befe91b..81e7a439c 100644 --- a/src/main/java/baritone/builder/Main.java +++ b/src/main/java/baritone/builder/Main.java @@ -57,6 +57,7 @@ public class Main { System.out.println(face.index); System.out.println(face.offset); System.out.println(face.oppositeIndex); + System.out.println("Horizontal " + face.horizontalIndex); } { System.out.println("Without"); @@ -268,7 +269,7 @@ public class Main { { DebugStates.debug(); } - { + for (int aaaa = 0; aaaa < 0; aaaa++) { Random rand = new Random(5021); int trials = 10_000_000; int[] X = new int[trials]; @@ -395,6 +396,16 @@ Branchy took 124ms PlayerPhysics.determinePlayerRealSupport(BlockStateCachedData.get(69), BlockStateCachedData.get(420)); PlayerPhysics.determinePlayerRealSupport(BlockStateCachedData.get(420), BlockStateCachedData.get(69)); }*/ + { + for (int sneak = 0; sneak < 4; sneak++) { + System.out.println("meow"); + System.out.println(sneak); + System.out.println(Node.encode(0, sneak)); + System.out.println(Node.encode(BetterBlockPos.POST_ADDITION_MASK, sneak)); + System.out.println(Node.decode(Node.encode(0, sneak))); + System.out.println(Node.decode(Node.encode(BetterBlockPos.POST_ADDITION_MASK, sneak))); + } + } System.exit(0); } } diff --git a/src/main/java/baritone/builder/Node.java b/src/main/java/baritone/builder/Node.java index 4dbd94477..1ed28c6a6 100644 --- a/src/main/java/baritone/builder/Node.java +++ b/src/main/java/baritone/builder/Node.java @@ -35,8 +35,8 @@ public class Node { // int unrealizedState; // no longer needed now that world state is binarized with scaffolding/build versus air // long unrealizedZobristParentHash; // no longer needed since we can compute it backwards with XOR - public Node(long pos, long zobristState, long unrealizedBlockPlacement, GreedySolver solver, int heuristic) { - this.pos = pos; + public Node(long pos, Face sneakingTowards, long zobristState, long unrealizedBlockPlacement, GreedySolver solver, int heuristic) { + this.posAndSneak = encode(pos, sneakingTowards); this.heapPosition = -1; this.cost = Integer.MAX_VALUE; this.heuristic = heuristic; @@ -69,6 +69,13 @@ public class Node { return myState; } + public static long encode(long pos, Face sneakingTowards) { + if (Main.DEBUG && sneakingTowards != null && sneakingTowards.vertical) { + throw new IllegalStateException(); + } + return sneakingTowards == null ? pos : encode(pos, sneakingTowards.horizontalIndex); + } + public static long encode(long pos, int sneak) { if (Main.DEBUG && (sneak < 0 || sneak > 3)) { throw new IllegalStateException(); @@ -77,19 +84,50 @@ public class Node { throw new IllegalStateException(); } long ret = pos - | (sneak & 0x1L) << 26 // snugly and cozily fit into the two bits left between X and Y and between Y and Z - | (sneak & 0x2L) << 35 + | (sneak & 0x1L) << 26 // snugly and cozily fit into the two bits left between Y and Z + | (sneak & 0x2L) << 35 // and between X and Y | 1L << 63; // and turn on the top bit as a signal - if (Main.DEBUG && ((ret & BetterBlockPos.POST_ADDITION_MASK) != pos)) { + if (Main.DEBUG && ((ret & BetterBlockPos.POST_ADDITION_MASK) != pos)) { // ensure that POST_ADDITION_MASK undoes this trickery (see the comments around POST_ADDITION_MASK definition for why/how) throw new IllegalStateException(); } return ret; } + public static int decode(long posAndSneak) { + if (Main.DEBUG && !hasSneak(posAndSneak)) { + throw new IllegalStateException(); + } + return (int) (posAndSneak >> 26 & 0x1L | posAndSneak >> 35 & 0x2L); + } + + public boolean sneaking() { + return hasSneak(posAndSneak); + } + + public static boolean hasSneak(long posAndSneak) { + return posAndSneak < 0; // checks the MSB like a boss (epically) + } + public long pos() { return posAndSneak & BetterBlockPos.POST_ADDITION_MASK; } + public Face sneakDirectionFromPlayerToSupportingBlock() { + if (hasSneak(posAndSneak)) { + return Face.HORIZONTALS[Face.oppositeHorizontal(decode(posAndSneak))]; + } else { + return null; + } + } + + public Face sneakDirectionFromSupportingBlockToPlayer() { + if (hasSneak(posAndSneak)) { + return Face.HORIZONTALS[decode(posAndSneak)]; + } else { + return null; + } + } + public long nodeMapKey() { return posAndSneak ^ worldStateZobristHash; } diff --git a/src/main/java/baritone/builder/PlayerPhysics.java b/src/main/java/baritone/builder/PlayerPhysics.java index 8c84a27b4..ab62fb7df 100644 --- a/src/main/java/baritone/builder/PlayerPhysics.java +++ b/src/main/java/baritone/builder/PlayerPhysics.java @@ -21,8 +21,8 @@ import baritone.api.utils.BetterBlockPos; public class PlayerPhysics { - public static int determinePlayerRealSupportLevel(BlockStateCachedData underneath, BlockStateCachedData within) { - switch (canPlayerStand(underneath, within)) { + public static int determinePlayerRealSupportLevel(BlockStateCachedData underneath, BlockStateCachedData within, VoxelResidency residency) { + switch (residency) { case STANDARD_WITHIN_SUPPORT: return within.collisionHeightBlips(); case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK: @@ -32,6 +32,10 @@ public class PlayerPhysics { } } + public static int determinePlayerRealSupportLevel(BlockStateCachedData underneath, BlockStateCachedData within) { + return determinePlayerRealSupportLevel(underneath, within, canPlayerStand(underneath, within)); + } + /** * the player Y is within within. i.e. the player Y is greater than or equal within and less than within+1 *

@@ -88,22 +92,17 @@ public class PlayerPhysics { } } - public static Collision playerTravelCollides(int feetBlips, - BlockStateCachedData underneath, - BlockStateCachedData feet, - BlockStateCachedData above, - BlockStateCachedData aboveAbove, - long newPos, WorldState worldState, SolverEngineInput engineInput) { - return playerTravelCollides(feetBlips, - above, - engineInput.at(BetterBlockPos.offsetBy(newPos, 0, 2, 0), worldState), - engineInput.at(Face.UP.offset(newPos), worldState), - engineInput.at(newPos, worldState), - engineInput.at(Face.DOWN.offset(newPos), worldState), - underneath, - feet, - aboveAbove, - engineInput.at(BetterBlockPos.offsetBy(newPos, 0, 3, 0), worldState)); + public static Collision playerTravelCollides(Column within, Column into) { + return playerTravelCollides(within.feetBlips, + within.above, + into.above, + into.head, + into.feet, + into.underneath, + within.underneath, + within.feet, + within.aboveAbove, + into.aboveAbove); } /** @@ -230,14 +229,44 @@ public class PlayerPhysics { } public enum Collision { + // TODO maybe we need another option that is like "you could do it, but you shouldn't". like, "if you hit W, you would walk forward, but you wouldn't like the outcome" such as cactus or lava or something + BLOCKED, // if you hit W, you would not travel (example: walking into wall) JUMP_TO_VOXEL_LEVEL, // blocked, BUT, if you jumped, you would end up at voxel level. this one is rare, it could only happen if you jump onto a block that is between 0.5 and 1.0 blocks high, such as 7-high snow layers JUMP_TO_VOXEL_UP, // blocked, BUT, if you jumped, you would end up at one voxel higher. this is the common case for jumping. JUMP_TO_VOXEL_TWO_UP, // blocked, BUT, if you jumped, you would end up two voxels higher. this can only happen for weird blocks like jumping out of soul sand and up one VOXEL_UP, // if you hit W, you will end up at a position that's a bit higher, such that you'd determineRealPlayerSupport up by one (example: walking from a partial block to a full block or higher, e.g. half slab to full block, or soul sand to full block, or soul sand to full block+carpet on top) VOXEL_LEVEL, // if you hit W, you will end up at a similar position, such that you'd determineRealPlayerSupport at the same integer grid location (example: walking forward on level ground) - FALL // if you hit W, you will not immediately collide with anything, at all, to the front or to the bottom (example: walking off a cliff) - // TODO maybe we need another option that is like "you could do it, but you shouldn't". like, "if you hit W, you would walk forward, but you wouldn't like the outcome" such as cactus or lava or something + FALL; // if you hit W, you will not immediately collide with anything, at all, to the front or to the bottom (example: walking off a cliff) + + public int voxelVerticalOffset() { + switch (this) { + case VOXEL_LEVEL: + case JUMP_TO_VOXEL_LEVEL: + return 0; + case VOXEL_UP: + case JUMP_TO_VOXEL_UP: + return 1; + case JUMP_TO_VOXEL_TWO_UP: + return 2; + default: + throw new IllegalStateException(); + } + } + + public boolean requiresJump() { + switch (this) { + case VOXEL_LEVEL: + case VOXEL_UP: + return false; + case JUMP_TO_VOXEL_LEVEL: + case JUMP_TO_VOXEL_UP: + case JUMP_TO_VOXEL_TWO_UP: + return true; + default: + throw new IllegalStateException(); + } + } } public static int playerFalls(long newPos, WorldState worldState, SolverEngineInput engineInput) { @@ -263,10 +292,25 @@ public class PlayerPhysics { case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK: case STANDARD_WITHIN_SUPPORT: // found our landing spot + if (Main.DEBUG && descent <= 0) { + throw new IllegalStateException(); + } return descent; default: throw new IllegalStateException(); } } } + + /** + * If the player feet is greater than the return from this function, the player can sneak out at that altitude without colliding with this column + *

+ * If there is no collision possible this will return a negative number (which should fit in fine with the above ^ use case) + */ + public static int highestCollision(BlockStateCachedData underneath, BlockStateCachedData within) { + return Math.max( + underneath.collidesWithPlayer ? underneath.collisionHeightBlips() - Blip.FULL_BLOCK : -1, + within.collidesWithPlayer ? within.collisionHeightBlips() : -1 + ); + } } diff --git a/src/main/java/baritone/builder/WorldState.java b/src/main/java/baritone/builder/WorldState.java index 4e39a9a5a..8320a6550 100644 --- a/src/main/java/baritone/builder/WorldState.java +++ b/src/main/java/baritone/builder/WorldState.java @@ -24,6 +24,9 @@ import java.util.BitSet; public abstract class WorldState { + /** + * https://en.wikipedia.org/wiki/Zobrist_hashing + */ public final long zobristHash; protected WorldState(long zobristHash) {