diff --git a/src/main/java/baritone/builder/BlockStateCachedData.java b/src/main/java/baritone/builder/BlockStateCachedData.java index f64e30665..4563e68a7 100644 --- a/src/main/java/baritone/builder/BlockStateCachedData.java +++ b/src/main/java/baritone/builder/BlockStateCachedData.java @@ -31,7 +31,7 @@ public final class BlockStateCachedData { public static final BlockStateCachedData SCAFFOLDING = new BlockStateCachedData(new BlockStateCachedDataBuilder().collidesWithPlayer(true).fullyWalkableTop().collisionHeight(1).canPlaceAgainstMe()); public final boolean fullyWalkableTop; - public final Integer collisionHeightBlips; + private final int collisionHeightBlips; public final boolean isAir; public final boolean collidesWithPlayer; @@ -52,7 +52,11 @@ public final class BlockStateCachedData { this.isAir = builder.isAir(); this.fullyWalkableTop = builder.isFullyWalkableTop(); this.collidesWithPlayer = builder.isCollidesWithPlayer(); - this.collisionHeightBlips = builder.collisionHeightBlips(); + if (collidesWithPlayer) { + this.collisionHeightBlips = builder.collisionHeightBlips(); + } else { + this.collisionHeightBlips = -1; + } this.mustSneakWhenPlacingAgainstMe = builder.isMustSneakWhenPlacingAgainstMe(); this.options = Collections.unmodifiableList(builder.howCanIBePlaced()); @@ -60,6 +64,13 @@ public final class BlockStateCachedData { this.againstMe = builder.placeAgainstMe(); } + public int collisionHeightBlips() { + if (Main.DEBUG && !collidesWithPlayer) { // confirmed and tested: when DEBUG is false, proguard removes this if in the first pass, then inlines the calls in the second pass, making this just as good as a field access in release builds + throw new IllegalStateException(); + } + return collisionHeightBlips; + } + public boolean possibleAgainstMe(BlockStatePlacementOption placement) { if (Main.fakePlacementForPerformanceTesting) { return Main.RAND.nextInt(10) < 8; diff --git a/src/main/java/baritone/builder/BlockStateCachedDataBuilder.java b/src/main/java/baritone/builder/BlockStateCachedDataBuilder.java index bad922b99..7c9d4cf59 100644 --- a/src/main/java/baritone/builder/BlockStateCachedDataBuilder.java +++ b/src/main/java/baritone/builder/BlockStateCachedDataBuilder.java @@ -30,7 +30,7 @@ public class BlockStateCachedDataBuilder { private boolean fullyWalkableTop; private boolean collidesWithPlayer; private boolean mustSneakWhenPlacingAgainstMe; - private boolean falling; + private boolean mustBePlacedBottomToTop; /** * Examples: *
@@ -76,6 +76,15 @@ public class BlockStateCachedDataBuilder { return fullyWalkableTop; } + /** + * The highest collision extension of this block possible + *
+ * For example, should be 1 for stairs, even though part of the top face is really 0.5 + *
+ * Should be 1 for top slabs + *
+ * 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 = 1; h <= Blip.PER_BLOCK + Blip.HALF_BLOCK; h++) { if (y == h * Blip.RATIO) { @@ -131,8 +140,8 @@ public class BlockStateCachedDataBuilder { return this; } - public BlockStateCachedDataBuilder falling() { - falling = true; + public BlockStateCachedDataBuilder mustBePlacedBottomToTop() { + mustBePlacedBottomToTop = true; return this; } @@ -166,7 +175,7 @@ public class BlockStateCachedDataBuilder { if (playerMustBeEntityFacingInOrderToPlaceMe == face) { continue; } - if (falling && face != Face.DOWN) { + if (mustBePlacedBottomToTop && face != Face.DOWN) { continue; } if (canOnlyPlaceAgainst != null && face != canOnlyPlaceAgainst) { @@ -214,6 +223,14 @@ public class BlockStateCachedDataBuilder { return new PlaceAgainstData(face, face.vertical ? Half.EITHER : mustBePlacedAgainst, mustSneakWhenPlacingAgainstMe); } + /** + * The idea here is that I codify all my assumptions in one place instead of having ad hoc checks absolutely everywhere + *
+ * Example: in PlayerPhysics, I made an assumption that a block will never have a collision block taller than 1.5 blocks (e.g. like a fence)
+ * When I wrote the code that assumed that, I also added a check here to make sure every block is like that.
+ * If, in some future update to Minecraft, mojang adds a block that's even taller than a fence, it will be caught here immediately, with a comment saying "playerphysics assumes this is never true"
+ * This way, I'll know immediately, instead of pathing randomly trying to do something impossible with that new block and it being really confusing and annoying.
+ */
public void sanityCheck() {
if (isAir()) {
if (!howCanIBePlaced().isEmpty()) {
@@ -246,17 +263,10 @@ public class BlockStateCachedDataBuilder {
if ((playerMustBeHorizontalFacingInOrderToPlaceMe != null || playerMustBeEntityFacingInOrderToPlaceMe != null) && mustBePlacedAgainst == null) {
throw new IllegalStateException();
}
- if (isFullyWalkableTop() ^ collisionHeightBlips != null) {
- if (!isFullyWalkableTop() && collisionHeightBlips > Blip.PER_BLOCK) {
- // exception for fences, walls
- } else {
- throw new IllegalStateException();
- }
- }
if (collisionHeightBlips != null && (collisionHeightBlips > Blip.FULL_BLOCK + Blip.HALF_BLOCK || collisionHeightBlips <= 0)) { // playerphysics assumes this is never true
throw new IllegalStateException();
}
- if (collidesWithPlayer && collisionHeightBlips == null) {
+ if (collidesWithPlayer ^ collisionHeightBlips != null) {
throw new IllegalStateException();
}
if (fullyWalkableTop && !collidesWithPlayer) {
diff --git a/src/main/java/baritone/builder/BlockStatePlacementOption.java b/src/main/java/baritone/builder/BlockStatePlacementOption.java
index 18d3ca29a..a4c5d2685 100644
--- a/src/main/java/baritone/builder/BlockStatePlacementOption.java
+++ b/src/main/java/baritone/builder/BlockStatePlacementOption.java
@@ -147,7 +147,7 @@ public class BlockStatePlacementOption {
if (playerMustBeHorizontalFacing.isPresent()) {
return eye.flatDirectionTo(hit) == playerMustBeHorizontalFacing.get();
}
- if (playerMustBeEntityFacing.isPresent()) { // handle piston, dispenser, observer
+ if (playerMustBeEntityFacing.isPresent()) { // handle piston, dispenser, dropper, observer
if (!hit.inOriginUnitVoxel()) {
throw new IllegalStateException();
}
@@ -156,7 +156,7 @@ public class BlockStatePlacementOption {
double dx = Math.abs(eye.x - 0.5);
double dz = Math.abs(eye.z - 0.5);
if (dx < 2 - ENTITY_FACING_TOLERANCE && dz < 2 - ENTITY_FACING_TOLERANCE) { // < 1.99
- if (eye.y < 0) { // eye below placement level = it will be facing down, so this is only okay if we wantthat
+ if (eye.y < 0) { // eye below placement level = it will be facing down, so this is only okay if we want that
return entFace == Face.DOWN;
}
if (eye.y > 2) { // same for up, if y>2 then it will be facing up
@@ -165,7 +165,7 @@ public class BlockStatePlacementOption {
} else if (!(dx > 2 + ENTITY_FACING_TOLERANCE || dz > 2 + ENTITY_FACING_TOLERANCE)) { // > 2.01
// this is the ambiguous case, because we are neither unambiguously both-within-2 (previous case), nor unambiguously either-above-two (this elseif condition).
// UP/DOWN are impossible, but that's caught by flat check
- if (eye.y < 0 || eye.y > 2) {
+ if (eye.y < 0 || eye.y > 2) { // this check is okay because player eye height is not an even multiple of blips, therefore there's no way for it to == 0 or == 2, so using > and < is safe
return false; // anything that could cause up/down instead of horizontal is also not allowed sadly
}
} // else we are in unambiguous either-above-two, putting us in simple horizontal mode, so fallthrough to flat condition is correct, yay
diff --git a/src/main/java/baritone/builder/CuboidBounds.java b/src/main/java/baritone/builder/CuboidBounds.java
index 3ea21b3b5..3af590bc8 100644
--- a/src/main/java/baritone/builder/CuboidBounds.java
+++ b/src/main/java/baritone/builder/CuboidBounds.java
@@ -61,10 +61,11 @@ public class CuboidBounds {
}
public boolean inRange(int x, int y, int z) {
- throw new UnsupportedOperationException("ugh benchmark this tomorrow when im less tired");
+ return inRangeBranchless(x, y, z);
}
- public boolean inRangeBranchy(int x, int y, int z) {
+ @Deprecated
+ public boolean inRangeBranchy(int x, int y, int z) { // benchmarked: approx 4x slower than branchless
return (x >= 0) && (x < sizeX) && (y >= 0) && (y < sizeY) && (z >= 0) && (z < sizeZ);
}
@@ -72,6 +73,18 @@ public class CuboidBounds {
return (x | y | z | (sizeXMinusOne - x) | (sizeYMinusOne - y) | (sizeZMinusOne - z)) >= 0;
}
+ public boolean inRangeBranchless2(int x, int y, int z) {
+ return (x | y | z | ((sizeX - 1) - x) | ((sizeY - 1) - y) | ((sizeZ - 1) - z)) >= 0;
+ }
+
+ public boolean inRangeBranchless3(int x, int y, int z) {
+ return (x | y | z | (sizeX - (x + 1)) | (sizeY - (y + 1)) | (sizeZ - (z + 1))) >= 0;
+ }
+
+ public boolean inRangeBranchless4(int x, int y, int z) {
+ return (x | y | z | ((sizeX - x) - 1) | ((sizeY - y) - 1) | ((sizeZ - z) - 1)) >= 0;
+ }
+
public boolean inRangeIndex(int index) {
return (index | (sizeMinusOne - index)) >= 0;
}
diff --git a/src/main/java/baritone/builder/DependencyGraphAnalyzer.java b/src/main/java/baritone/builder/DependencyGraphAnalyzer.java
index b0136ad2b..d4345b559 100644
--- a/src/main/java/baritone/builder/DependencyGraphAnalyzer.java
+++ b/src/main/java/baritone/builder/DependencyGraphAnalyzer.java
@@ -56,6 +56,7 @@ public class DependencyGraphAnalyzer {
if (!locs.isEmpty()) {
throw new IllegalStateException("Unplaceable from any side: " + cuteTrim(locs));
}
+ // TODO instead of cuteTrim have a like SpecificBlockPositionsImpossibleException that this throws, and then later, an enclosing function can give the option to reset those locations to air
}
/**
diff --git a/src/main/java/baritone/builder/IBlockStateDataProvider.java b/src/main/java/baritone/builder/IBlockStateDataProvider.java
index 5db18f612..95130b0e1 100644
--- a/src/main/java/baritone/builder/IBlockStateDataProvider.java
+++ b/src/main/java/baritone/builder/IBlockStateDataProvider.java
@@ -17,8 +17,6 @@
package baritone.builder;
-import java.util.Arrays;
-
public interface IBlockStateDataProvider {
int numStates();
@@ -27,7 +25,20 @@ public interface IBlockStateDataProvider {
default BlockStateCachedData[] allNullable() {
BlockStateCachedData[] ret = new BlockStateCachedData[numStates()];
- Arrays.setAll(ret, this::getNullable);
+ RuntimeException ex = null;
+ for (int i = 0; i < ret.length; i++) {
+ try {
+ ret[i] = getNullable(i);
+ } catch (RuntimeException e) {
+ if (ex != null) {
+ ex.printStackTrace(); // printstacktrace all but the one that we throw
+ }
+ ex = e;
+ }
+ }
+ if (ex != null) {
+ throw ex; // throw the last one
+ }
return ret;
}
}
diff --git a/src/main/java/baritone/builder/Main.java b/src/main/java/baritone/builder/Main.java
index dd3580c73..b06e531f7 100644
--- a/src/main/java/baritone/builder/Main.java
+++ b/src/main/java/baritone/builder/Main.java
@@ -266,6 +266,133 @@ public class Main {
{
DebugStates.debug();
}
+ {
+ Random rand = new Random(5021);
+ int trials = 10_000_000;
+ int[] X = new int[trials];
+ int[] Y = new int[trials];
+ int[] Z = new int[trials];
+ int sz = 10;
+ CuboidBounds bounds = new CuboidBounds(sz, sz, sz);
+ for (int i = 0; i < trials; i++) {
+ for (int[] toAdd : new int[][]{X, Y, Z}) {
+ toAdd[i] = rand.nextBoolean() ? rand.nextInt(sz) : rand.nextBoolean() ? -1 : sz;
+ }
+ }
+ boolean[] a = new boolean[trials];
+ boolean[] b = new boolean[trials];
+ boolean[] c = new boolean[trials];
+ boolean[] d = new boolean[trials];
+ boolean[] e = new boolean[trials];
+ for (int it = 0; it < 20; it++) {
+
+ {
+ Thread.sleep(1000);
+ System.gc();
+ Thread.sleep(1000);
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < trials; i++) {
+ a[i] = bounds.inRangeBranchy(X[i], Y[i], Z[i]);
+ }
+ long end = System.currentTimeMillis();
+ System.out.println("Branchy took " + (end - start) + "ms");
+ }
+ {
+ Thread.sleep(1000);
+ System.gc();
+ Thread.sleep(1000);
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < trials; i++) {
+ b[i] = bounds.inRangeBranchless(X[i], Y[i], Z[i]);
+ }
+ long end = System.currentTimeMillis();
+ System.out.println("Branchless took " + (end - start) + "ms");
+ }
+ {
+ Thread.sleep(1000);
+ System.gc();
+ Thread.sleep(1000);
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < trials; i++) {
+ c[i] = bounds.inRangeBranchless2(X[i], Y[i], Z[i]);
+ }
+ long end = System.currentTimeMillis();
+ System.out.println("Branchless2 took " + (end - start) + "ms");
+ }
+ {
+ Thread.sleep(1000);
+ System.gc();
+ Thread.sleep(1000);
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < trials; i++) {
+ d[i] = bounds.inRangeBranchless3(X[i], Y[i], Z[i]);
+ }
+ long end = System.currentTimeMillis();
+ System.out.println("Branchless3 took " + (end - start) + "ms");
+ }
+ {
+ Thread.sleep(1000);
+ System.gc();
+ Thread.sleep(1000);
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < trials; i++) {
+ e[i] = bounds.inRangeBranchless4(X[i], Y[i], Z[i]);
+ }
+ long end = System.currentTimeMillis();
+ System.out.println("Branchless4 took " + (end - start) + "ms");
+ }
+ /*
+Branchless2 took 55ms
+Branchless3 took 53ms
+Branchless4 took 47ms
+Branchy took 137ms
+Branchless took 35ms
+Branchless2 took 36ms
+Branchless3 took 35ms
+Branchless4 took 41ms
+Branchy took 118ms
+Branchless took 33ms
+Branchless2 took 39ms
+Branchless3 took 36ms
+Branchless4 took 42ms
+Branchy took 125ms
+Branchless took 41ms
+Branchless2 took 45ms
+Branchless3 took 41ms
+Branchless4 took 45ms
+Branchy took 123ms
+Branchless took 38ms
+Branchless2 took 43ms
+Branchless3 took 35ms
+Branchless4 took 43ms
+Branchy took 117ms
+Branchless took 37ms
+Branchless2 took 42ms
+Branchless3 took 41ms
+Branchless4 took 45ms
+Branchy took 123ms
+Branchless took 35ms
+Branchless2 took 42ms
+Branchless3 took 38ms
+Branchless4 took 46ms
+Branchy took 126ms
+Branchless took 34ms
+Branchless2 took 47ms
+Branchless3 took 40ms
+Branchless4 took 47ms
+Branchy took 124ms
+ */
+
+ // 3 is better than 2 and 4 because of data dependency
+ // the L1 cache fetch for this.sizeX can happen at the same time as "x+1" (which is an increment of an argument)
+ // in other words: in options 2 and 4, the "+1" or "-1" has a data dependency on the RAM fetch for this.sizeX, but in option 3 alone, the +1 happens upon the argument x, which is likely in a register, meaning it can be pipelined in parallel with the L1 cache fetch for this.sizeX
+
+ }
+ }
+ /*{ // proguard test
+ PlayerPhysics.determinePlayerRealSupport(BlockStateCachedData.get(69), BlockStateCachedData.get(420));
+ PlayerPhysics.determinePlayerRealSupport(BlockStateCachedData.get(420), BlockStateCachedData.get(69));
+ }*/
System.exit(0);
}
}
diff --git a/src/main/java/baritone/builder/PlaceAgainstData.java b/src/main/java/baritone/builder/PlaceAgainstData.java
index 9baf77c93..bffcd6bed 100644
--- a/src/main/java/baritone/builder/PlaceAgainstData.java
+++ b/src/main/java/baritone/builder/PlaceAgainstData.java
@@ -52,6 +52,12 @@ public class PlaceAgainstData {
this.top = top;
this.bottom = bottom;
this.hits = hits;
+ if (!streamRelativeToMyself().allMatch(Vec3d::inOriginUnitVoxel)) {
+ throw new IllegalStateException();
+ }
+ if (!streamRelativeToPlace().allMatch(Vec3d::inOriginUnitVoxel)) {
+ throw new IllegalStateException();
+ }
}
public PlaceAgainstData(Face against, Half half, boolean mustSneak) {
diff --git a/src/main/java/baritone/builder/PlayerPhysics.java b/src/main/java/baritone/builder/PlayerPhysics.java
index f1df290cf..0ddc0952e 100644
--- a/src/main/java/baritone/builder/PlayerPhysics.java
+++ b/src/main/java/baritone/builder/PlayerPhysics.java
@@ -26,21 +26,21 @@ public class PlayerPhysics {
*/
public static int determinePlayerRealSupport(BlockStateCachedData underneath, BlockStateCachedData within) {
if (within.collidesWithPlayer) {
- if (underneath.collisionHeightBlips != null && underneath.collisionHeightBlips - Blip.FULL_BLOCK > within.collisionHeightBlips) { // TODO > or >=
+ if (underneath.collidesWithPlayer && underneath.collisionHeightBlips() - Blip.FULL_BLOCK > within.collisionHeightBlips()) { // > because imagine something like slab on top of fence, we can walk on the slab even though the fence is equivalent height
if (!underneath.fullyWalkableTop) {
return -1;
}
- return underneath.collisionHeightBlips - Blip.FULL_BLOCK; // this could happen if "underneath" is a fence and "within" is a carpet
+ return underneath.collisionHeightBlips() - Blip.FULL_BLOCK; // this could happen if "underneath" is a fence and "within" is a carpet
}
- if (!within.fullyWalkableTop || within.collisionHeightBlips >= Blip.FULL_BLOCK) {
+ if (!within.fullyWalkableTop || within.collisionHeightBlips() >= Blip.FULL_BLOCK) {
return -1;
}
- return within.collisionHeightBlips;
+ return within.collisionHeightBlips();
} else {
- if (!underneath.fullyWalkableTop || underneath.collisionHeightBlips < Blip.FULL_BLOCK) {
+ if (!underneath.fullyWalkableTop || underneath.collisionHeightBlips() < Blip.FULL_BLOCK) { // short circuit only calls collisionHeightBlips when fullyWalkableTop is true, so this is safe
return -1;
}
- return underneath.collisionHeightBlips - Blip.FULL_BLOCK;
+ return underneath.collisionHeightBlips() - Blip.FULL_BLOCK;
}
}
@@ -119,13 +119,10 @@ public class PlayerPhysics {
if (!D.collidesWithPlayer) {
return Collision.FALL;
}
- if (Main.DEBUG && D.collisionHeightBlips == null) {
+ if (Main.DEBUG && D.collisionHeightBlips() >= Blip.FULL_BLOCK && D.fullyWalkableTop) {
throw new IllegalStateException();
}
- if (Main.DEBUG && D.collisionHeightBlips >= Blip.FULL_BLOCK && D.fullyWalkableTop) {
- throw new IllegalStateException();
- }
- if (D.collisionHeightBlips < Blip.FULL_BLOCK + feet) {
+ if (D.collisionHeightBlips() < Blip.FULL_BLOCK + feet) {
return Collision.FALL;
} else {
return Collision.BLOCKED;
@@ -143,5 +140,6 @@ public class PlayerPhysics {
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
}
}
diff --git a/src/main/java/baritone/builder/Scaffolder.java b/src/main/java/baritone/builder/Scaffolder.java
index 17535dbc1..ed3f046db 100644
--- a/src/main/java/baritone/builder/Scaffolder.java
+++ b/src/main/java/baritone/builder/Scaffolder.java
@@ -52,9 +52,13 @@ public class Scaffolder {
this.components = collapsedGraph.getComponents();
this.componentLocations = collapsedGraph.getComponentLocations();
+ this.rootComponents = calcRoots();
+ }
+
+ private List