diff --git a/src/main/java/baritone/builder/Blip.java b/src/main/java/baritone/builder/Blip.java index c1a342d39..da7de9978 100644 --- a/src/main/java/baritone/builder/Blip.java +++ b/src/main/java/baritone/builder/Blip.java @@ -35,10 +35,11 @@ public class Blip { public static final double RATIO = 0.0625; public static final int HALF_BLOCK = 8; public static final int PLAYER_HEIGHT_SLIGHT_UNDERESTIMATE = 28; - public static final int PLAYER_HEIGHT_SLIGHT_OVERESTIMATE = PLAYER_HEIGHT_SLIGHT_UNDERESTIMATE + 1; + public static final int PLAYER_HEIGHT_SLIGHT_OVERESTIMATE = 29; public static final int TWO_BLOCKS = 2 * FULL_BLOCK; public static final int FEET_TO_EYE_APPROX = (int) (IPlayerContext.eyeHeight(false) / RATIO); public static final int JUMP = 20; // 1.25 + public static final int TALLEST_BLOCK = FULL_BLOCK + HALF_BLOCK; // 24 public static double playerEyeFromFeetBlips(int feetBlips, boolean sneaking) { return feetBlips * RATIO + IPlayerContext.eyeHeight(sneaking); @@ -46,7 +47,7 @@ public class Blip { static { double realPlayerHeight = 1.8; - if (PLAYER_HEIGHT_SLIGHT_OVERESTIMATE * RATIO <= realPlayerHeight || PLAYER_HEIGHT_SLIGHT_UNDERESTIMATE * RATIO >= realPlayerHeight) { + if (PLAYER_HEIGHT_SLIGHT_OVERESTIMATE * RATIO <= realPlayerHeight || PLAYER_HEIGHT_SLIGHT_UNDERESTIMATE * RATIO >= realPlayerHeight || PLAYER_HEIGHT_SLIGHT_OVERESTIMATE != PLAYER_HEIGHT_SLIGHT_UNDERESTIMATE + 1) { throw new IllegalStateException(); } if (PER_BLOCK * RATIO != 1) { diff --git a/src/main/java/baritone/builder/BlockStateCachedData.java b/src/main/java/baritone/builder/BlockStateCachedData.java index d2095924d..39d3c62b0 100644 --- a/src/main/java/baritone/builder/BlockStateCachedData.java +++ b/src/main/java/baritone/builder/BlockStateCachedData.java @@ -30,7 +30,7 @@ public final class BlockStateCachedData { private static final BlockStateCachedData[] PER_STATE = Main.DATA_PROVIDER.allNullable(); public static final BlockStateCachedData SCAFFOLDING = new BlockStateCachedData(new BlockStateCachedDataBuilder().collidesWithPlayer(true).fullyWalkableTop().collisionHeight(1).canPlaceAgainstMe()); - public static final BlockStateCachedData AIR = PER_STATE[0]; + public static final BlockStateCachedData AIR = new BlockStateCachedData(new BlockStateCachedDataBuilder().setAir()); public static final BlockStateCachedData OUT_OF_BOUNDS = new BlockStateCachedData(new BlockStateCachedDataBuilder().collidesWithPlayer(true).collisionHeight(1)); static { diff --git a/src/main/java/baritone/builder/BlockStateCachedDataBuilder.java b/src/main/java/baritone/builder/BlockStateCachedDataBuilder.java index ca15ccc41..ef54f2488 100644 --- a/src/main/java/baritone/builder/BlockStateCachedDataBuilder.java +++ b/src/main/java/baritone/builder/BlockStateCachedDataBuilder.java @@ -86,7 +86,7 @@ public class BlockStateCachedDataBuilder { * Should be 1 for trapdoors because when they're open, they touch the top face of the voxel */ public BlockStateCachedDataBuilder collisionHeight(double y) { - for (int h = 0; h <= Blip.PER_BLOCK + Blip.HALF_BLOCK; h++) { + for (int h = 0; h <= Blip.TALLEST_BLOCK; h++) { // max height of if (y == h * Blip.RATIO) { collisionHeightBlips = h; return this; @@ -263,7 +263,7 @@ public class BlockStateCachedDataBuilder { if ((playerMustBeHorizontalFacingInOrderToPlaceMe != null || playerMustBeEntityFacingInOrderToPlaceMe != null) && mustBePlacedAgainst == null) { throw new IllegalStateException(); } - if (collisionHeightBlips != null && (collisionHeightBlips > Blip.FULL_BLOCK + Blip.HALF_BLOCK || collisionHeightBlips < 0)) { // playerphysics assumes this is never true + if (collisionHeightBlips != null && (collisionHeightBlips > Blip.TALLEST_BLOCK || collisionHeightBlips < 0)) { // playerphysics assumes this is never true throw new IllegalStateException(); } if (collidesWithPlayer ^ collisionHeightBlips != null) { diff --git a/src/main/java/baritone/builder/Column.java b/src/main/java/baritone/builder/Column.java index 6b3dd74c0..c354b5584 100644 --- a/src/main/java/baritone/builder/Column.java +++ b/src/main/java/baritone/builder/Column.java @@ -25,13 +25,14 @@ 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 + * A mutable class representing a 1x6x1 column of blocks *

* Mutable because allocations are not on the table for the core solver loop */ public class Column { public long pos; + //public BlockStateCachedData underUnderneath; public BlockStateCachedData underneath; public BlockStateCachedData feet; public BlockStateCachedData head; @@ -42,13 +43,22 @@ public class Column { public void initFrom(long pos, WorldState worldState, SolverEngineInput engineInput) { this.pos = pos; + //this.underUnderneath = engineInput.at((pos + DOWN_2) & BetterBlockPos.POST_ADDITION_MASK, worldState); 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); + init(); + } + + public void init() { this.voxelResidency = PlayerPhysics.canPlayerStand(underneath, feet); this.feetBlips = boxNullable(PlayerPhysics.determinePlayerRealSupportLevel(underneath, feet, voxelResidency)); + if (feetBlips != null && !playerCanExistAtFootBlip(feetBlips)) { // TODO is this the correct way to handle head collision? + voxelResidency = PlayerPhysics.VoxelResidency.IMPOSSIBLE_WITHOUT_SUFFOCATING; // TODO this is a misuse of this enum value i think + feetBlips = null; + } } public boolean playerCanExistAtFootBlip(int blipWithinFeet) { @@ -70,12 +80,16 @@ public class Column { return feetBlips != null; } + private static final long DOWN_2 = (Y_MASK - 1) << Y_SHIFT; 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_2 != BetterBlockPos.toLong(0, -2, 0)) { + throw new IllegalStateException(); + } if (DOWN_1 != BetterBlockPos.toLong(0, -1, 0)) { throw new IllegalStateException(); } @@ -85,7 +99,7 @@ public class Column { if (UP_2 != BetterBlockPos.toLong(0, 2, 0)) { throw new IllegalStateException(); } - if (UP_3 != BetterBlockPos.toLong(0, 4, 0)) { + if (UP_3 != BetterBlockPos.toLong(0, 3, 0)) { throw new IllegalStateException(); } } diff --git a/src/main/java/baritone/builder/PlayerPhysics.java b/src/main/java/baritone/builder/PlayerPhysics.java index ab62fb7df..54d55bc0d 100644 --- a/src/main/java/baritone/builder/PlayerPhysics.java +++ b/src/main/java/baritone/builder/PlayerPhysics.java @@ -93,6 +93,9 @@ public class PlayerPhysics { } public static Collision playerTravelCollides(Column within, Column into) { + if (Main.DEBUG && within.head.collidesWithPlayer) { + throw new IllegalStateException(); + } return playerTravelCollides(within.feetBlips, within.above, into.above, @@ -105,6 +108,89 @@ public class PlayerPhysics { into.aboveAbove); } + + /** + * Is there a movement (ascend, level, descend) that the player can do AND UNDO? + * This is basically the same as playerTravelCollides, but, it's okay to fall, but only as long as you don't fall further than what can be jumped back up + * + * @return the integer change in voxel Y, or null if there is no such option + */ + public static Integer bidirectionalPlayerTravel( + Column within, + Column into, + BlockStateCachedData underUnderneathInto, + BlockStateCachedData underUnderUnderneathInto + ) { + switch (playerTravelCollides(within, into)) { + + case BLOCKED: + return null; + + case JUMP_TO_VOXEL_TWO_UP: + return 2; + case VOXEL_UP: + case JUMP_TO_VOXEL_UP: + return 1; + case VOXEL_LEVEL: + case JUMP_TO_VOXEL_LEVEL: + return 0; + + case FALL: + if (Main.DEBUG && (canPlayerStand(into.underneath, into.feet) != VoxelResidency.FLOATING || !into.playerCanExistAtFootBlip(within.feetBlips))) { + throw new IllegalStateException(); + } + VoxelResidency downOne = canPlayerStand(underUnderneathInto, into.underneath); + switch (downOne) { + case PREVENTED_BY_UNDERNEATH: + case PREVENTED_BY_WITHIN: + return null; // a block we aren't allowed to stand on + case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK: + case STANDARD_WITHIN_SUPPORT: + int fallBy = within.feetBlips + Blip.FULL_BLOCK - determinePlayerRealSupportLevel(underUnderneathInto, into.underneath, downOne); + if (fallBy < 1) { + throw new IllegalStateException(); + } + if (fallBy <= Blip.JUMP) { + return -1; // we could jump back! yay! + } + return null; // one-way trip + default: + throw new IllegalStateException(); + case FLOATING: + break; + } + // ^ as shown, we are floating in downOne, so let's try downTwo + // but first let's make sure it's possible + int highestBlipsWithinUnderUnderUnder = Blip.FULL_BLOCK - 1; // 15 + int highestJumpFromTwoUnder = highestBlipsWithinUnderUnderUnder + Blip.JUMP; // 35 + int highestJumpIntoThisVoxel = highestJumpFromTwoUnder - Blip.TWO_BLOCKS; // 3 + if (within.feetBlips > highestJumpIntoThisVoxel) { + return null; + } + VoxelResidency downTwo = canPlayerStand(underUnderUnderneathInto, underUnderneathInto); + switch (downTwo) { + case UNDERNEATH_PROTRUDES_AT_OR_ABOVE_FULL_BLOCK: // tallest block is 24 blips, which is less than 29, so this case is impossible + case PREVENTED_BY_UNDERNEATH: + case PREVENTED_BY_WITHIN: + case FLOATING: + return null; + case STANDARD_WITHIN_SUPPORT: + int fallBy = within.feetBlips + Blip.TWO_BLOCKS - determinePlayerRealSupportLevel(underUnderUnderneathInto, underUnderneathInto, downTwo); + if (fallBy < Blip.FULL_BLOCK + 1) { + throw new IllegalStateException(); + } + if (fallBy <= Blip.JUMP) { + return -2; // we could jump back! yay! + } + return null; // one-way trip + default: + throw new IllegalStateException(); + } + default: + throw new IllegalStateException(); + } + } + /** * "Can the player walk forwards without needing to break anything?" *

@@ -150,7 +236,7 @@ public class PlayerPhysics { return Collision.JUMP_TO_VOXEL_TWO_UP; } } - if (alreadyWithinU && A.collidesWithPlayer) { + if (alreadyWithinU && A.collidesWithPlayer) { // now that voxel two up, the result within A, is eliminated, we can't proceed if A would block at head level return Collision.BLOCKED; // we are too tall. bonk! } // D cannot prevent us from doing anything because it cant be higher than 1.5. therefore, makes sense to check CB before DC. @@ -169,7 +255,7 @@ public class PlayerPhysics { return Collision.JUMP_TO_VOXEL_UP; } // else this is possible! if (Main.DEBUG && (U.collidesWithPlayer || A.collidesWithPlayer || !alreadyWithinU)) { - // must already be colliding with U because in order for this step to be even possible, feet must be at least HALF_BLOCK + // must already be colliding with U because in order for this non-jump voxel-up step to be even possible, feet must be at least HALF_BLOCK throw new IllegalStateException(); } // B can collide with player here, such as if X is soul sand, C is full block, and B is carpet @@ -177,7 +263,7 @@ public class PlayerPhysics { } // voxelUp is impossible. pessimistically, this means B is colliding. optimistically, this means B and C are air. if (B.collidesWithPlayer) { - // AB can no longer be possible since it was checked with E and F + // voxel up and voxel two up are both eliminated, so... return Collision.BLOCKED; // we have ruled out stepping on top of B, so now if B is still colliding there is no way forward } int stayLevel = determinePlayerRealSupportLevel(D, C); @@ -192,10 +278,11 @@ public class PlayerPhysics { } } if (stayLevel > couldStepUpTo) { // staying within the same voxel means that a jump will always succeed + // aka. the jump might headbonk but not in a way that prevents the action. also TODO this is an example of where Collision.INADVISABLE could come in such as if E/F/U/A were like lava or something return Collision.JUMP_TO_VOXEL_LEVEL; } - return Collision.VOXEL_LEVEL; - } + return Collision.VOXEL_LEVEL; // btw it's possible that stayLevel < feetBlips, since a small fall is sorta ignored and treated as a VOXEL_LEVEL + } // voxel_level (C within, D underneath) is eliminated, only remaining possibilities are blocked or fall if (C.collidesWithPlayer) { return Collision.BLOCKED; } @@ -212,7 +299,7 @@ public class PlayerPhysics { } } - public static boolean protrudesIntoThirdBlock(int feet) { + public static boolean protrudesIntoThirdBlock(int feet) { // only false for feet between 0 and 3. true for 4 and up return feet > Blip.TWO_BLOCKS - Blip.PLAYER_HEIGHT_SLIGHT_OVERESTIMATE; // > and not >= because the player height is a slight overestimate } @@ -220,10 +307,14 @@ public class PlayerPhysics { if (Blip.PLAYER_HEIGHT_SLIGHT_OVERESTIMATE >= Blip.TWO_BLOCKS || Blip.PLAYER_HEIGHT_SLIGHT_OVERESTIMATE + Blip.HALF_BLOCK <= Blip.TWO_BLOCKS) { throw new IllegalStateException("Assumptions made in playerTravelCollides"); } - int maxFeet = Blip.FULL_BLOCK - 1; - int couldJumpUpTo = maxFeet + Blip.JUMP; - int maxWithinAB = couldJumpUpTo - Blip.TWO_BLOCKS; + if (Blip.TALLEST_BLOCK - Blip.FULL_BLOCK + Blip.JUMP - Blip.TWO_BLOCKS >= 0) { + throw new IllegalStateException("Assumption made in bidirectionalPlayerTravel"); + } + int maxFeet = Blip.FULL_BLOCK - 1; // 15 + int couldJumpUpTo = maxFeet + Blip.JUMP; // 35 + int maxWithinAB = couldJumpUpTo - Blip.TWO_BLOCKS; // 3 if (protrudesIntoThirdBlock(maxWithinAB)) { + // btw this is literally only 1 blip away from being true lol throw new IllegalStateException("Oh no, if this is true then playerTravelCollides needs to check another layer above EF"); } } @@ -281,7 +372,7 @@ public class PlayerPhysics { } VoxelResidency res = canPlayerStand(engineInput.at(under, worldState), engineInput.at(support, worldState)); if (Main.DEBUG && descent == 0 && res != VoxelResidency.FLOATING) { - throw new IllegalStateException(); // CD shouldn't collide, it should be D and the one beneath... + throw new IllegalStateException(); // CD shouldn't collide, it should be D and the one beneath... (playerTravelCollides would have returned BLOCKED or VOXEL_LEVEL) } switch (res) { case FLOATING: @@ -292,9 +383,6 @@ 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(); diff --git a/src/test/java/baritone/builder/NavigableSurfaceTest.java b/src/test/java/baritone/builder/NavigableSurfaceTest.java index fd6f5de92..754fc9bec 100644 --- a/src/test/java/baritone/builder/NavigableSurfaceTest.java +++ b/src/test/java/baritone/builder/NavigableSurfaceTest.java @@ -825,6 +825,8 @@ public class NavigableSurfaceTest { "XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|X XXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX X\n" + "XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXX \n" + "XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX\n"; + // basically the story is that i saw THIS ^^^ weird ass shape, then i spent the next few hours figuring out exactly what happened and convincing myself that it actually is accurate and I don't have any bugs causing this incorrectly + // because i mean what are the odds lol, it looks crazy assertEquals(shouldBeWeird, reportAllFourWalls(surface3)); } diff --git a/src/test/java/baritone/builder/PlayerPhysicsTest.java b/src/test/java/baritone/builder/PlayerPhysicsTest.java new file mode 100644 index 000000000..083c7193a --- /dev/null +++ b/src/test/java/baritone/builder/PlayerPhysicsTest.java @@ -0,0 +1,146 @@ +/* + * 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 org.junit.Test; + +import static org.junit.Assert.*; + +public class PlayerPhysicsTest { + + @Test + public void testBasic() { + assertEquals(16, PlayerPhysics.highestCollision(BlockStateCachedData.SCAFFOLDING, BlockStateCachedData.SCAFFOLDING)); + + Column normal = new Column(); + normal.underneath = BlockStateCachedData.SCAFFOLDING; + normal.feet = BlockStateCachedData.AIR; + normal.head = BlockStateCachedData.AIR; + normal.above = BlockStateCachedData.AIR; + normal.aboveAbove = BlockStateCachedData.AIR; + normal.init(); + + Column up = new Column(); + up.underneath = BlockStateCachedData.SCAFFOLDING; + up.feet = BlockStateCachedData.SCAFFOLDING; + up.head = BlockStateCachedData.AIR; + up.above = BlockStateCachedData.AIR; + up.aboveAbove = BlockStateCachedData.AIR; + up.init(); + + assertEquals(PlayerPhysics.Collision.VOXEL_LEVEL, PlayerPhysics.playerTravelCollides(normal, normal)); + assertEquals(PlayerPhysics.Collision.JUMP_TO_VOXEL_UP, PlayerPhysics.playerTravelCollides(normal, up)); + } + + private static final BlockStateCachedData[] BY_HEIGHT; + + static { + BY_HEIGHT = new BlockStateCachedData[Blip.FULL_BLOCK + 1]; + for (int height = 1; height <= Blip.FULL_BLOCK; height++) { + BY_HEIGHT[height] = new BlockStateCachedData(new BlockStateCachedDataBuilder().collidesWithPlayer(true).fullyWalkableTop().collisionHeight(height * Blip.RATIO)); + } + BY_HEIGHT[0] = BlockStateCachedData.AIR; + } + + private static BlockStateCachedData[] makeColToHeight(int height) { + height += Blip.FULL_BLOCK * 3; // i don't truck with negative division / modulo + if (height < 0) { + throw new IllegalStateException(); + } + int fullBlocks = height / Blip.FULL_BLOCK; + BlockStateCachedData[] ret = new BlockStateCachedData[7]; + for (int i = 0; i < fullBlocks; i++) { + ret[i] = BlockStateCachedData.SCAFFOLDING; + } + ret[fullBlocks] = BY_HEIGHT[height % Blip.FULL_BLOCK]; + for (int i = fullBlocks + 1; i < ret.length; i++) { + ret[i] = BlockStateCachedData.AIR; + } + return ret; + } + + private static Column toCol(BlockStateCachedData[] fromCol) { + Column col = new Column(); + col.underneath = fromCol[2]; + col.feet = fromCol[3]; + col.head = fromCol[4]; + col.above = fromCol[5]; + col.aboveAbove = fromCol[6]; + col.init(); + return col; + } + + @Test + public void testMakeCol() { + for (int i = 0; i < Blip.FULL_BLOCK; i++) { + Column col = toCol(makeColToHeight(i)); + assertTrue(col.standing()); + assertEquals(i, (int) col.feetBlips); + } + assertFalse(toCol(makeColToHeight(-1)).standing()); + assertFalse(toCol(makeColToHeight(Blip.FULL_BLOCK)).standing()); + } + + @Test + public void testAscDesc() { + for (int startHeight = 0; startHeight < Blip.FULL_BLOCK; startHeight++) { + BlockStateCachedData[] fromCol = makeColToHeight(startHeight); + Column from = toCol(fromCol); + assertEquals(startHeight, (int) from.feetBlips); + + for (int endHeight = startHeight - Blip.JUMP - 10; endHeight <= startHeight + Blip.JUMP + 10; endHeight++) { + BlockStateCachedData[] toCol = makeColToHeight(endHeight); + Column to = toCol(toCol); + int endVoxel = (endHeight + Blip.FULL_BLOCK * 10) / Blip.FULL_BLOCK - 10; // hate negative division rounding to zero, punch negative division rounding to zero + + PlayerPhysics.Collision col = PlayerPhysics.playerTravelCollides(from, to); + Integer dy = PlayerPhysics.bidirectionalPlayerTravel(from, to, toCol[1], toCol[0]); + assertEquals(col == PlayerPhysics.Collision.BLOCKED, endHeight > startHeight + Blip.JUMP); + assertEquals(col == PlayerPhysics.Collision.FALL, endVoxel < 0); + assertEquals(dy == null, endHeight > startHeight + Blip.JUMP || endHeight < startHeight - Blip.JUMP); + + if (dy != null) { + Integer reverse = -dy; + BlockStateCachedData[] shiftedTo = shift(toCol, reverse); + BlockStateCachedData[] shiftedFrom = shift(fromCol, reverse); + // make sure it's actually bidirectional + assertEquals(reverse, PlayerPhysics.bidirectionalPlayerTravel(toCol(shiftedTo), toCol(shiftedFrom), shiftedFrom[1], shiftedFrom[0])); + } + if (col != PlayerPhysics.Collision.BLOCKED && col != PlayerPhysics.Collision.FALL) { + assertEquals(col.voxelVerticalOffset(), endVoxel); + assertEquals(endVoxel, (int) dy); + assertEquals(col.requiresJump(), endHeight > startHeight + Blip.HALF_BLOCK); + } + System.out.println(startHeight + " " + (endHeight - startHeight) + " " + col + " " + endVoxel + " " + dy); + } + } + } + + private static BlockStateCachedData[] shift(BlockStateCachedData[] col, int dy) { + BlockStateCachedData[] ret = new BlockStateCachedData[7]; + for (int i = 0; i < ret.length; i++) { + int j = i - dy; + if (j >= 0 && j < col.length) { + ret[i] = col[j]; + } else { + ret[i] = BlockStateCachedData.OUT_OF_BOUNDS; + } + } + return ret; + } +}