aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--patches/server/0991-Moonrise-optimisation-patches.patch5620
-rw-r--r--patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch8
-rw-r--r--patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch6
-rw-r--r--patches/server/1000-Entity-Activation-Range-2.0.patch6
-rw-r--r--patches/server/1002-Anti-Xray.patch36
-rw-r--r--patches/server/1004-Add-Alternate-Current-redstone-implementation.patch4
-rw-r--r--patches/server/1011-Optimize-Voxel-Shape-Merging.patch6
-rw-r--r--patches/server/1024-Optimise-random-block-ticking.patch456
-rw-r--r--patches/server/1024-Registry-Modification-API.patch (renamed from patches/server/1025-Registry-Modification-API.patch)0
-rw-r--r--patches/server/1025-Add-registry-entry-and-builders.patch (renamed from patches/server/1026-Add-registry-entry-and-builders.patch)0
-rw-r--r--patches/server/1026-Improved-Watchdog-Support.patch (renamed from patches/server/1027-Improved-Watchdog-Support.patch)12
-rw-r--r--patches/server/1027-Proxy-ItemStack-to-CraftItemStack.patch (renamed from patches/server/1028-Proxy-ItemStack-to-CraftItemStack.patch)0
-rw-r--r--patches/server/1028-Make-a-PDC-view-accessible-directly-from-ItemStack.patch (renamed from patches/server/1029-Make-a-PDC-view-accessible-directly-from-ItemStack.patch)0
-rw-r--r--patches/server/1029-Prioritize-Minecraft-commands-in-function-parsing-an.patch (renamed from patches/server/1030-Prioritize-Minecraft-commands-in-function-parsing-an.patch)0
-rw-r--r--patches/server/1030-optimize-dirt-and-snow-spreading.patch (renamed from patches/server/1031-optimize-dirt-and-snow-spreading.patch)0
-rw-r--r--patches/server/1031-Fix-NPE-for-Jukebox-setRecord.patch (renamed from patches/server/1032-Fix-NPE-for-Jukebox-setRecord.patch)0
-rw-r--r--patches/server/1032-Fix-CraftWorld-isChunkGenerated.patch (renamed from patches/server/1033-Fix-CraftWorld-isChunkGenerated.patch)0
-rw-r--r--patches/server/1033-Add-debug-for-chunk-system-unload-crash.patch (renamed from patches/server/1034-Add-debug-for-chunk-system-unload-crash.patch)0
-rw-r--r--patches/server/1034-fix-horse-inventories.patch (renamed from patches/server/1035-fix-horse-inventories.patch)0
-rw-r--r--patches/server/1035-Only-call-EntityDamageEvents-before-actuallyHurt.patch (renamed from patches/server/1036-Only-call-EntityDamageEvents-before-actuallyHurt.patch)0
-rw-r--r--patches/server/1036-Fix-entity-tracker-desync-when-new-players-are-added.patch (renamed from patches/server/1037-Fix-entity-tracker-desync-when-new-players-are-added.patch)12
21 files changed, 5618 insertions, 548 deletions
diff --git a/patches/server/0991-Moonrise-optimisation-patches.patch b/patches/server/0991-Moonrise-optimisation-patches.patch
index de0997769f..a98f328d98 100644
--- a/patches/server/0991-Moonrise-optimisation-patches.patch
+++ b/patches/server/0991-Moonrise-optimisation-patches.patch
@@ -6,6 +6,7 @@ Subject: [PATCH] Moonrise optimisation patches
Currently includes:
- Starlight + Chunk System
- Entity tracker optimisations
+ - Collision optimisations
See https://github.com/Tuinity/Moonrise
@@ -3362,6 +3363,21 @@ index 0000000000000000000000000000000000000000..e95cc73ddf20050aa4a241b0a309240e
+ throw new RuntimeException();
+ }
+}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..08338917dc61c856eaba0b76e05c1497c458399d
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java
+@@ -0,0 +1,9 @@
++package ca.spottedleaf.moonrise.patches.chunk_getblock;
++
++import net.minecraft.world.level.block.state.BlockState;
++
++public interface GetBlockChunk {
++
++ public BlockState moonrise$getBlock(final int x, final int y, final int z);
++
++}
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..c2ff037e180393de6576f12c32c665ef640d6f50
@@ -18266,6 +18282,2336 @@ index 0000000000000000000000000000000000000000..4b9e2fa963c14f65f15407c1814c543c
+ public LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ);
+
+}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..3cbd02085cc3e2ddb15458faea4b553868cff39a
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java
+@@ -0,0 +1,1853 @@
++package ca.spottedleaf.moonrise.patches.collisions;
++
++public final class CollisionUtil {
++
++ public static final double COLLISION_EPSILON = 1.0E-7;
++ public static final it.unimi.dsi.fastutil.doubles.DoubleArrayList ZERO_ONE = it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(new double[] { 0.0, 1.0 });
++
++ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) {
++ return block.hasLargeCollisionShape() || block.getBlock() == net.minecraft.world.level.block.Blocks.MOVING_PISTON;
++ }
++
++ public static boolean isEmpty(final net.minecraft.world.phys.AABB aabb) {
++ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON || (aabb.maxY - aabb.minY) < COLLISION_EPSILON || (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON;
++ }
++
++ public static boolean isEmpty(final double minX, final double minY, final double minZ,
++ final double maxX, final double maxY, final double maxZ) {
++ return (maxX - minX) < COLLISION_EPSILON || (maxY - minY) < COLLISION_EPSILON || (maxZ - minZ) < COLLISION_EPSILON;
++ }
++
++ public static net.minecraft.world.phys.AABB getBoxForChunk(final int chunkX, final int chunkZ) {
++ double x = (double)(chunkX << 4);
++ double z = (double)(chunkZ << 4);
++ // use a bounding box bigger than the chunk to prevent entities from entering it on move
++ return new net.minecraft.world.phys.AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON,
++ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON));
++ }
++
++ /*
++ A couple of rules for VoxelShape collisions:
++ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement
++ checks.
++ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite
++ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code
++ will automatically round it to 0.
++ */
++
++ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1,
++ final double maxY1, final double maxZ1, final double minX2, final double minY2,
++ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) {
++ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON &&
++ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON &&
++ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON;
++ }
++
++ public static boolean voxelShapeIntersect(final net.minecraft.world.phys.AABB box, final double minX, final double minY, final double minZ,
++ final double maxX, final double maxY, final double maxZ) {
++ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON &&
++ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON &&
++ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON;
++ }
++
++ public static boolean voxelShapeIntersect(final net.minecraft.world.phys.AABB box1, final net.minecraft.world.phys.AABB box2) {
++ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON &&
++ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON &&
++ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON;
++ }
++
++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON
++ public static double collideX(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) {
++ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON &&
++ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) {
++ if (source_move >= 0.0) {
++ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision
++ if (max_move < -COLLISION_EPSILON) {
++ return source_move;
++ }
++ return Math.min(max_move, source_move);
++ } else {
++ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision
++ if (max_move > COLLISION_EPSILON) {
++ return source_move;
++ }
++ return Math.max(max_move, source_move);
++ }
++ }
++ return source_move;
++ }
++
++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON
++ public static double collideY(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) {
++ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON &&
++ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) {
++ if (source_move >= 0.0) {
++ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision
++ if (max_move < -COLLISION_EPSILON) {
++ return source_move;
++ }
++ return Math.min(max_move, source_move);
++ } else {
++ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision
++ if (max_move > COLLISION_EPSILON) {
++ return source_move;
++ }
++ return Math.max(max_move, source_move);
++ }
++ }
++ return source_move;
++ }
++
++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON
++ public static double collideZ(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) {
++ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON &&
++ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) {
++ if (source_move >= 0.0) {
++ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision
++ if (max_move < -COLLISION_EPSILON) {
++ return source_move;
++ }
++ return Math.min(max_move, source_move);
++ } else {
++ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision
++ if (max_move > COLLISION_EPSILON) {
++ return source_move;
++ }
++ return Math.max(max_move, source_move);
++ }
++ }
++ return source_move;
++ }
++
++ // startIndex and endIndex inclusive
++ // assumes indices are in range of array
++ private static int findFloor(final double[] values, final double value, int startIndex, int endIndex) {
++ do {
++ final int middle = (startIndex + endIndex) >>> 1;
++ final double middleVal = values[middle];
++
++ if (value < middleVal) {
++ endIndex = middle - 1;
++ } else {
++ startIndex = middle + 1;
++ }
++ } while (startIndex <= endIndex);
++
++ return startIndex - 1;
++ }
++
++ public static boolean voxelShapeIntersectNoEmpty(final net.minecraft.world.phys.shapes.VoxelShape voxel, final net.minecraft.world.phys.AABB aabb) {
++ if (voxel.isEmpty()) {
++ return false;
++ }
++
++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true
++
++ // offsets that should be applied to coords
++ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetX();
++ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetY();
++ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetZ();
++
++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesX();
++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesY();
++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ();
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getCachedVoxelData();
++
++ // note: size = coords.length - 1
++ final int size_x = cached_shape_data.sizeX();
++ final int size_y = cached_shape_data.sizeY();
++ final int size_z = cached_shape_data.sizeZ();
++
++ // note: voxel bitset with set index (x, y, z) indicates that
++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1])
++ // is collidable. this is the fundamental principle of operation for the voxel collision operation
++
++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is
++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that)
++ // note: for intersection, one we find the floor of the min we can use that as the start index
++ // for the next check as source max >= source min
++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size,
++ // as this implies that coords[coords.length - 1] < source min
++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max
++
++ final int floor_min_x = Math.max(
++ 0,
++ findFloor(coords_x, (aabb.minX - off_x) + COLLISION_EPSILON, 0, size_x)
++ );
++ if (floor_min_x >= size_x) {
++ // cannot intersect
++ return false;
++ }
++
++ final int ceil_max_x = Math.min(
++ size_x,
++ findFloor(coords_x, (aabb.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1
++ );
++ if (floor_min_x >= ceil_max_x) {
++ // cannot intersect
++ return false;
++ }
++
++ final int floor_min_y = Math.max(
++ 0,
++ findFloor(coords_y, (aabb.minY - off_y) + COLLISION_EPSILON, 0, size_y)
++ );
++ if (floor_min_y >= size_y) {
++ // cannot intersect
++ return false;
++ }
++
++ final int ceil_max_y = Math.min(
++ size_y,
++ findFloor(coords_y, (aabb.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1
++ );
++ if (floor_min_y >= ceil_max_y) {
++ // cannot intersect
++ return false;
++ }
++
++ final int floor_min_z = Math.max(
++ 0,
++ findFloor(coords_z, (aabb.minZ - off_z) + COLLISION_EPSILON, 0, size_z)
++ );
++ if (floor_min_z >= size_z) {
++ // cannot intersect
++ return false;
++ }
++
++ final int ceil_max_z = Math.min(
++ size_z,
++ findFloor(coords_z, (aabb.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1
++ );
++ if (floor_min_z >= ceil_max_z) {
++ // cannot intersect
++ return false;
++ }
++
++ final long[] bitset = cached_shape_data.voxelSet();
++
++ // check bitset to check if any shapes in range are full
++
++ final int mul_x = size_y*size_z;
++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) {
++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) {
++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) {
++ final int index = curr_z + curr_y*size_z + curr_x*mul_x;
++ // note: JLS states long shift operators ANDS shift by 63
++ if ((bitset[index >>> 6] & (1L << index)) != 0L) {
++ return true;
++ }
++ }
++ }
++ }
++
++ return false;
++ }
++
++ // assume !target.isEmpty() && abs(source_move) >= COLLISION_EPSILON
++ public static double collideX(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) {
++ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation();
++ if (single_aabb != null) {
++ return collideX(single_aabb, source, source_move);
++ }
++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true
++
++ // offsets that should be applied to coords
++ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX();
++ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY();
++ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ();
++
++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX();
++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY();
++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ();
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData();
++
++ // note: size = coords.length - 1
++ final int size_x = cached_shape_data.sizeX();
++ final int size_y = cached_shape_data.sizeY();
++ final int size_z = cached_shape_data.sizeZ();
++
++ // note: voxel bitset with set index (x, y, z) indicates that
++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1])
++ // is collidable. this is the fundamental principle of operation for the voxel collision operation
++
++
++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is
++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that)
++ // note: for intersection, one we find the floor of the min we can use that as the start index
++ // for the next check as source max >= source min
++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size,
++ // as this implies that coords[coords.length - 1] < source min
++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max
++
++ final int floor_min_y = Math.max(
++ 0,
++ findFloor(coords_y, (source.minY - off_y) + COLLISION_EPSILON, 0, size_y)
++ );
++ if (floor_min_y >= size_y) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int ceil_max_y = Math.min(
++ size_y,
++ findFloor(coords_y, (source.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1
++ );
++ if (floor_min_y >= ceil_max_y) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int floor_min_z = Math.max(
++ 0,
++ findFloor(coords_z, (source.minZ - off_z) + COLLISION_EPSILON, 0, size_z)
++ );
++ if (floor_min_z >= size_z) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int ceil_max_z = Math.min(
++ size_z,
++ findFloor(coords_z, (source.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1
++ );
++ if (floor_min_z >= ceil_max_z) {
++ // cannot intersect
++ return source_move;
++ }
++
++ // index = z + y*size_z + x*(size_z*size_y)
++
++ final long[] bitset = cached_shape_data.voxelSet();
++
++ if (source_move > 0.0) {
++ final double source_max = source.maxX - off_x;
++ final int ceil_max_x = findFloor(
++ coords_x, source_max - COLLISION_EPSILON, 0, size_x
++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max
++
++ // note: only the order of the first loop matters
++
++ // note: we cannot collide with the face at index size on the collision axis for forward movement
++
++ final int mul_x = size_y*size_z;
++ for (int curr_x = ceil_max_x; curr_x < size_x; ++curr_x) {
++ double max_dist = coords_x[curr_x] - source_max;
++ if (max_dist >= source_move) {
++ // if we reach here, then we will never have a case where
++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1]
++ // thus, we can return immediately
++
++ // this optimization is important since this loop is bounded by size, and _not_ by
++ // a calculated max index based off of source_move - so it would be possible to check
++ // the whole intersected shape for collisions when we didn't need to!
++ return source_move;
++ }
++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON
++ max_dist = Math.min(max_dist, source_move);
++ }
++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) {
++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) {
++ final int index = curr_z + curr_y*size_z + curr_x*mul_x;
++ // note: JLS states long shift operators ANDS shift by 63
++ if ((bitset[index >>> 6] & (1L << index)) != 0L) {
++ return max_dist;
++ }
++ }
++ }
++ }
++
++ return source_move;
++ } else {
++ final double source_min = source.minX - off_x;
++ final int floor_min_x = findFloor(
++ coords_x, source_min + COLLISION_EPSILON, 0, size_x
++ );
++
++ // note: only the order of the first loop matters
++
++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement
++
++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the
++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1]
++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid
++ final int mul_x = size_y*size_z;
++ for (int curr_x = floor_min_x - 1; curr_x >= 0; --curr_x) {
++ double max_dist = coords_x[curr_x + 1] - source_min;
++ if (max_dist <= source_move) {
++ // if we reach here, then we will never have a case where
++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1]
++ // thus, we can return immediately
++
++ // this optimization is important since this loop is possibly bounded by size, and _not_ by
++ // a calculated max index based off of source_move - so it would be possible to check
++ // the whole intersected shape for collisions when we didn't need to!
++ return source_move;
++ }
++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON
++ max_dist = Math.max(max_dist, source_move);
++ }
++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) {
++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) {
++ final int index = curr_z + curr_y*size_z + curr_x*mul_x;
++ // note: JLS states long shift operators ANDS shift by 63
++ if ((bitset[index >>> 6] & (1L << index)) != 0L) {
++ return max_dist;
++ }
++ }
++ }
++ }
++
++ return source_move;
++ }
++ }
++
++ public static double collideY(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) {
++ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation();
++ if (single_aabb != null) {
++ return collideY(single_aabb, source, source_move);
++ }
++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true
++
++ // offsets that should be applied to coords
++ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX();
++ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY();
++ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ();
++
++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX();
++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY();
++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ();
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData();
++
++ // note: size = coords.length - 1
++ final int size_x = cached_shape_data.sizeX();
++ final int size_y = cached_shape_data.sizeY();
++ final int size_z = cached_shape_data.sizeZ();
++
++ // note: voxel bitset with set index (x, y, z) indicates that
++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1])
++ // is collidable. this is the fundamental principle of operation for the voxel collision operation
++
++
++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is
++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that)
++ // note: for intersection, one we find the floor of the min we can use that as the start index
++ // for the next check as source max >= source min
++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size,
++ // as this implies that coords[coords.length - 1] < source min
++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max
++
++ final int floor_min_x = Math.max(
++ 0,
++ findFloor(coords_x, (source.minX - off_x) + COLLISION_EPSILON, 0, size_x)
++ );
++ if (floor_min_x >= size_x) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int ceil_max_x = Math.min(
++ size_x,
++ findFloor(coords_x, (source.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1
++ );
++ if (floor_min_x >= ceil_max_x) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int floor_min_z = Math.max(
++ 0,
++ findFloor(coords_z, (source.minZ - off_z) + COLLISION_EPSILON, 0, size_z)
++ );
++ if (floor_min_z >= size_z) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int ceil_max_z = Math.min(
++ size_z,
++ findFloor(coords_z, (source.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1
++ );
++ if (floor_min_z >= ceil_max_z) {
++ // cannot intersect
++ return source_move;
++ }
++
++ // index = z + y*size_z + x*(size_z*size_y)
++
++ final long[] bitset = cached_shape_data.voxelSet();
++
++ if (source_move > 0.0) {
++ final double source_max = source.maxY - off_y;
++ final int ceil_max_y = findFloor(
++ coords_y, source_max - COLLISION_EPSILON, 0, size_y
++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max
++
++ // note: only the order of the first loop matters
++
++ // note: we cannot collide with the face at index size on the collision axis for forward movement
++
++ final int mul_x = size_y*size_z;
++ for (int curr_y = ceil_max_y; curr_y < size_y; ++curr_y) {
++ double max_dist = coords_y[curr_y] - source_max;
++ if (max_dist >= source_move) {
++ // if we reach here, then we will never have a case where
++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1]
++ // thus, we can return immediately
++
++ // this optimization is important since this loop is bounded by size, and _not_ by
++ // a calculated max index based off of source_move - so it would be possible to check
++ // the whole intersected shape for collisions when we didn't need to!
++ return source_move;
++ }
++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON
++ max_dist = Math.min(max_dist, source_move);
++ }
++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) {
++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) {
++ final int index = curr_z + curr_y*size_z + curr_x*mul_x;
++ // note: JLS states long shift operators ANDS shift by 63
++ if ((bitset[index >>> 6] & (1L << index)) != 0L) {
++ return max_dist;
++ }
++ }
++ }
++ }
++
++ return source_move;
++ } else {
++ final double source_min = source.minY - off_y;
++ final int floor_min_y = findFloor(
++ coords_y, source_min + COLLISION_EPSILON, 0, size_y
++ );
++
++ // note: only the order of the first loop matters
++
++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement
++
++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the
++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1]
++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid
++ final int mul_x = size_y*size_z;
++ for (int curr_y = floor_min_y - 1; curr_y >= 0; --curr_y) {
++ double max_dist = coords_y[curr_y + 1] - source_min;
++ if (max_dist <= source_move) {
++ // if we reach here, then we will never have a case where
++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1]
++ // thus, we can return immediately
++
++ // this optimization is important since this loop is possibly bounded by size, and _not_ by
++ // a calculated max index based off of source_move - so it would be possible to check
++ // the whole intersected shape for collisions when we didn't need to!
++ return source_move;
++ }
++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON
++ max_dist = Math.max(max_dist, source_move);
++ }
++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) {
++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) {
++ final int index = curr_z + curr_y*size_z + curr_x*mul_x;
++ // note: JLS states long shift operators ANDS shift by 63
++ if ((bitset[index >>> 6] & (1L << index)) != 0L) {
++ return max_dist;
++ }
++ }
++ }
++ }
++
++ return source_move;
++ }
++ }
++
++ public static double collideZ(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) {
++ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation();
++ if (single_aabb != null) {
++ return collideZ(single_aabb, source, source_move);
++ }
++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true
++
++ // offsets that should be applied to coords
++ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX();
++ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY();
++ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ();
++
++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX();
++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY();
++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ();
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData();
++
++ // note: size = coords.length - 1
++ final int size_x = cached_shape_data.sizeX();
++ final int size_y = cached_shape_data.sizeY();
++ final int size_z = cached_shape_data.sizeZ();
++
++ // note: voxel bitset with set index (x, y, z) indicates that
++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1])
++ // is collidable. this is the fundamental principle of operation for the voxel collision operation
++
++
++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is
++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that)
++ // note: for intersection, one we find the floor of the min we can use that as the start index
++ // for the next check as source max >= source min
++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size,
++ // as this implies that coords[coords.length - 1] < source min
++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max
++
++ final int floor_min_x = Math.max(
++ 0,
++ findFloor(coords_x, (source.minX - off_x) + COLLISION_EPSILON, 0, size_x)
++ );
++ if (floor_min_x >= size_x) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int ceil_max_x = Math.min(
++ size_x,
++ findFloor(coords_x, (source.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1
++ );
++ if (floor_min_x >= ceil_max_x) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int floor_min_y = Math.max(
++ 0,
++ findFloor(coords_y, (source.minY - off_y) + COLLISION_EPSILON, 0, size_y)
++ );
++ if (floor_min_y >= size_y) {
++ // cannot intersect
++ return source_move;
++ }
++
++ final int ceil_max_y = Math.min(
++ size_y,
++ findFloor(coords_y, (source.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1
++ );
++ if (floor_min_y >= ceil_max_y) {
++ // cannot intersect
++ return source_move;
++ }
++
++ // index = z + y*size_z + x*(size_z*size_y)
++
++ final long[] bitset = cached_shape_data.voxelSet();
++
++ if (source_move > 0.0) {
++ final double source_max = source.maxZ - off_z;
++ final int ceil_max_z = findFloor(
++ coords_z, source_max - COLLISION_EPSILON, 0, size_z
++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max
++
++ // note: only the order of the first loop matters
++
++ // note: we cannot collide with the face at index size on the collision axis for forward movement
++
++ final int mul_x = size_y*size_z;
++ for (int curr_z = ceil_max_z; curr_z < size_z; ++curr_z) {
++ double max_dist = coords_z[curr_z] - source_max;
++ if (max_dist >= source_move) {
++ // if we reach here, then we will never have a case where
++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1]
++ // thus, we can return immediately
++
++ // this optimization is important since this loop is bounded by size, and _not_ by
++ // a calculated max index based off of source_move - so it would be possible to check
++ // the whole intersected shape for collisions when we didn't need to!
++ return source_move;
++ }
++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON
++ max_dist = Math.min(max_dist, source_move);
++ }
++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) {
++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) {
++ final int index = curr_z + curr_y*size_z + curr_x*mul_x;
++ // note: JLS states long shift operators ANDS shift by 63
++ if ((bitset[index >>> 6] & (1L << index)) != 0L) {
++ return max_dist;
++ }
++ }
++ }
++ }
++
++ return source_move;
++ } else {
++ final double source_min = source.minZ - off_z;
++ final int floor_min_z = findFloor(
++ coords_z, source_min + COLLISION_EPSILON, 0, size_z
++ );
++
++ // note: only the order of the first loop matters
++
++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement
++
++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the
++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1]
++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid
++ final int mul_x = size_y*size_z;
++ for (int curr_z = floor_min_z - 1; curr_z >= 0; --curr_z) {
++ double max_dist = coords_z[curr_z + 1] - source_min;
++ if (max_dist <= source_move) {
++ // if we reach here, then we will never have a case where
++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1]
++ // thus, we can return immediately
++
++ // this optimization is important since this loop is possibly bounded by size, and _not_ by
++ // a calculated max index based off of source_move - so it would be possible to check
++ // the whole intersected shape for collisions when we didn't need to!
++ return source_move;
++ }
++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON
++ max_dist = Math.max(max_dist, source_move);
++ }
++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) {
++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) {
++ final int index = curr_z + curr_y*size_z + curr_x*mul_x;
++ // note: JLS states long shift operators ANDS shift by 63
++ if ((bitset[index >>> 6] & (1L << index)) != 0L) {
++ return max_dist;
++ }
++ }
++ }
++ }
++
++ return source_move;
++ }
++ }
++
++ // does not use epsilon
++ public static boolean strictlyContains(final net.minecraft.world.phys.shapes.VoxelShape voxel, final net.minecraft.world.phys.Vec3 point) {
++ return strictlyContains(voxel, point.x, point.y, point.z);
++ }
++
++ // does not use epsilon
++ public static boolean strictlyContains(final net.minecraft.world.phys.shapes.VoxelShape voxel, double x, double y, double z) {
++ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation();
++ if (single_aabb != null) {
++ return single_aabb.contains(x, y, z);
++ }
++
++ if (voxel.isEmpty()) {
++ // bitset is clear, no point in searching
++ return false;
++ }
++
++ // offset input
++ x -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetX();
++ y -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetY();
++ z -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetZ();
++
++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesX();
++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesY();
++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ();
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getCachedVoxelData();
++
++ // note: size = coords.length - 1
++ final int size_x = cached_shape_data.sizeX();
++ final int size_y = cached_shape_data.sizeY();
++ final int size_z = cached_shape_data.sizeZ();
++
++ // note: should mirror AABB#contains, which is that for any point X that X >= min and X < max.
++ // specifically, it cannot collide on the max bounds of the shape
++
++ final int index_x = findFloor(coords_x, x, 0, size_x);
++ if (index_x < 0 || index_x >= size_x) {
++ return false;
++ }
++
++ final int index_y = findFloor(coords_y, y, 0, size_y);
++ if (index_y < 0 || index_y >= size_y) {
++ return false;
++ }
++
++ final int index_z = findFloor(coords_z, z, 0, size_z);
++ if (index_z < 0 || index_z >= size_z) {
++ return false;
++ }
++
++ // index = z + y*size_z + x*(size_z*size_y)
++
++ final int index = index_z + index_y*size_z + index_x*(size_z*size_y);
++
++ final long[] bitset = cached_shape_data.voxelSet();
++
++ return (bitset[index >>> 6] & (1L << index)) != 0L;
++ }
++
++ private static int makeBitset(final boolean ft, final boolean tf, final boolean tt) {
++ // idx ff -> 0
++ // idx ft -> 1
++ // idx tf -> 2
++ // idx tt -> 3
++ return ((ft ? 1 : 0) << 1) | ((tf ? 1 : 0) << 2) | ((tt ? 1 : 0) << 3);
++ }
++
++ private static net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape merge(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst, final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond,
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX, final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY,
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ,
++ final int booleanOp) {
++ final int sizeX = mergedX.voxels;
++ final int sizeY = mergedY.voxels;
++ final int sizeZ = mergedZ.voxels;
++
++ final long[] s1Voxels = shapeDataFirst.voxelSet();
++ final long[] s2Voxels = shapeDataSecond.voxelSet();
++
++ final int s1Mul1 = shapeDataFirst.sizeZ();
++ final int s1Mul2 = s1Mul1 * shapeDataFirst.sizeY();
++
++ final int s2Mul1 = shapeDataSecond.sizeZ();
++ final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY();
++
++ // note: indices may contain -1, but nothing > size
++ final net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape ret = new net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape(sizeX, sizeY, sizeZ);
++
++ boolean empty = true;
++
++ int mergedIdx = 0;
++ for (int idxX = 0; idxX < sizeX; ++idxX) {
++ final int s1x = mergedX.firstIndices[idxX];
++ final int s2x = mergedX.secondIndices[idxX];
++ boolean setX = false;
++ for (int idxY = 0; idxY < sizeY; ++idxY) {
++ final int s1y = mergedY.firstIndices[idxY];
++ final int s2y = mergedY.secondIndices[idxY];
++ boolean setY = false;
++ for (int idxZ = 0; idxZ < sizeZ; ++idxZ) {
++ final int s1z = mergedZ.firstIndices[idxZ];
++ final int s2z = mergedZ.secondIndices[idxZ];
++
++ int idx;
++
++ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L);
++ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L);
++
++ // idx ff -> 0
++ // idx ft -> 1
++ // idx tf -> 2
++ // idx tt -> 3
++
++ final boolean res = (booleanOp & (1 << (isS2Full | (isS1Full << 1)))) != 0;
++ setY |= res;
++ setX |= res;
++
++ if (res) {
++ empty = false;
++ // inline and optimize fill operation
++ ret.zMin = Math.min(ret.zMin, idxZ);
++ ret.zMax = Math.max(ret.zMax, idxZ + 1);
++ ret.storage.set(mergedIdx);
++ }
++
++ ++mergedIdx;
++ }
++ if (setY) {
++ ret.yMin = Math.min(ret.yMin, idxY);
++ ret.yMax = Math.max(ret.yMax, idxY + 1);
++ }
++ }
++ if (setX) {
++ ret.xMin = Math.min(ret.xMin, idxX);
++ ret.xMax = Math.max(ret.xMax, idxX + 1);
++ }
++ }
++
++ return empty ? null : ret;
++ }
++
++ private static boolean isMergeEmpty(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst, final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond,
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX, final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY,
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ,
++ final int booleanOp) {
++ final int sizeX = mergedX.voxels;
++ final int sizeY = mergedY.voxels;
++ final int sizeZ = mergedZ.voxels;
++
++ final long[] s1Voxels = shapeDataFirst.voxelSet();
++ final long[] s2Voxels = shapeDataSecond.voxelSet();
++
++ final int s1Mul1 = shapeDataFirst.sizeZ();
++ final int s1Mul2 = s1Mul1 * shapeDataFirst.sizeY();
++
++ final int s2Mul1 = shapeDataSecond.sizeZ();
++ final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY();
++
++ // note: indices may contain -1, but nothing > size
++ for (int idxX = 0; idxX < sizeX; ++idxX) {
++ final int s1x = mergedX.firstIndices[idxX];
++ final int s2x = mergedX.secondIndices[idxX];
++ for (int idxY = 0; idxY < sizeY; ++idxY) {
++ final int s1y = mergedY.firstIndices[idxY];
++ final int s2y = mergedY.secondIndices[idxY];
++ for (int idxZ = 0; idxZ < sizeZ; ++idxZ) {
++ final int s1z = mergedZ.firstIndices[idxZ];
++ final int s2z = mergedZ.secondIndices[idxZ];
++
++ int idx;
++
++ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L);
++ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L);
++
++ // idx ff -> 0
++ // idx ft -> 1
++ // idx tf -> 2
++ // idx tt -> 3
++
++ final boolean res = (booleanOp & (1 << (isS2Full | (isS1Full << 1)))) != 0;
++
++ if (res) {
++ return false;
++ }
++ }
++ }
++ }
++
++ return true;
++ }
++
++ public static net.minecraft.world.phys.shapes.VoxelShape joinOptimized(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) {
++ return joinUnoptimized(first, second, operator).optimize();
++ }
++
++ public static net.minecraft.world.phys.shapes.VoxelShape joinUnoptimized(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) {
++ final boolean ff = operator.apply(false, false);
++ if (ff) {
++ // technically, should be an infinite box but that's clearly an error
++ throw new UnsupportedOperationException("Ambiguous operator: (false, false) -> true");
++ }
++
++ final boolean tt = operator.apply(true, true);
++
++ if (first == second) {
++ return tt ? first : net.minecraft.world.phys.shapes.Shapes.empty();
++ }
++
++ final boolean ft = operator.apply(false, true);
++ final boolean tf = operator.apply(true, false);
++
++ if (first.isEmpty()) {
++ return ft ? second : net.minecraft.world.phys.shapes.Shapes.empty();
++ }
++ if (second.isEmpty()) {
++ return tf ? first : net.minecraft.world.phys.shapes.Shapes.empty();
++ }
++
++ if (!tt) {
++ // try to check for no intersection, since tt = false
++ final net.minecraft.world.phys.AABB aabbF = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation();
++ final net.minecraft.world.phys.AABB aabbS = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation();
++
++ final boolean intersect;
++
++ final boolean hasAABBF = aabbF != null;
++ final boolean hasAABBS = aabbS != null;
++ if (hasAABBF | hasAABBS) {
++ if (hasAABBF & hasAABBS) {
++ intersect = voxelShapeIntersect(aabbF, aabbS);
++ } else if (hasAABBF) {
++ intersect = voxelShapeIntersectNoEmpty(second, aabbF);
++ } else {
++ intersect = voxelShapeIntersectNoEmpty(first, aabbS);
++ }
++ } else {
++ // expect cached bounds
++ intersect = voxelShapeIntersect(first.bounds(), second.bounds());
++ }
++
++ if (!intersect) {
++ if (!tf & !ft) {
++ return net.minecraft.world.phys.shapes.Shapes.empty();
++ }
++ if (!tf | !ft) {
++ return tf ? first : second;
++ }
++ }
++ }
++
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge(
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetX(),
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetX(),
++ ft, tf
++ );
++ if (mergedX == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) {
++ return net.minecraft.world.phys.shapes.Shapes.empty();
++ }
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge(
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetY(),
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetY(),
++ ft, tf
++ );
++ if (mergedY == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) {
++ return net.minecraft.world.phys.shapes.Shapes.empty();
++ }
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge(
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetZ(),
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetZ(),
++ ft, tf
++ );
++ if (mergedZ == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) {
++ return net.minecraft.world.phys.shapes.Shapes.empty();
++ }
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getCachedVoxelData();
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getCachedVoxelData();
++
++ final net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape mergedShape = merge(
++ shapeDataFirst, shapeDataSecond,
++ mergedX, mergedY, mergedZ,
++ makeBitset(ft, tf, tt)
++ );
++
++ if (mergedShape == null) {
++ return net.minecraft.world.phys.shapes.Shapes.empty();
++ }
++
++ return new net.minecraft.world.phys.shapes.ArrayVoxelShape(
++ mergedShape, mergedX.wrapCoords(), mergedY.wrapCoords(), mergedZ.wrapCoords()
++ );
++ }
++
++ public static boolean isJoinNonEmpty(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) {
++ final boolean ff = operator.apply(false, false);
++ if (ff) {
++ // technically, should be an infinite box but that's clearly an error
++ throw new UnsupportedOperationException("Ambiguous operator: (false, false) -> true");
++ }
++ final boolean firstEmpty = first.isEmpty();
++ final boolean secondEmpty = second.isEmpty();
++ if (firstEmpty | secondEmpty) {
++ return operator.apply(!firstEmpty, !secondEmpty);
++ }
++
++ final boolean tt = operator.apply(true, true);
++
++ if (first == second) {
++ return tt;
++ }
++
++ final boolean ft = operator.apply(false, true);
++ final boolean tf = operator.apply(true, false);
++
++ // try to check intersection
++ final net.minecraft.world.phys.AABB aabbF = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation();
++ final net.minecraft.world.phys.AABB aabbS = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation();
++
++ final boolean intersect;
++
++ final boolean hasAABBF = aabbF != null;
++ final boolean hasAABBS = aabbS != null;
++ if (hasAABBF | hasAABBS) {
++ if (hasAABBF & hasAABBS) {
++ intersect = voxelShapeIntersect(aabbF, aabbS);
++ } else if (hasAABBF) {
++ intersect = voxelShapeIntersectNoEmpty(second, aabbF);
++ } else {
++ // hasAABBS -> true
++ intersect = voxelShapeIntersectNoEmpty(first, aabbS);
++ }
++
++ if (!intersect) {
++ // is only non-empty if we take from first or second, as there is no overlap AND both shapes are non-empty
++ return tf | ft;
++ } else if (tt) {
++ // intersect = true && tt = true -> non-empty merged shape
++ return true;
++ }
++ } else {
++ // expect cached bounds
++ intersect = voxelShapeIntersect(first.bounds(), second.bounds());
++ if (!intersect) {
++ // is only non-empty if we take from first or second, as there is no intersection
++ return tf | ft;
++ }
++ }
++
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge(
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetX(),
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetX(),
++ ft, tf
++ );
++ if (mergedX == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) {
++ return false;
++ }
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge(
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetY(),
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetY(),
++ ft, tf
++ );
++ if (mergedY == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) {
++ return false;
++ }
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge(
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetZ(),
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetZ(),
++ ft, tf
++ );
++ if (mergedZ == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) {
++ return false;
++ }
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getCachedVoxelData();
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getCachedVoxelData();
++
++ return !isMergeEmpty(
++ shapeDataFirst, shapeDataSecond,
++ mergedX, mergedY, mergedZ,
++ makeBitset(ft, tf, tt)
++ );
++ }
++
++ private static final class MergedVoxelCoordinateList {
++
++ private static final int[][] SIMPLE_INDICES_CACHE = new int[64][];
++ static {
++ for (int i = 0; i < SIMPLE_INDICES_CACHE.length; ++i) {
++ SIMPLE_INDICES_CACHE[i] = getIndices(i);
++ }
++ }
++
++ private static final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList EMPTY = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(
++ new double[] { 0.0 }, 0.0, new int[0], new int[0], 0
++ );
++
++ private static int[] getIndices(final int length) {
++ final int[] ret = new int[length];
++
++ for (int i = 1; i < length; ++i) {
++ ret[i] = i;
++ }
++
++ return ret;
++ }
++
++ // indices above voxel size are always set to -1
++ public final double[] coordinates;
++ public final double coordinateOffset;
++ public final int[] firstIndices;
++ public final int[] secondIndices;
++ public final int voxels;
++
++ private MergedVoxelCoordinateList(final double[] coordinates, final double coordinateOffset,
++ final int[] firstIndices, final int[] secondIndices, final int voxels) {
++ this.coordinates = coordinates;
++ this.coordinateOffset = coordinateOffset;
++ this.firstIndices = firstIndices;
++ this.secondIndices = secondIndices;
++ this.voxels = voxels;
++ }
++
++ public it.unimi.dsi.fastutil.doubles.DoubleList wrapCoords() {
++ if (this.coordinateOffset == 0.0) {
++ return it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(this.coordinates, this.voxels + 1);
++ }
++ return new net.minecraft.world.phys.shapes.OffsetDoubleList(it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(this.coordinates, this.voxels + 1), this.coordinateOffset);
++ }
++
++ // assume coordinates.length > 1
++ public static ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList getForSingle(final double[] coordinates, final double offset) {
++ final int voxels = coordinates.length - 1;
++ final int[] indices = voxels < SIMPLE_INDICES_CACHE.length ? SIMPLE_INDICES_CACHE[voxels] : getIndices(voxels);
++
++ return new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(coordinates, offset, indices, indices, voxels);
++ }
++
++ // assume coordinates.length > 1
++ public static ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList merge(final double[] firstCoordinates, final double firstOffset,
++ final double[] secondCoordinates, final double secondOffset,
++ final boolean ft, final boolean tf) {
++ if (firstCoordinates == secondCoordinates && firstOffset == secondOffset) {
++ return getForSingle(firstCoordinates, firstOffset);
++ }
++
++ final int firstCount = firstCoordinates.length;
++ final int secondCount = secondCoordinates.length;
++
++ final int voxelsFirst = firstCount - 1;
++ final int voxelsSecond = secondCount - 1;
++
++ final int maxCount = firstCount + secondCount;
++
++ final double[] coordinates = new double[maxCount];
++ final int[] firstIndices = new int[maxCount];
++ final int[] secondIndices = new int[maxCount];
++
++ final boolean notTF = !tf;
++ final boolean notFT = !ft;
++
++ int firstIndex = 0;
++ int secondIndex = 0;
++ int resultSize = 0;
++
++ // note: operations on NaN are false
++ double last = Double.NaN;
++
++ for (;;) {
++ final boolean noneLeftFirst = firstIndex >= firstCount;
++ final boolean noneLeftSecond = secondIndex >= secondCount;
++
++ if ((noneLeftFirst & noneLeftSecond) | (noneLeftSecond & notTF) | (noneLeftFirst & notFT)) {
++ break;
++ }
++
++ final boolean firstZero = firstIndex == 0;
++ final boolean secondZero = secondIndex == 0;
++
++ final double select;
++
++ if (noneLeftFirst) {
++ // noneLeftSecond -> false
++ // notFT -> false
++ select = secondCoordinates[secondIndex] + secondOffset;
++ ++secondIndex;
++ } else if (noneLeftSecond) {
++ // noneLeftFirst -> false
++ // notTF -> false
++ select = firstCoordinates[firstIndex] + firstOffset;
++ ++firstIndex;
++ } else {
++ // noneLeftFirst | noneLeftSecond -> false
++ // notTF -> ??
++ // notFT -> ??
++ final boolean breakFirst = notTF & secondZero;
++ final boolean breakSecond = notFT & firstZero;
++
++ final double first = firstCoordinates[firstIndex] + firstOffset;
++ final double second = secondCoordinates[secondIndex] + secondOffset;
++ final boolean useFirst = first < (second + COLLISION_EPSILON);
++ final boolean cont = (useFirst & breakFirst) | (!useFirst & breakSecond);
++
++ select = useFirst ? first : second;
++ firstIndex += useFirst ? 1 : 0;
++ secondIndex += 1 ^ (useFirst ? 1 : 0);
++
++ if (cont) {
++ continue;
++ }
++ }
++
++ int prevFirst = firstIndex - 1;
++ prevFirst = prevFirst >= voxelsFirst ? -1 : prevFirst;
++ int prevSecond = secondIndex - 1;
++ prevSecond = prevSecond >= voxelsSecond ? -1 : prevSecond;
++
++ if (last >= (select - COLLISION_EPSILON)) {
++ // note: any operations on NaN is false
++ firstIndices[resultSize - 1] = prevFirst;
++ secondIndices[resultSize - 1] = prevSecond;
++ } else {
++ firstIndices[resultSize] = prevFirst;
++ secondIndices[resultSize] = prevSecond;
++ coordinates[resultSize] = select;
++
++ ++resultSize;
++ last = select;
++ }
++ }
++
++ return resultSize <= 1 ? EMPTY : new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(coordinates, 0.0, firstIndices, secondIndices, resultSize - 1);
++ }
++ }
++
++ public static boolean equals(final net.minecraft.world.phys.shapes.DiscreteVoxelShape shape1, final net.minecraft.world.phys.shapes.DiscreteVoxelShape shape2) {
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData1 = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData();
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData2 = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData();
++
++ final boolean isEmpty1 = cachedShapeData1.isEmpty();
++ final boolean isEmpty2 = cachedShapeData2.isEmpty();
++
++ if (isEmpty1 & isEmpty2) {
++ return true;
++ } else if (isEmpty1 ^ isEmpty2) {
++ return false;
++ }
++
++ if (cachedShapeData1.hasSingleAABB() != cachedShapeData2.hasSingleAABB()) {
++ return false;
++ }
++
++ if (cachedShapeData1.sizeX() != cachedShapeData2.sizeX()) {
++ return false;
++ }
++ if (cachedShapeData1.sizeY() != cachedShapeData2.sizeY()) {
++ return false;
++ }
++ if (cachedShapeData1.sizeZ() != cachedShapeData2.sizeZ()) {
++ return false;
++ }
++
++ return java.util.Arrays.equals(cachedShapeData1.voxelSet(), cachedShapeData2.voxelSet());
++ }
++
++ // useful only for testing
++ public static boolean equals(final net.minecraft.world.phys.shapes.VoxelShape shape1, final net.minecraft.world.phys.shapes.VoxelShape shape2) {
++ if (!equals(shape1.shape, shape2.shape)) {
++ return false;
++ }
++
++ return shape1.getCoords(net.minecraft.core.Direction.Axis.X).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.X)) &&
++ shape1.getCoords(net.minecraft.core.Direction.Axis.Y).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.Y)) &&
++ shape1.getCoords(net.minecraft.core.Direction.Axis.Z).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.Z));
++ }
++
++ public static net.minecraft.world.phys.AABB offsetX(final net.minecraft.world.phys.AABB box, final double dx) {
++ return new net.minecraft.world.phys.AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB offsetY(final net.minecraft.world.phys.AABB box, final double dy) {
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB offsetZ(final net.minecraft.world.phys.AABB box, final double dz) {
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz);
++ }
++
++ public static net.minecraft.world.phys.AABB expandRight(final net.minecraft.world.phys.AABB box, final double dx) { // dx > 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB expandLeft(final net.minecraft.world.phys.AABB box, final double dx) { // dx < 0.0
++ return new net.minecraft.world.phys.AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB expandUpwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy > 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB expandDownwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy < 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB expandForwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz > 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz);
++ }
++
++ public static net.minecraft.world.phys.AABB expandBackwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz < 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB cutRight(final net.minecraft.world.phys.AABB box, final double dx) { // dx > 0.0
++ return new net.minecraft.world.phys.AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB cutLeft(final net.minecraft.world.phys.AABB box, final double dx) { // dx < 0.0
++ return new net.minecraft.world.phys.AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB cutUpwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy > 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB cutDownwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy < 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ);
++ }
++
++ public static net.minecraft.world.phys.AABB cutForwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz > 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz);
++ }
++
++ public static net.minecraft.world.phys.AABB cutBackwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz < 0.0
++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ);
++ }
++
++ public static double performAABBCollisionsX(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List<net.minecraft.world.phys.AABB> potentialCollisions) {
++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
++ if (Math.abs(value) < COLLISION_EPSILON) {
++ return 0.0;
++ }
++ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i);
++ value = collideX(target, currentBoundingBox, value);
++ }
++
++ return value;
++ }
++
++ public static double performAABBCollisionsY(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List<net.minecraft.world.phys.AABB> potentialCollisions) {
++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
++ if (Math.abs(value) < COLLISION_EPSILON) {
++ return 0.0;
++ }
++ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i);
++ value = collideY(target, currentBoundingBox, value);
++ }
++
++ return value;
++ }
++
++ public static double performAABBCollisionsZ(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List<net.minecraft.world.phys.AABB> potentialCollisions) {
++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
++ if (Math.abs(value) < COLLISION_EPSILON) {
++ return 0.0;
++ }
++ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i);
++ value = collideZ(target, currentBoundingBox, value);
++ }
++
++ return value;
++ }
++
++ public static double performVoxelCollisionsX(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List<net.minecraft.world.phys.shapes.VoxelShape> potentialCollisions) {
++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
++ if (Math.abs(value) < COLLISION_EPSILON) {
++ return 0.0;
++ }
++ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i);
++ value = collideX(target, currentBoundingBox, value);
++ }
++
++ return value;
++ }
++
++ public static double performVoxelCollisionsY(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List<net.minecraft.world.phys.shapes.VoxelShape> potentialCollisions) {
++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
++ if (Math.abs(value) < COLLISION_EPSILON) {
++ return 0.0;
++ }
++ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i);
++ value = collideY(target, currentBoundingBox, value);
++ }
++
++ return value;
++ }
++
++ public static double performVoxelCollisionsZ(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List<net.minecraft.world.phys.shapes.VoxelShape> potentialCollisions) {
++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
++ if (Math.abs(value) < COLLISION_EPSILON) {
++ return 0.0;
++ }
++ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i);
++ value = collideZ(target, currentBoundingBox, value);
++ }
++
++ return value;
++ }
++
++ public static net.minecraft.world.phys.Vec3 performVoxelCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, final java.util.List<net.minecraft.world.phys.shapes.VoxelShape> potentialCollisions) {
++ double x = moveVector.x;
++ double y = moveVector.y;
++ double z = moveVector.z;
++
++ if (y != 0.0) {
++ y = performVoxelCollisionsY(axisalignedbb, y, potentialCollisions);
++ if (y != 0.0) {
++ axisalignedbb = offsetY(axisalignedbb, y);
++ }
++ }
++
++ final boolean xSmaller = Math.abs(x) < Math.abs(z);
++
++ if (xSmaller && z != 0.0) {
++ z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions);
++ if (z != 0.0) {
++ axisalignedbb = offsetZ(axisalignedbb, z);
++ }
++ }
++
++ if (x != 0.0) {
++ x = performVoxelCollisionsX(axisalignedbb, x, potentialCollisions);
++ if (!xSmaller && x != 0.0) {
++ axisalignedbb = offsetX(axisalignedbb, x);
++ }
++ }
++
++ if (!xSmaller && z != 0.0) {
++ z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions);
++ }
++
++ return new net.minecraft.world.phys.Vec3(x, y, z);
++ }
++
++ public static net.minecraft.world.phys.Vec3 performAABBCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, final java.util.List<net.minecraft.world.phys.AABB> potentialCollisions) {
++ double x = moveVector.x;
++ double y = moveVector.y;
++ double z = moveVector.z;
++
++ if (y != 0.0) {
++ y = performAABBCollisionsY(axisalignedbb, y, potentialCollisions);
++ if (y != 0.0) {
++ axisalignedbb = offsetY(axisalignedbb, y);
++ }
++ }
++
++ final boolean xSmaller = Math.abs(x) < Math.abs(z);
++
++ if (xSmaller && z != 0.0) {
++ z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions);
++ if (z != 0.0) {
++ axisalignedbb = offsetZ(axisalignedbb, z);
++ }
++ }
++
++ if (x != 0.0) {
++ x = performAABBCollisionsX(axisalignedbb, x, potentialCollisions);
++ if (!xSmaller && x != 0.0) {
++ axisalignedbb = offsetX(axisalignedbb, x);
++ }
++ }
++
++ if (!xSmaller && z != 0.0) {
++ z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions);
++ }
++
++ return new net.minecraft.world.phys.Vec3(x, y, z);
++ }
++
++ public static net.minecraft.world.phys.Vec3 performCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb,
++ final java.util.List<net.minecraft.world.phys.shapes.VoxelShape> voxels,
++ final java.util.List<net.minecraft.world.phys.AABB> aabbs) {
++ if (voxels.isEmpty()) {
++ // fast track only AABBs
++ return performAABBCollisions(moveVector, axisalignedbb, aabbs);
++ }
++
++ double x = moveVector.x;
++ double y = moveVector.y;
++ double z = moveVector.z;
++
++ if (y != 0.0) {
++ y = performAABBCollisionsY(axisalignedbb, y, aabbs);
++ y = performVoxelCollisionsY(axisalignedbb, y, voxels);
++ if (y != 0.0) {
++ axisalignedbb = offsetY(axisalignedbb, y);
++ }
++ }
++
++ final boolean xSmaller = Math.abs(x) < Math.abs(z);
++
++ if (xSmaller && z != 0.0) {
++ z = performAABBCollisionsZ(axisalignedbb, z, aabbs);
++ z = performVoxelCollisionsZ(axisalignedbb, z, voxels);
++ if (z != 0.0) {
++ axisalignedbb = offsetZ(axisalignedbb, z);
++ }
++ }
++
++ if (x != 0.0) {
++ x = performAABBCollisionsX(axisalignedbb, x, aabbs);
++ x = performVoxelCollisionsX(axisalignedbb, x, voxels);
++ if (!xSmaller && x != 0.0) {
++ axisalignedbb = offsetX(axisalignedbb, x);
++ }
++ }
++
++ if (!xSmaller && z != 0.0) {
++ z = performAABBCollisionsZ(axisalignedbb, z, aabbs);
++ z = performVoxelCollisionsZ(axisalignedbb, z, voxels);
++ }
++
++ return new net.minecraft.world.phys.Vec3(x, y, z);
++ }
++
++ public static boolean isCollidingWithBorder(final net.minecraft.world.level.border.WorldBorder worldborder, final net.minecraft.world.phys.AABB boundingBox) {
++ return isCollidingWithBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
++ }
++
++ public static boolean isCollidingWithBorder(final net.minecraft.world.level.border.WorldBorder worldborder,
++ final double boxMinX, final double boxMaxX,
++ final double boxMinZ, final double boxMaxZ) {
++ final double borderMinX = Math.floor(worldborder.getMinX()); // -X
++ final double borderMaxX = Math.ceil(worldborder.getMaxX()); // +X
++
++ final double borderMinZ = Math.floor(worldborder.getMinZ()); // -Z
++ final double borderMaxZ = Math.ceil(worldborder.getMaxZ()); // +Z
++
++ // inverted check for world border enclosing the specified box expanded by -EPSILON
++ return (borderMinX - boxMinX) > ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || (borderMaxX - boxMaxX) < -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON ||
++ (borderMinZ - boxMinZ) > ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || (borderMaxZ - boxMaxZ) < -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON;
++ }
++
++ /* Math.max/min specify that any NaN argument results in a NaN return, unlike these functions */
++ private static double min(final double x, final double y) {
++ return x < y ? x : y;
++ }
++
++ private static double max(final double x, final double y) {
++ return x > y ? x : y;
++ }
++
++ public static final int COLLISION_FLAG_LOAD_CHUNKS = 1 << 0;
++ public static final int COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS = 1 << 1;
++ public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2;
++ public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3;
++
++ public static boolean getCollisionsForBlocksOrWorldBorder(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB aabb,
++ final java.util.List<net.minecraft.world.phys.shapes.VoxelShape> intoVoxel, final java.util.List<net.minecraft.world.phys.AABB> intoAABB,
++ final int collisionFlags, final java.util.function.BiPredicate<net.minecraft.world.level.block.state.BlockState, net.minecraft.core.BlockPos> predicate) {
++ final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0;
++ boolean ret = false;
++
++ if ((collisionFlags & COLLISION_FLAG_CHECK_BORDER) != 0) {
++ final net.minecraft.world.level.border.WorldBorder worldBorder = world.getWorldBorder();
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(worldBorder, aabb) && entity != null && worldBorder.isInsideCloseToBorder(entity, aabb)) {
++ if (checkOnly) {
++ return true;
++ } else {
++ final net.minecraft.world.phys.shapes.VoxelShape borderShape = worldBorder.getCollisionShape();
++ intoVoxel.add(borderShape);
++ ret = true;
++ }
++ }
++ }
++
++ final int minSection = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)world).moonrise$getMinSection();
++
++ final int minBlockX = net.minecraft.util.Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
++ final int maxBlockX = net.minecraft.util.Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
++
++ final int minBlockY = Math.max((minSection << 4) - 1, net.minecraft.util.Mth.floor(aabb.minY - COLLISION_EPSILON) - 1);
++ final int maxBlockY = Math.min((((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)world).moonrise$getMaxSection() << 4) + 16, net.minecraft.util.Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1);
++
++ final int minBlockZ = net.minecraft.util.Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
++ final int maxBlockZ = net.minecraft.util.Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
++
++ final net.minecraft.core.BlockPos.MutableBlockPos mutablePos = new net.minecraft.core.BlockPos.MutableBlockPos();
++ final net.minecraft.world.phys.shapes.CollisionContext collisionShape = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity);
++
++ // special cases:
++ if (minBlockY > maxBlockY) {
++ // no point in checking
++ return ret;
++ }
++
++ final int minChunkX = minBlockX >> 4;
++ final int maxChunkX = maxBlockX >> 4;
++
++ final int minChunkY = minBlockY >> 4;
++ final int maxChunkY = maxBlockY >> 4;
++
++ final int minChunkZ = minBlockZ >> 4;
++ final int maxChunkZ = maxBlockZ >> 4;
++
++ final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0;
++ final net.minecraft.world.level.chunk.ChunkSource chunkSource = world.getChunkSource();
++
++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
++ final net.minecraft.world.level.chunk.ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, loadChunks);
++
++ if (chunk == null) {
++ if ((collisionFlags & COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS) != 0) {
++ if (checkOnly) {
++ return true;
++ } else {
++ intoAABB.add(getBoxForChunk(currChunkX, currChunkZ));
++ ret = true;
++ }
++ }
++ continue;
++ }
++
++ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections();
++
++ // bound y
++ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
++ final int sectionIdx = currChunkY - minSection;
++ if (sectionIdx < 0 || sectionIdx >= sections.length) {
++ continue;
++ }
++ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx];
++ if (section == null || section.hasOnlyAir()) {
++ // empty
++ continue;
++ }
++
++ final boolean hasSpecial = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevelChunkSection)section).moonrise$getSpecialCollidingBlocks() != 0;
++ final int sectionAdjust = !hasSpecial ? 1 : 0;
++
++ final net.minecraft.world.level.chunk.PalettedContainer<net.minecraft.world.level.block.state.BlockState> blocks = section.states;
++
++ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0;
++ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15;
++ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0;
++ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) - sectionAdjust : 15;
++ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) + sectionAdjust : 0;
++ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) - sectionAdjust : 15;
++
++ for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
++ final int blockY = currY | (currChunkY << 4);
++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
++ final int blockZ = currZ | (currChunkZ << 4);
++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
++ final int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8);
++ final int blockX = currX | (currChunkX << 4);
++
++ final int edgeCount = hasSpecial ? ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0) : 0;
++ if (edgeCount == 3) {
++ continue;
++ }
++
++ final net.minecraft.world.level.block.state.BlockState blockData = blocks.get(localBlockIndex);
++
++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$emptyCollisionShape()) {
++ continue;
++ }
++
++ net.minecraft.world.phys.shapes.VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$getConstantCollisionShape();
++
++ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == net.minecraft.world.level.block.Blocks.MOVING_PISTON))) {
++ if (blockCollision == null) {
++ mutablePos.set(blockX, blockY, blockZ);
++ blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape);
++ }
++
++ net.minecraft.world.phys.AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation();
++ if (singleAABB != null) {
++ singleAABB = singleAABB.move((double)blockX, (double)blockY, (double)blockZ);
++ if (!voxelShapeIntersect(aabb, singleAABB)) {
++ continue;
++ }
++
++ if (predicate != null) {
++ mutablePos.set(blockX, blockY, blockZ);
++ if (!predicate.test(blockData, mutablePos)) {
++ continue;
++ }
++ }
++
++ if (checkOnly) {
++ return true;
++ } else {
++ ret = true;
++ intoAABB.add(singleAABB);
++ continue;
++ }
++ }
++
++ if (blockCollision.isEmpty()) {
++ continue;
++ }
++
++ final net.minecraft.world.phys.shapes.VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ);
++
++ if (!voxelShapeIntersectNoEmpty(blockCollisionOffset, aabb)) {
++ continue;
++ }
++
++ if (predicate != null) {
++ mutablePos.set(blockX, blockY, blockZ);
++ if (!predicate.test(blockData, mutablePos)) {
++ continue;
++ }
++ }
++
++ if (checkOnly) {
++ return true;
++ } else {
++ ret = true;
++ intoVoxel.add(blockCollisionOffset);
++ continue;
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++
++ return ret;
++ }
++
++ public static boolean getEntityHardCollisions(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, net.minecraft.world.phys.AABB aabb,
++ final java.util.List<net.minecraft.world.phys.AABB> into, final int collisionFlags, final java.util.function.Predicate<net.minecraft.world.entity.Entity> predicate) {
++ final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0;
++
++ boolean ret = false;
++
++ // to comply with vanilla intersection rules, expand by -epsilon so that we only get stuff we definitely collide with.
++ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems
++ // specifically with boat collisions.
++ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON);
++ final java.util.List<net.minecraft.world.entity.Entity> entities;
++ if (entity != null && ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$isHardColliding()) {
++ entities = world.getEntities(entity, aabb, predicate);
++ } else {
++ entities = ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter)world).moonrise$getHardCollidingEntities(entity, aabb, predicate);
++ }
++
++ for (int i = 0, len = entities.size(); i < len; ++i) {
++ final net.minecraft.world.entity.Entity otherEntity = entities.get(i);
++
++ if (otherEntity.isSpectator()) {
++ continue;
++ }
++
++ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) {
++ if (checkOnly) {
++ return true;
++ } else {
++ into.add(otherEntity.getBoundingBox());
++ ret = true;
++ }
++ }
++ }
++
++ return ret;
++ }
++
++ public static boolean getCollisions(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB aabb,
++ final java.util.List<net.minecraft.world.phys.shapes.VoxelShape> intoVoxel, final java.util.List<net.minecraft.world.phys.AABB> intoAABB, final int collisionFlags,
++ final java.util.function.BiPredicate<net.minecraft.world.level.block.state.BlockState, net.minecraft.core.BlockPos> blockPredicate,
++ final java.util.function.Predicate<net.minecraft.world.entity.Entity> entityPredicate) {
++ if ((collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0) {
++ return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate)
++ || getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate);
++ } else {
++ return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate)
++ | getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate);
++ }
++ }
++
++ public static final class LazyEntityCollisionContext extends net.minecraft.world.phys.shapes.EntityCollisionContext {
++
++ private net.minecraft.world.phys.shapes.CollisionContext delegate;
++ private boolean delegated;
++
++ public LazyEntityCollisionContext(final net.minecraft.world.entity.Entity entity) {
++ super(false, 0.0, null, null, entity);
++ }
++
++ public boolean isDelegated() {
++ final boolean delegated = this.delegated;
++ this.delegated = false;
++ return delegated;
++ }
++
++ public net.minecraft.world.phys.shapes.CollisionContext getDelegate() {
++ this.delegated = true;
++ final net.minecraft.world.entity.Entity entity = this.getEntity();
++ return this.delegate == null ? this.delegate = (entity == null ? net.minecraft.world.phys.shapes.CollisionContext.empty() : net.minecraft.world.phys.shapes.CollisionContext.of(entity)) : this.delegate;
++ }
++
++ @Override
++ public boolean isDescending() {
++ return this.getDelegate().isDescending();
++ }
++
++ @Override
++ public boolean isAbove(final net.minecraft.world.phys.shapes.VoxelShape shape, final net.minecraft.core.BlockPos pos, final boolean defaultValue) {
++ return this.getDelegate().isAbove(shape, pos, defaultValue);
++ }
++
++ @Override
++ public boolean isHoldingItem(final net.minecraft.world.item.Item item) {
++ return this.getDelegate().isHoldingItem(item);
++ }
++
++ @Override
++ public boolean canStandOnFluid(final net.minecraft.world.level.material.FluidState state, final net.minecraft.world.level.material.FluidState fluidState) {
++ return this.getDelegate().canStandOnFluid(state, fluidState);
++ }
++ }
++
++ private CollisionUtil() {
++ throw new RuntimeException();
++ }
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..eb7200657d5c7ac37ee93868ba43be0aefecac6d
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java
+@@ -0,0 +1,23 @@
++package ca.spottedleaf.moonrise.patches.collisions;
++
++public final class ExplosionBlockCache {
++
++ public final long key;
++ public final net.minecraft.core.BlockPos immutablePos;
++ public final net.minecraft.world.level.block.state.BlockState blockState;
++ public final net.minecraft.world.level.material.FluidState fluidState;
++ public final float resistance;
++ public final boolean outOfWorld;
++ public Boolean shouldExplode; // null -> not called yet
++ public net.minecraft.world.phys.shapes.VoxelShape cachedCollisionShape;
++
++ public ExplosionBlockCache(final long key, final net.minecraft.core.BlockPos immutablePos, final net.minecraft.world.level.block.state.BlockState blockState,
++ final net.minecraft.world.level.material.FluidState fluidState, final float resistance, final boolean outOfWorld) {
++ this.key = key;
++ this.immutablePos = immutablePos;
++ this.blockState = blockState;
++ this.fluidState = fluidState;
++ this.resistance = resistance;
++ this.outOfWorld = outOfWorld;
++ }
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..02b29c563a298e06186de010de68a716bccba494
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java
+@@ -0,0 +1,26 @@
++package ca.spottedleaf.moonrise.patches.collisions.block;
++
++public interface CollisionBlockState {
++
++ // note: this does not consider canOcclude, it is only based on the cached collision shape (i.e hasCache())
++ // and whether Shapes.faceShapeOccludes(EMPTY, cached shape) is true
++ public boolean moonrise$occludesFullBlock();
++
++ // whether the cached collision shape exists and is empty
++ public boolean moonrise$emptyCollisionShape();
++
++ // indicates that occludesFullBlock is cached for the collision shape
++ public boolean moonrise$hasCache();
++
++ // note: this is HashCommon#murmurHash3(incremental id); and since murmurHash3 has an inverse function the returned
++ // value is still unique
++ public int moonrise$uniqueId1();
++
++ // note: this is HashCommon#murmurHash3(incremental id); and since murmurHash3 has an inverse function the returned
++ // value is still unique
++ public int moonrise$uniqueId2();
++
++ public net.minecraft.world.phys.shapes.VoxelShape moonrise$getConstantCollisionShape();
++
++ public net.minecraft.world.phys.AABB moonrise$getConstantCollisionAABB();
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..5a6b16be4b8c0cc92d017bc592bc4818dba17da7
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java
+@@ -0,0 +1,10 @@
++package ca.spottedleaf.moonrise.patches.collisions.shape;
++
++public record CachedShapeData(
++ int sizeX, int sizeY, int sizeZ,
++ long[] voxelSet,
++ int minFullX, int minFullY, int minFullZ,
++ int maxFullX, int maxFullY, int maxFullZ,
++ boolean isEmpty, boolean hasSingleAABB
++) {
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..5fe1dad9dad368911aedbe6ba7fcd8f9b0189d32
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java
+@@ -0,0 +1,35 @@
++package ca.spottedleaf.moonrise.patches.collisions.shape;
++
++public record CachedToAABBs(
++ java.util.List<net.minecraft.world.phys.AABB> aabbs,
++ boolean isOffset,
++ double offX, double offY, double offZ
++) {
++
++ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs removeOffset() {
++ final java.util.List<net.minecraft.world.phys.AABB> toOffset = this.aabbs;
++ final double offX = this.offX;
++ final double offY = this.offY;
++ final double offZ = this.offZ;
++
++ final java.util.List<net.minecraft.world.phys.AABB> ret = new java.util.ArrayList<>(toOffset.size());
++
++ for (int i = 0, len = toOffset.size(); i < len; ++i) {
++ ret.add(toOffset.get(i).move(offX, offY, offZ));
++ }
++
++ return new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(ret, false, 0.0, 0.0, 0.0);
++ }
++
++ public static ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs offset(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cache, final double offX, final double offY, final double offZ) {
++ if (offX == 0.0 && offY == 0.0 && offZ == 0.0) {
++ return cache;
++ }
++
++ final double resX = cache.offX + offX;
++ final double resY = cache.offY + offY;
++ final double resZ = cache.offZ + offZ;
++
++ return new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(cache.aabbs, true, resX, resY, resZ);
++ }
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..a09efadea9b733840bbe69830dd8f2a303fe656f
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java
+@@ -0,0 +1,7 @@
++package ca.spottedleaf.moonrise.patches.collisions.shape;
++
++public interface CollisionDiscreteVoxelShape {
++
++ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getOrCreateCachedShapeData();
++
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..70371eb87c11a106e8513cdbc8d938dda088f745
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java
+@@ -0,0 +1,36 @@
++package ca.spottedleaf.moonrise.patches.collisions.shape;
++
++public interface CollisionVoxelShape {
++
++ public double moonrise$offsetX();
++
++ public double moonrise$offsetY();
++
++ public double moonrise$offsetZ();
++
++ public double[] moonrise$rootCoordinatesX();
++
++ public double[] moonrise$rootCoordinatesY();
++
++ public double[] moonrise$rootCoordinatesZ();
++
++ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getCachedVoxelData();
++
++ // rets null if not possible to represent this shape as one AABB
++ public net.minecraft.world.phys.AABB moonrise$getSingleAABBRepresentation();
++
++ // ONLY USE INTERNALLY, ONLY FOR INITIALISING IN CONSTRUCTOR: VOXELSHAPES ARE STATIC
++ public void moonrise$initCache();
++
++ // this returns empty if not clamped to 1.0 or 0.0 depending on direction
++ public net.minecraft.world.phys.shapes.VoxelShape moonrise$getFaceShapeClamped(final net.minecraft.core.Direction direction);
++
++ public boolean moonrise$isFullBlock();
++
++ public boolean moonrise$occludesFullBlock();
++
++ public boolean moonrise$occludesFullBlockIfCached();
++
++ // uses a cache internally
++ public net.minecraft.world.phys.shapes.VoxelShape moonrise$orUnoptimized(final net.minecraft.world.phys.shapes.VoxelShape other);
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..4217426d3eca5e5cd2bc37e509f84da1d6fed0b2
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java
+@@ -0,0 +1,8 @@
++package ca.spottedleaf.moonrise.patches.collisions.shape;
++
++public record MergedORCache(
++ net.minecraft.world.phys.shapes.VoxelShape key,
++ net.minecraft.world.phys.shapes.VoxelShape result
++) {
++
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..f62359e5d6aa9a9cdb015441dbdb6182dc302f02
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java
+@@ -0,0 +1,9 @@
++package ca.spottedleaf.moonrise.patches.collisions.util;
++
++public interface CollisionDirection {
++
++ // note: this is HashCommon#murmurHash3(some unique id) and since murmurHash3 has an inverse function the returned
++ // value is still unique
++ public int moonrise$uniqueId();
++
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..673103f160cbe577c6e05f998706af4e6850011b
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java
+@@ -0,0 +1,225 @@
++package ca.spottedleaf.moonrise.patches.collisions.util;
++
++import java.util.Iterator;
++import java.util.Optional;
++import java.util.Spliterator;
++import java.util.stream.Stream;
++
++public final class EmptyStreamForMoveCall<T> implements java.util.stream.Stream<T> {
++
++ public static final ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall INSTANCE = new ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall();
++
++ @Override
++ public boolean noneMatch(java.util.function.Predicate<? super T> predicate) {
++ return false; // important: ret false so the branch is never taken by mojang code
++ }
++
++ @Override
++ public java.util.stream.Stream<T> filter(java.util.function.Predicate<? super T> predicate) {
++ return null;
++ }
++
++ @Override
++ public <R> java.util.stream.Stream<R> map(java.util.function.Function<? super T, ? extends R> mapper) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.IntStream mapToInt(java.util.function.ToIntFunction<? super T> mapper) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.LongStream mapToLong(java.util.function.ToLongFunction<? super T> mapper) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.DoubleStream mapToDouble(java.util.function.ToDoubleFunction<? super T> mapper) {
++ return null;
++ }
++
++ @Override
++ public <R> java.util.stream.Stream<R> flatMap(java.util.function.Function<? super T, ? extends java.util.stream.Stream<? extends R>> mapper) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.IntStream flatMapToInt(java.util.function.Function<? super T, ? extends java.util.stream.IntStream> mapper) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.LongStream flatMapToLong(java.util.function.Function<? super T, ? extends java.util.stream.LongStream> mapper) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.DoubleStream flatMapToDouble(java.util.function.Function<? super T, ? extends java.util.stream.DoubleStream> mapper) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.Stream<T> distinct() {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.Stream<T> sorted() {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.Stream<T> sorted(java.util.Comparator<? super T> comparator) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.Stream<T> peek(java.util.function.Consumer<? super T> action) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.Stream<T> limit(long maxSize) {
++ return null;
++ }
++
++ @Override
++ public java.util.stream.Stream<T> skip(long n) {
++ return null;
++ }
++
++ @Override
++ public void forEach(java.util.function.Consumer<? super T> action) {
++
++ }
++
++ @Override
++ public void forEachOrdered(java.util.function.Consumer<? super T> action) {
++
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Object[] toArray() {
++ return new Object[0];
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public <A> A[] toArray(java.util.function.IntFunction<A[]> generator) {
++ return null;
++ }
++
++ @Override
++ public T reduce(T identity, java.util.function.BinaryOperator<T> accumulator) {
++ return null;
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Optional<T> reduce(java.util.function.BinaryOperator<T> accumulator) {
++ return java.util.Optional.empty();
++ }
++
++ @Override
++ public <U> U reduce(U identity, java.util.function.BiFunction<U, ? super T, U> accumulator, java.util.function.BinaryOperator<U> combiner) {
++ return null;
++ }
++
++ @Override
++ public <R> R collect(java.util.function.Supplier<R> supplier, java.util.function.BiConsumer<R, ? super T> accumulator, java.util.function.BiConsumer<R, R> combiner) {
++ return null;
++ }
++
++ @Override
++ public <R, A> R collect(java.util.stream.Collector<? super T, A, R> collector) {
++ return null;
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Optional<T> min(java.util.Comparator<? super T> comparator) {
++ return java.util.Optional.empty();
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Optional<T> max(java.util.Comparator<? super T> comparator) {
++ return java.util.Optional.empty();
++ }
++
++ @Override
++ public long count() {
++ return 0;
++ }
++
++ @Override
++ public boolean anyMatch(java.util.function.Predicate<? super T> predicate) {
++ return false;
++ }
++
++ @Override
++ public boolean allMatch(java.util.function.Predicate<? super T> predicate) {
++ return false;
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Optional<T> findFirst() {
++ return java.util.Optional.empty();
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Optional<T> findAny() {
++ return java.util.Optional.empty();
++ }
++
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Iterator<T> iterator() {
++ return null;
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Spliterator<T> spliterator() {
++ return null;
++ }
++
++ @Override
++ public boolean isParallel() {
++ return false;
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Stream<T> sequential() {
++ return null;
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Stream<T> parallel() {
++ return null;
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Stream<T> unordered() {
++ return null;
++ }
++
++ @org.jetbrains.annotations.NotNull
++ @Override
++ public Stream<T> onClose(Runnable closeHandler) {
++ return null;
++ }
++
++ @Override
++ public void close() {
++
++ }
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..128267ff40b38c7b3ea0feb5133825cc6aae075b
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java
+@@ -0,0 +1,4 @@
++package ca.spottedleaf.moonrise.patches.collisions.util;
++
++public record FluidOcclusionCacheKey(net.minecraft.world.level.block.state.BlockState first, net.minecraft.world.level.block.state.BlockState second, net.minecraft.core.Direction direction, boolean result) {
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..e851e81e13edbad6316df63fcb7095d48f85c5b0
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java
+@@ -0,0 +1,9 @@
++package ca.spottedleaf.moonrise.patches.collisions.world;
++
++public interface CollisionLevel {
++
++ public int moonrise$getMinSection();
++
++ public int moonrise$getMaxSection();
++
++}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2f0b43374b70a51aeabfd9e0e16738e0ab584517
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java
+@@ -0,0 +1,7 @@
++package ca.spottedleaf.moonrise.patches.collisions.world;
++
++public interface CollisionLevelChunkSection {
++
++ public int moonrise$getSpecialCollidingBlocks();
++
++}
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f5734c00ce8245a1ff69b2d4c3036579d5392e0
@@ -23425,6 +25771,115 @@ index 73e83d56a340f0c7dcb8ff737d621003e72c6de4..d05297d77147ab68f8c5bb08f13a1f88
public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) {
return isTickThread();
}
+diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java
+index 03c45ee77276462818a6f774b5945b25924aa3f0..f15dd2ccb99ade10ac1e49b63e6f4080bd39b3c9 100644
+--- a/src/main/java/net/minecraft/core/Direction.java
++++ b/src/main/java/net/minecraft/core/Direction.java
+@@ -27,7 +27,7 @@ import org.joml.Quaternionf;
+ import org.joml.Vector3f;
+ import org.joml.Vector4f;
+
+-public enum Direction implements StringRepresentable {
++public enum Direction implements StringRepresentable, ca.spottedleaf.moonrise.patches.collisions.util.CollisionDirection { // Paper - optimise collisions
+ DOWN(0, 1, -1, "down", Direction.AxisDirection.NEGATIVE, Direction.Axis.Y, new Vec3i(0, -1, 0)),
+ UP(1, 0, -1, "up", Direction.AxisDirection.POSITIVE, Direction.Axis.Y, new Vec3i(0, 1, 0)),
+ NORTH(2, 3, 2, "north", Direction.AxisDirection.NEGATIVE, Direction.Axis.Z, new Vec3i(0, 0, -1)),
+@@ -60,6 +60,46 @@ public enum Direction implements StringRepresentable {
+ private final int adjY;
+ private final int adjZ;
+ // Paper end - Perf: Inline shift direction fields
++ // Paper start - optimise collisions
++ private static final int RANDOM_OFFSET = 2017601568;
++ private Direction opposite;
++ private Quaternionf rotation;
++ private int id;
++ private int stepX;
++ private int stepY;
++ private int stepZ;
++
++ private Quaternionf getRotationUncached() {
++ switch ((Direction)(Object)this) {
++ case DOWN: {
++ return new Quaternionf().rotationX(3.1415927F);
++ }
++ case UP: {
++ return new Quaternionf();
++ }
++ case NORTH: {
++ return new Quaternionf().rotationXYZ(1.5707964F, 0.0F, 3.1415927F);
++ }
++ case SOUTH: {
++ return new Quaternionf().rotationX(1.5707964F);
++ }
++ case WEST: {
++ return new Quaternionf().rotationXYZ(1.5707964F, 0.0F, 1.5707964F);
++ }
++ case EAST: {
++ return new Quaternionf().rotationXYZ(1.5707964F, 0.0F, -1.5707964F);
++ }
++ default: {
++ throw new IllegalStateException();
++ }
++ }
++ }
++
++ @Override
++ public final int moonrise$uniqueId() {
++ return this.id;
++ }
++ // Paper end - optimise collisions
+
+ private Direction(
+ final int id,
+@@ -134,14 +174,13 @@ public enum Direction implements StringRepresentable {
+ }
+
+ public Quaternionf getRotation() {
+- return switch (this) {
+- case DOWN -> new Quaternionf().rotationX((float) Math.PI);
+- case UP -> new Quaternionf();
+- case NORTH -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) Math.PI);
+- case SOUTH -> new Quaternionf().rotationX((float) (Math.PI / 2));
+- case WEST -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) (Math.PI / 2));
+- case EAST -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) (-Math.PI / 2));
+- };
++ // Paper start - optimise collisions
++ try {
++ return (Quaternionf)this.rotation.clone();
++ } catch (final CloneNotSupportedException ex) {
++ throw new InternalError(ex);
++ }
++ // Paper end - optimise collisions
+ }
+
+ public int get3DDataValue() {
+@@ -165,7 +204,7 @@ public enum Direction implements StringRepresentable {
+ }
+
+ public Direction getOpposite() {
+- return from3DDataValue(this.oppositeIndex);
++ return this.opposite; // Paper - optimise collisions
+ }
+
+ public Direction getClockWise(Direction.Axis axis) {
+@@ -551,4 +590,17 @@ public enum Direction implements StringRepresentable {
+ return this.faces.length;
+ }
+ }
++
++ // Paper start - optimise collisions
++ static {
++ for (final Direction direction : VALUES) {
++ ((Direction)(Object)direction).opposite = from3DDataValue(((Direction)(Object)direction).oppositeIndex);
++ ((Direction)(Object)direction).rotation = ((Direction)(Object)direction).getRotationUncached();
++ ((Direction)(Object)direction).id = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(direction.ordinal() + RANDOM_OFFSET) + RANDOM_OFFSET);
++ ((Direction)(Object)direction).stepX = ((Direction)(Object)direction).normal.getX();
++ ((Direction)(Object)direction).stepY = ((Direction)(Object)direction).normal.getY();
++ ((Direction)(Object)direction).stepZ = ((Direction)(Object)direction).normal.getZ();
++ }
++ }
++ // Paper end - optimise collisions
+ }
diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
index c33f85b570f159ab465b5a10a8044a81f2797f43..244a19ecd0234fa1d7a6ecfea20751595688605d 100644
--- a/src/main/java/net/minecraft/server/Main.java
@@ -26475,6 +28930,22 @@ index be9604a0f267558c95125852d86761a2f175732a..67eb2fb32de3555b3afb4b4b7a3a47a1
// CraftBukkit end
}
}
+diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
+index 1d849ce4e2c85f149af25318b8ffb6dcef6c6788..12d86f27d04bffed8c3844e36b42fbc2f84dacff 100644
+--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
+@@ -97,6 +97,11 @@ public class ServerEntity {
+ }
+
+ public void sendChanges() {
++ // Paper start - optimise collisions
++ if (((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this.entity).moonrise$isHardColliding()) {
++ this.teleportDelay = 9999;
++ }
++ // Paper end - optimise collisions
+ List<Entity> list = this.entity.getPassengers();
+
+ if (!list.equals(this.lastPassengers)) {
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 4d7e234d379a451c4bb53bc2fcdf22cb191f8d1a..e955bf0c9f76f6e90bd726342f204e999090fee1 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -27565,7 +30036,7 @@ index ea72dcb064a35bc6245bc5c94d592efedd8faf41..87ee8e51dfa7657ed7d83fcbceef48bf
this.comparator = comparator;
if (initialCapacity < 0) {
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 2e2101274f3afebbae783fa119f5cae8104de45d..690d3f669092f0a0a39a93a401c2e8a1626650b4 100644
+index 2e2101274f3afebbae783fa119f5cae8104de45d..4f4b7e738fe604808d837a38d23bf437bc1d5329 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -167,7 +167,7 @@ import org.bukkit.event.player.PlayerTeleportEvent;
@@ -27675,7 +30146,200 @@ index 2e2101274f3afebbae783fa119f5cae8104de45d..690d3f669092f0a0a39a93a401c2e8a1
public Entity(EntityType<?> type, Level world) {
this.id = Entity.ENTITY_COUNTER.incrementAndGet();
-@@ -4025,14 +4116,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -1279,41 +1370,82 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ }
+
+ private Vec3 collide(Vec3 movement) {
+- AABB axisalignedbb = this.getBoundingBox();
+- List<VoxelShape> list = this.level().getEntityCollisions(this, axisalignedbb.expandTowards(movement));
+- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBox(this, movement, axisalignedbb, this.level(), list);
+- boolean flag = movement.x != vec3d1.x;
+- boolean flag1 = movement.y != vec3d1.y;
+- boolean flag2 = movement.z != vec3d1.z;
+- boolean flag3 = flag1 && movement.y < 0.0D;
+-
+- if (this.maxUpStep() > 0.0F && (flag3 || this.onGround()) && (flag || flag2)) {
+- AABB axisalignedbb1 = flag3 ? axisalignedbb.move(0.0D, vec3d1.y, 0.0D) : axisalignedbb;
+- AABB axisalignedbb2 = axisalignedbb1.expandTowards(movement.x, (double) this.maxUpStep(), movement.z);
+-
+- if (!flag3) {
+- axisalignedbb2 = axisalignedbb2.expandTowards(0.0D, -9.999999747378752E-6D, 0.0D);
+- }
++ // Paper start - optimise collisions
++ final boolean xZero = movement.x == 0.0;
++ final boolean yZero = movement.y == 0.0;
++ final boolean zZero = movement.z == 0.0;
++ if (xZero & yZero & zZero) {
++ return movement;
++ }
+
+- List<VoxelShape> list1 = Entity.collectColliders(this, this.level, list, axisalignedbb2);
+- float f = (float) vec3d1.y;
+- float[] afloat = Entity.collectCandidateStepUpHeights(axisalignedbb1, list1, this.maxUpStep(), f);
+- float[] afloat1 = afloat;
+- int i = afloat.length;
++ final Level world = this.level;
++ final AABB currBoundingBox = this.getBoundingBox();
+
+- for (int j = 0; j < i; ++j) {
+- float f1 = afloat1[j];
+- Vec3 vec3d2 = Entity.collideWithShapes(new Vec3(movement.x, (double) f1, movement.z), axisalignedbb1, list1);
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(currBoundingBox)) {
++ return movement;
++ }
+
+- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) {
+- double d0 = axisalignedbb.minY - axisalignedbb1.minY;
++ final List<AABB> potentialCollisionsBB = new ArrayList<>();
++ final List<VoxelShape> potentialCollisionsVoxel = new ArrayList<>();
++ final double stepHeight = (double)this.maxUpStep();
++ final AABB collisionBox;
++ final boolean onGround = this.onGround;
+
+- return vec3d2.add(0.0D, -d0, 0.0D);
++ if (xZero & zZero) {
++ if (movement.y > 0.0) {
++ collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currBoundingBox, movement.y);
++ } else {
++ collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currBoundingBox, movement.y);
++ }
++ } else {
++ // note: xZero == false or zZero == false
++ if (stepHeight > 0.0 && (onGround || (movement.y < 0.0))) {
++ // don't bother getting the collisions if we don't need them.
++ if (movement.y <= 0.0) {
++ collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight);
++ } else {
++ collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z);
+ }
++ } else {
++ collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z);
+ }
+ }
+
+- return vec3d1;
++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisions(
++ world, (Entity)(Object)this, collisionBox, potentialCollisionsVoxel, potentialCollisionsBB,
++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER,
++ null, null
++ );
++
++ if (potentialCollisionsVoxel.isEmpty() && potentialCollisionsBB.isEmpty()) {
++ return movement;
++ }
++
++ final Vec3 limitedMoveVector = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB);
++
++ if (stepHeight > 0.0
++ && (onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0))
++ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) {
++ Vec3 vec3d2 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB);
++ final Vec3 vec3d3 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisionsVoxel, potentialCollisionsBB);
++
++ if (vec3d3.y < stepHeight) {
++ final Vec3 vec3d4 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisionsVoxel, potentialCollisionsBB).add(vec3d3);
++
++ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) {
++ vec3d2 = vec3d4;
++ }
++ }
++
++ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) {
++ return vec3d2.add(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisionsVoxel, potentialCollisionsBB));
++ }
++
++ return limitedMoveVector;
++ } else {
++ return limitedMoveVector;
++ }
++ // Paper end - optimise collisions
+ }
+
+ private static float[] collectCandidateStepUpHeights(AABB collisionBox, List<VoxelShape> collisions, float f, float stepHeight) {
+@@ -2629,18 +2761,75 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ }
+
+ public boolean isInWall() {
++ // Paper start - optimise collisions
+ if (this.noPhysics) {
+ return false;
+- } else {
+- float f = this.dimensions.width() * 0.8F;
+- AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f);
++ }
+
+- return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> {
+- BlockState iblockdata = this.level().getBlockState(blockposition);
++ final float reducedWith = this.dimensions.width() * 0.8F;
++ final AABB box = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith);
++
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(box)) {
++ return false;
++ }
+
+- return !iblockdata.isAir() && iblockdata.isSuffocating(this.level(), blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level(), blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND);
+- });
++ final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos();
++
++ final int minX = Mth.floor(box.minX);
++ final int minY = Mth.floor(box.minY);
++ final int minZ = Mth.floor(box.minZ);
++ final int maxX = Mth.floor(box.maxX);
++ final int maxY = Mth.floor(box.maxY);
++ final int maxZ = Mth.floor(box.maxZ);
++
++ final net.minecraft.world.level.chunk.ChunkSource chunkProvider = this.level.getChunkSource();
++
++ long lastChunkKey = ChunkPos.INVALID_CHUNK_POS;
++ net.minecraft.world.level.chunk.LevelChunk lastChunk = null;
++ for (int fz = minZ; fz <= maxZ; ++fz) {
++ tempPos.setZ(fz);
++ for (int fx = minX; fx <= maxX; ++fx) {
++ final int newChunkX = fx >> 4;
++ final int newChunkZ = fz >> 4;
++ final net.minecraft.world.level.chunk.LevelChunk chunk = lastChunkKey == (lastChunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ?
++ lastChunk : (lastChunk = (net.minecraft.world.level.chunk.LevelChunk)chunkProvider.getChunk(newChunkX, newChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true));
++ tempPos.setX(fx);
++ for (int fy = minY; fy <= maxY; ++fy) {
++ tempPos.setY(fy);
++
++ final BlockState state = chunk.getBlockState(tempPos);
++
++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) {
++ continue;
++ }
++
++ // Yes, it does not use the Entity context stuff.
++ final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos);
++
++ if (collisionShape.isEmpty()) {
++ continue;
++ }
++
++ final AABB toCollide = box.move(-(double)fx, -(double)fy, -(double)fz);
++
++ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation();
++ if (singleAABB != null) {
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) {
++ return true;
++ }
++ continue;
++ }
++
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) {
++ return true;
++ }
++ continue;
++ }
++ }
+ }
++
++ return false;
++ // Paper end - optimise collisions
+ }
+
+ public InteractionResult interact(Player player, InteractionHand hand) {
+@@ -4025,14 +4214,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public Iterable<Entity> getIndirectPassengers() {
@@ -27700,7 +30364,7 @@ index 2e2101274f3afebbae783fa119f5cae8104de45d..690d3f669092f0a0a39a93a401c2e8a1
}
private Iterable<Entity> getIndirectPassengers_old() {
// Paper end - Optimize indirect passenger iteration
-@@ -4397,6 +4491,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4397,6 +4589,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.setPosRaw(x, y, z, false);
}
public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
@@ -27716,7 +30380,7 @@ index 2e2101274f3afebbae783fa119f5cae8104de45d..690d3f669092f0a0a39a93a401c2e8a1
if (!checkPosition(this, x, y, z)) {
return;
}
-@@ -4528,6 +4631,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4528,6 +4729,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@Override
public final void setRemoved(Entity.RemovalReason entity_removalreason, EntityRemoveEvent.Cause cause) {
@@ -27729,7 +30393,7 @@ index 2e2101274f3afebbae783fa119f5cae8104de45d..690d3f669092f0a0a39a93a401c2e8a1
CraftEventFactory.callEntityRemoveEvent(this, cause);
// CraftBukkit end
final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers
-@@ -4539,7 +4648,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4539,7 +4746,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.stopRiding();
}
@@ -27738,7 +30402,7 @@ index 2e2101274f3afebbae783fa119f5cae8104de45d..690d3f669092f0a0a39a93a401c2e8a1
this.levelCallback.onRemove(entity_removalreason);
// Paper start - Folia schedulers
if (!(this instanceof ServerPlayer) && entity_removalreason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) {
-@@ -4570,7 +4679,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+@@ -4570,7 +4777,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@Override
public boolean shouldBeSaved() {
@@ -28001,8 +30665,34 @@ index 971fb29a2c3dc713cb8ab1d2eed054cc16f9c93c..a6c0e89cb645693034f8e90ac2de8f2d
public PoiSection(Runnable updateListener) {
this(updateListener, true, ImmutableList.of());
}
+diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
+index 2f398750bfee5758ad8b1367b6fc14364e4de776..c292f58ba4b29395484dbbf8591e455f449581d8 100644
+--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
+@@ -359,7 +359,7 @@ public class ArmorStand extends LivingEntity {
+ @Override
+ protected void pushEntities() {
+ if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups
+- List<Entity> list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS);
++ List<AbstractMinecart> list = this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), RIDABLE_MINECARTS); // Paper - optimise collisions
+ Iterator iterator = list.iterator();
+
+ while (iterator.hasNext()) {
+diff --git a/src/main/java/net/minecraft/world/level/ClipContext.java b/src/main/java/net/minecraft/world/level/ClipContext.java
+index 3fa2964b979053ecbefc946c7fe76828de86d8f1..28bf0518f7d17099d7e4990defbeda6757b4477c 100644
+--- a/src/main/java/net/minecraft/world/level/ClipContext.java
++++ b/src/main/java/net/minecraft/world/level/ClipContext.java
+@@ -18,7 +18,7 @@ public class ClipContext {
+ private final Vec3 from;
+ private final Vec3 to;
+ private final ClipContext.Block block;
+- private final ClipContext.Fluid fluid;
++ public final ClipContext.Fluid fluid; // Paper - optimise collisions - public
+ private final CollisionContext collisionContext;
+
+ public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) {
diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
-index bd20bea7f76a7307f1698fb2dfef37125032d166..70c2017400168d4fef3c14462798edcfed58d4bf 100644
+index bd20bea7f76a7307f1698fb2dfef37125032d166..141b748abe80402731cdaf14a3d36aa7cef4f4bd 100644
--- a/src/main/java/net/minecraft/world/level/EntityGetter.java
+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
@@ -18,7 +18,7 @@ import net.minecraft.world.phys.shapes.BooleanOp;
@@ -28014,10 +30704,20 @@ index bd20bea7f76a7307f1698fb2dfef37125032d166..70c2017400168d4fef3c14462798edcf
List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate);
<T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate);
-@@ -33,6 +33,13 @@ public interface EntityGetter {
+@@ -33,21 +33,44 @@ public interface EntityGetter {
return this.getEntities(except, box, EntitySelector.NO_SPECTATORS);
}
+- default boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) {
+- if (shape.isEmpty()) {
+- return true;
+- } else {
+- for (Entity entity : this.getEntities(except, shape.bounds())) {
+- if (!entity.isRemoved()
+- && entity.blocksBuilding
+- && (except == null || !entity.isPassengerOfSameVehicle(except))
+- && Shapes.joinIsNotEmpty(shape, Shapes.create(entity.getBoundingBox()), BooleanOp.AND)) {
+- return false;
+ // Paper start - rewrite chunk system
+ @Override
+ default List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate) {
@@ -28025,23 +30725,611 @@ index bd20bea7f76a7307f1698fb2dfef37125032d166..70c2017400168d4fef3c14462798edcf
+ }
+ // Paper end - rewrite chunk system
+
- default boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) {
- if (shape.isEmpty()) {
- return true;
++ // Paper start - optimise collisions
++ default boolean isUnobstructed(@Nullable Entity entity, VoxelShape voxel) {
++ if (voxel.isEmpty()) {
++ return false;
++ }
++
++ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation();
++ final List<Entity> entities = this.getEntities(
++ entity,
++ singleAABB == null ? voxel.bounds() : singleAABB.inflate(-ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)
++ );
++
++ for (int i = 0, len = entities.size(); i < len; ++i) {
++ final Entity otherEntity = entities.get(i);
++
++ if (otherEntity.isRemoved() || !otherEntity.blocksBuilding || (entity != null && otherEntity.isPassengerOfSameVehicle(entity))) {
++ continue;
++ }
++
++ if (singleAABB == null) {
++ final AABB entityBB = otherEntity.getBoundingBox();
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(entityBB) || !ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(voxel, entityBB)) {
++ continue;
+ }
+ }
+
+- return true;
++ return false;
+ }
++
++ return true;
++ // Paper end - optimise collisions
+ }
+
+ default <T extends Entity> List<T> getEntitiesOfClass(Class<T> entityClass, AABB box) {
+@@ -55,23 +78,41 @@ public interface EntityGetter {
+ }
+
+ default List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box) {
+- if (box.getSize() < 1.0E-7) {
+- return List.of();
++ // Paper start - optimise collisions
++ // first behavior change is to correctly check for empty AABB
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(box)) {
++ // reduce indirection by always returning type with same class
++ return new java.util.ArrayList<>();
++ }
++
++ // to comply with vanilla intersection rules, expand by -epsilon so that we only get stuff we definitely collide with.
++ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems
++ // specifically with boat collisions.
++ box = box.inflate(-ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON);
++
++ final List<Entity> entities;
++ if (entity != null && ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$isHardColliding()) {
++ entities = this.getEntities(entity, box, null);
+ } else {
+- Predicate<Entity> predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith);
+- List<Entity> list = this.getEntities(entity, box.inflate(1.0E-7), predicate);
+- if (list.isEmpty()) {
+- return List.of();
+- } else {
+- Builder<VoxelShape> builder = ImmutableList.builderWithExpectedSize(list.size());
+-
+- for (Entity entity2 : list) {
+- builder.add(Shapes.create(entity2.getBoundingBox()));
+- }
++ entities = ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter)this).moonrise$getHardCollidingEntities(entity, box, null);
++ }
+
+- return builder.build();
++ final List<VoxelShape> ret = new java.util.ArrayList<>(Math.min(25, entities.size()));
++
++ for (int i = 0, len = entities.size(); i < len; ++i) {
++ final Entity otherEntity = entities.get(i);
++
++ if (otherEntity.isSpectator()) {
++ continue;
++ }
++
++ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) {
++ ret.add(Shapes.create(otherEntity.getBoundingBox()));
+ }
+ }
++
++ return ret;
++ // Paper end - optimise collisions
+ }
+
+ // Paper start - Affects Spawning API
+diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
+index bff83fe413c7baef4ba56a3270ea4463a58c792f..3ca6e0a948194d1c4528472d4b96cb26bc0946d8 100644
+--- a/src/main/java/net/minecraft/world/level/Explosion.java
++++ b/src/main/java/net/minecraft/world/level/Explosion.java
+@@ -75,6 +75,247 @@ public class Explosion {
+ public float yield;
+ // CraftBukkit end
+
++ // Paper start - optimise collisions
++ private static final double[] CACHED_RAYS;
++ static {
++ final it.unimi.dsi.fastutil.doubles.DoubleArrayList rayCoords = new it.unimi.dsi.fastutil.doubles.DoubleArrayList();
++
++ for (int x = 0; x <= 15; ++x) {
++ for (int y = 0; y <= 15; ++y) {
++ for (int z = 0; z <= 15; ++z) {
++ if ((x == 0 || x == 15) || (y == 0 || y == 15) || (z == 0 || z == 15)) {
++ double xDir = (double)((float)x / 15.0F * 2.0F - 1.0F);
++ double yDir = (double)((float)y / 15.0F * 2.0F - 1.0F);
++ double zDir = (double)((float)z / 15.0F * 2.0F - 1.0F);
++
++ double mag = Math.sqrt(
++ xDir * xDir + yDir * yDir + zDir * zDir
++ );
++
++ rayCoords.add((xDir / mag) * (double)0.3F);
++ rayCoords.add((yDir / mag) * (double)0.3F);
++ rayCoords.add((zDir / mag) * (double)0.3F);
++ }
++ }
++ }
++ }
++
++ CACHED_RAYS = rayCoords.toDoubleArray();
++ }
++
++ private static final int CHUNK_CACHE_SHIFT = 2;
++ private static final int CHUNK_CACHE_MASK = (1 << CHUNK_CACHE_SHIFT) - 1;
++ private static final int CHUNK_CACHE_WIDTH = 1 << CHUNK_CACHE_SHIFT;
++
++ private static final int BLOCK_EXPLOSION_CACHE_SHIFT = 3;
++ private static final int BLOCK_EXPLOSION_CACHE_MASK = (1 << BLOCK_EXPLOSION_CACHE_SHIFT) - 1;
++ private static final int BLOCK_EXPLOSION_CACHE_WIDTH = 1 << BLOCK_EXPLOSION_CACHE_SHIFT;
++
++ // resistance = (res + 0.3F) * 0.3F;
++ // so for resistance = 0, we need res = -0.3F
++ private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f);
++ private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache> blockCache = null;
++ private long[] chunkPosCache = null;
++ private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null;
++ private ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z,
++ final long key, final boolean calculateResistance) {
++ ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache ret = this.blockCache.get(key);
++ if (ret != null) {
++ return ret;
++ }
++
++ BlockPos pos = new BlockPos(x, y, z);
++
++ if (!this.level.isInWorldBounds(pos)) {
++ ret = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache(key, pos, null, null, 0.0f, true);
++ } else {
++ net.minecraft.world.level.chunk.LevelChunk chunk;
++ long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(x >> 4, z >> 4);
++ int chunkCacheKey = ((x >> 4) & CHUNK_CACHE_MASK) | (((z >> 4) << CHUNK_CACHE_SHIFT) & (CHUNK_CACHE_MASK << CHUNK_CACHE_SHIFT));
++ if (this.chunkPosCache[chunkCacheKey] == chunkKey) {
++ chunk = this.chunkCache[chunkCacheKey];
++ } else {
++ this.chunkPosCache[chunkCacheKey] = chunkKey;
++ this.chunkCache[chunkCacheKey] = chunk = this.level.getChunk(x >> 4, z >> 4);
++ }
++
++ BlockState blockState = ((ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk)chunk).moonrise$getBlock(x, y, z);
++ FluidState fluidState = blockState.getFluidState();
++
++ Optional<Float> resistance = !calculateResistance ? Optional.empty() : this.damageCalculator.getBlockExplosionResistance((Explosion)(Object)this, this.level, pos, blockState, fluidState);
++
++ ret = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache(
++ key, pos, blockState, fluidState,
++ (resistance.orElse(ZERO_RESISTANCE).floatValue() + 0.3f) * 0.3f,
++ false
++ );
++ }
++
++ this.blockCache.put(key, ret);
++
++ return ret;
++ }
++
++ private boolean clipsAnything(final Vec3 from, final Vec3 to,
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext context,
++ final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache,
++ final BlockPos.MutableBlockPos currPos) {
++ // assume that context.delegated = false
++ final double adjX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.x - to.x);
++ final double adjY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.y - to.y);
++ final double adjZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.z - to.z);
++
++ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) {
++ return false;
++ }
++
++ final double toXAdj = to.x - adjX;
++ final double toYAdj = to.y - adjY;
++ final double toZAdj = to.z - adjZ;
++ final double fromXAdj = from.x + adjX;
++ final double fromYAdj = from.y + adjY;
++ final double fromZAdj = from.z + adjZ;
++
++ int currX = Mth.floor(fromXAdj);
++ int currY = Mth.floor(fromYAdj);
++ int currZ = Mth.floor(fromZAdj);
++
++ final double diffX = toXAdj - fromXAdj;
++ final double diffY = toYAdj - fromYAdj;
++ final double diffZ = toZAdj - fromZAdj;
++
++ final double dxDouble = Math.signum(diffX);
++ final double dyDouble = Math.signum(diffY);
++ final double dzDouble = Math.signum(diffZ);
++
++ final int dx = (int)dxDouble;
++ final int dy = (int)dyDouble;
++ final int dz = (int)dzDouble;
++
++ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX;
++ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY;
++ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ;
++
++ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj));
++ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj));
++ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj));
++
++ for (;;) {
++ currPos.set(currX, currY, currZ);
++
++ // ClipContext.Block.COLLIDER -> BlockBehaviour.BlockStateBase::getCollisionShape
++ // ClipContext.Fluid.NONE -> ignore fluids
++
++ // read block from cache
++ final long key = BlockPos.asLong(currX, currY, currZ);
++
++ final int cacheKey =
++ (currX & BLOCK_EXPLOSION_CACHE_MASK) |
++ (currY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) |
++ (currZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT);
++ ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache cachedBlock = blockCache[cacheKey];
++ if (cachedBlock == null || cachedBlock.key != key) {
++ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(currX, currY, currZ, key, false);
++ }
++
++ final BlockState blockState = cachedBlock.blockState;
++ if (blockState != null && !((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockState).moonrise$emptyCollisionShape()) {
++ net.minecraft.world.phys.shapes.VoxelShape collision = cachedBlock.cachedCollisionShape;
++ if (collision == null) {
++ collision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockState).moonrise$getConstantCollisionShape();
++ if (collision == null) {
++ collision = blockState.getCollisionShape(this.level, currPos, context);
++ if (!context.isDelegated()) {
++ // if it was not delegated during this call, assume that for any future ones it will not be delegated
++ // again, and cache the result
++ cachedBlock.cachedCollisionShape = collision;
++ }
++ } else {
++ cachedBlock.cachedCollisionShape = collision;
++ }
++ }
++
++ if (!collision.isEmpty() && collision.clip(from, to, currPos) != null) {
++ return true;
++ }
++ }
++
++ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) {
++ return false;
++ }
++
++ // inc the smallest normalized coordinate
++
++ if (normalizedCurrX < normalizedCurrY) {
++ if (normalizedCurrX < normalizedCurrZ) {
++ currX += dx;
++ normalizedCurrX += normalizedDiffX;
++ } else {
++ // x < y && x >= z <--> z < y && z <= x
++ currZ += dz;
++ normalizedCurrZ += normalizedDiffZ;
++ }
++ } else if (normalizedCurrY < normalizedCurrZ) {
++ // y <= x && y < z
++ currY += dy;
++ normalizedCurrY += normalizedDiffY;
++ } else {
++ // y <= x && z <= y <--> z <= y && z <= x
++ currZ += dz;
++ normalizedCurrZ += normalizedDiffZ;
++ }
++ }
++ }
++
++ private float getSeenFraction(final Vec3 source, final Entity target,
++ final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache,
++ final BlockPos.MutableBlockPos blockPos) {
++ final AABB boundingBox = target.getBoundingBox();
++ final double diffX = boundingBox.maxX - boundingBox.minX;
++ final double diffY = boundingBox.maxY - boundingBox.minY;
++ final double diffZ = boundingBox.maxZ - boundingBox.minZ;
++
++ final double incX = 1.0 / (diffX * 2.0 + 1.0);
++ final double incY = 1.0 / (diffY * 2.0 + 1.0);
++ final double incZ = 1.0 / (diffZ * 2.0 + 1.0);
++
++ if (incX < 0.0 || incY < 0.0 || incZ < 0.0) {
++ return 0.0f;
++ }
++
++ final double offX = (1.0 - Math.floor(1.0 / incX) * incX) * 0.5 + boundingBox.minX;
++ final double offY = boundingBox.minY;
++ final double offZ = (1.0 - Math.floor(1.0 / incZ) * incZ) * 0.5 + boundingBox.minZ;
++
++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext context = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(target);
++
++ int totalRays = 0;
++ int missedRays = 0;
++
++ for (double dx = 0.0; dx <= 1.0; dx += incX) {
++ final double fromX = Math.fma(dx, diffX, offX);
++ for (double dy = 0.0; dy <= 1.0; dy += incY) {
++ final double fromY = Math.fma(dy, diffY, offY);
++ for (double dz = 0.0; dz <= 1.0; dz += incZ) {
++ ++totalRays;
++
++ final Vec3 from = new Vec3(
++ fromX,
++ fromY,
++ Math.fma(dz, diffZ, offZ)
++ );
++
++ if (!this.clipsAnything(from, source, context, blockCache, blockPos)) {
++ ++missedRays;
++ }
++ }
++ }
++ }
++
++ return (float)missedRays / (float)totalRays;
++ }
++ // Paper end - optimise collisions
++
+ public static DamageSource getDefaultDamageSource(Level world, @Nullable Entity source) {
+ return world.damageSources().explosion(source, Explosion.getIndirectSourceEntityInternal(source));
+ }
+@@ -167,68 +408,108 @@ public class Explosion {
+ }
+ // CraftBukkit end
+ this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z));
+- Set<BlockPos> set = Sets.newHashSet();
++
++ // Paper start - collision optimisations
++ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
++
++ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
++ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS);
++
++ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
++
++ final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
++
++ // use initial cache value that is most likely to be used: the source position
++ final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache initialCache;
++ {
++ final int blockX = Mth.floor(this.x);
++ final int blockY = Mth.floor(this.y);
++ final int blockZ = Mth.floor(this.z);
++
++ final long key = BlockPos.asLong(blockX, blockY, blockZ);
++
++ initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
++ }
++ // Paper end - collision optimisations
++
+ boolean flag = true;
+
+ int i;
+ int j;
+
+- for (int k = 0; k < 16; ++k) {
+- for (i = 0; i < 16; ++i) {
+- for (j = 0; j < 16; ++j) {
+- if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) {
+- double d0 = (double) ((float) k / 15.0F * 2.0F - 1.0F);
+- double d1 = (double) ((float) i / 15.0F * 2.0F - 1.0F);
+- double d2 = (double) ((float) j / 15.0F * 2.0F - 1.0F);
+- double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);
+-
+- d0 /= d3;
+- d1 /= d3;
+- d2 /= d3;
+- float f = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
+- double d4 = this.x;
+- double d5 = this.y;
+- double d6 = this.z;
+-
+- for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
+- BlockPos blockposition = BlockPos.containing(d4, d5, d6);
+- BlockState iblockdata = this.level.getBlockState(blockposition);
+- if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
+- FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
+-
+- if (!this.level.isInWorldBounds(blockposition)) {
+- break;
++ // Paper start - collision optimisations
++ for (int ray = 0, len = CACHED_RAYS.length; ray < len;) {
++ ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache cachedBlock = initialCache;
++
++ double currX = this.x;
++ double currY = this.y;
++ double currZ = this.z;
++
++ final double incX = CACHED_RAYS[ray];
++ final double incY = CACHED_RAYS[ray + 1];
++ final double incZ = CACHED_RAYS[ray + 2];
++
++ ray += 3;
++ float power = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
++ do {
++ final int blockX = Mth.floor(currX);
++ final int blockY = Mth.floor(currY);
++ final int blockZ = Mth.floor(currZ);
++
++ final long key = BlockPos.asLong(blockX, blockY, blockZ);
++
++ if (cachedBlock.key != key) {
++ final int cacheKey =
++ (blockX & BLOCK_EXPLOSION_CACHE_MASK) |
++ (blockY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) |
++ (blockZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT);
++ cachedBlock = blockCache[cacheKey];
++ if (cachedBlock == null || cachedBlock.key != key) {
++ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
++ }
+ }
+
+- Optional<Float> optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockposition, iblockdata, fluid);
+-
+- if (optional.isPresent()) {
+- f -= ((Float) optional.get() + 0.3F) * 0.3F;
++ if (cachedBlock.outOfWorld) {
++ break;
+ }
+-
+- if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) {
+- set.add(blockposition);
+- // Paper start - prevent headless pistons from forming
+- if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) {
+- net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition);
+- if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) {
+- net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
+- set.add(blockposition.relative(direction.getOpposite()));
++ // Paper end - collision optimisations
++ BlockState iblockdata = cachedBlock.blockState; // Paper - optimise collisions
++ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
++ // Paper - collision optimisations
++
++ // Paper start - collision optimisations
++ power -= cachedBlock.resistance;
++
++ if (power > 0.0f && cachedBlock.shouldExplode == null) {
++ // note: we expect shouldBlockExplode to be pure with respect to power, as Vanilla currently is.
++ // basically, it is unused, which allows us to cache the result
++ final boolean shouldExplode = this.damageCalculator.shouldBlockExplode((Explosion)(Object)this, this.level, cachedBlock.immutablePos, cachedBlock.blockState, power);
++ cachedBlock.shouldExplode = shouldExplode ? Boolean.TRUE : Boolean.FALSE;
++ if (shouldExplode) {
++ if (this.fire || !cachedBlock.blockState.isAir()) {
++ this.toBlow.add(cachedBlock.immutablePos);
++ // Paper start - prevent headless pistons from forming
++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) {
++ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(cachedBlock.immutablePos); // Paper - optimise collisions
++ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) {
++ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING);
++ this.toBlow.add(cachedBlock.immutablePos.relative(direction.getOpposite())); // Paper - optimise collisions
++ }
++ }
++ // Paper end - prevent headless pistons from forming
+ }
+ }
+- // Paper end - prevent headless pistons from forming
+ }
+
+- d4 += d0 * 0.30000001192092896D;
+- d5 += d1 * 0.30000001192092896D;
+- d6 += d2 * 0.30000001192092896D;
+- }
++ power -= 0.22500001F;
++ currX += incX;
++ currY += incY;
++ currZ += incZ;
++ } while (power > 0.0f);
++ // Paper end - collision optimisations
+ }
+- }
+- }
+- }
+
+- this.toBlow.addAll(set);
++ // Paper - optimise collisions
+ float f2 = this.radius * 2.0F;
+
+ i = Mth.floor(this.x - (double) f2 - 1.0D);
+@@ -241,6 +522,10 @@ public class Explosion {
+ Vec3 vec3d = new Vec3(this.x, this.y, this.z);
+ Iterator iterator = list.iterator();
+
++ // Paper start - optimise collisions
++ final BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
++ // Paper end - optimise collisions
++
+ while (iterator.hasNext()) {
+ Entity entity = (Entity) iterator.next();
+
+@@ -257,6 +542,7 @@ public class Explosion {
+ d8 /= d11;
+ d9 /= d11;
+ d10 /= d11;
++ final double seenFraction; // Paper - optimise collisions
+ if (this.damageCalculator.shouldDamageEntity(this, entity)) {
+ // CraftBukkit start
+
+@@ -272,6 +558,8 @@ public class Explosion {
+
+ entity.lastDamageCancelled = false;
+
++ seenFraction = (double)this.getBlockDensity(vec3d, entity, blockCache, blockPos); // Paper - optimise collisions
++
+ if (entity instanceof EnderDragon) {
+ for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
+ // Calculate damage separately for each EntityComplexPart
+@@ -280,16 +568,21 @@ public class Explosion {
+ }
+ }
+ } else {
+- entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity));
++ // Paper start - optimise collisions
++ // inline getEntityDamageAmount so that we can avoid double calling getSeenPercent, which is the MOST
++ // expensive part of this loop!!!!
++ final double factor = (1.0 - d7) * seenFraction;
++ entity.hurt(this.damageSource, (float)((factor * factor + factor) / 2.0 * 7.0 * (double)f2 + 1.0));
++ // Paper end - optimise collisions
+ }
+
+ if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
+ continue;
+ }
+ // CraftBukkit end
+- }
++ } else { seenFraction = (double)this.getBlockDensity(vec3d, entity, blockCache, blockPos); } // Paper - optimise collisions
+
+- double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity) * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions
++ double d12 = (1.0D - d7) * seenFraction * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions // Paper - optimise collisions
+ double d13;
+
+ if (entity instanceof LivingEntity) {
+@@ -327,7 +620,11 @@ public class Explosion {
+ }
+ }
+ }
+-
++ // Paper start - optimise collisions
++ this.blockCache = null;
++ this.chunkPosCache = null;
++ this.chunkCache = null;
++ // Paper end - optimise collisions
+ }
+
+ public void finalizeExplosion(boolean particles) {
+@@ -547,14 +844,14 @@ public class Explosion {
+ private BlockInteraction() {}
+ }
+ // Paper start - Optimize explosions
+- private float getBlockDensity(Vec3 vec3d, Entity entity) {
++ private float getBlockDensity(Vec3 vec3d, Entity entity, ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache, BlockPos.MutableBlockPos blockPos) { // Paper - optimise collisions
+ if (!this.level.paperConfig().environment.optimizeExplosions) {
+- return getSeenPercent(vec3d, entity);
++ return this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise collisions
+ }
+ CacheKey key = new CacheKey(this, entity.getBoundingBox());
+ Float blockDensity = this.level.explosionDensityCache.get(key);
+ if (blockDensity == null) {
+- blockDensity = getSeenPercent(vec3d, entity);
++ blockDensity = this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise collisions
+ this.level.explosionDensityCache.put(key, blockDensity);
+ }
+
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index e27d3547d1e19c137e05e6b8d075127a8bafb237..a022bbaa16054c56696a7a03e71ef042340d88ec 100644
+index e27d3547d1e19c137e05e6b8d075127a8bafb237..6559ca1b3025eae1837986d404369a9b80b988f7 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -102,7 +102,7 @@ import org.bukkit.entity.SpawnCategory;
+@@ -81,6 +81,7 @@ import net.minecraft.world.level.storage.LevelData;
+ import net.minecraft.world.level.storage.WritableLevelData;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
++import net.minecraft.world.phys.shapes.VoxelShape;
+ import net.minecraft.world.scores.Scoreboard;
+
+ // CraftBukkit start
+@@ -102,7 +103,7 @@ import org.bukkit.entity.SpawnCategory;
import org.bukkit.event.block.BlockPhysicsEvent;
// CraftBukkit end
-public abstract class Level implements LevelAccessor, AutoCloseable {
-+public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel, ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter { // Paper - rewrite chunk system
++public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel, ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter, ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel { // Paper - rewrite chunk system // Paper - optimise collisions
public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld"));
-@@ -199,6 +199,63 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -199,6 +200,439 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public abstract ResourceKey<LevelStem> getTypeKey();
@@ -28101,19 +31389,399 @@ index e27d3547d1e19c137e05e6b8d075127a8bafb237..a022bbaa16054c56696a7a03e71ef042
+ // no-op on ClientLevel
+ }
+ // Paper end - rewrite chunk system
++ // Paper start - optimise collisions
++ private final int minSection;
++ private final int maxSection;
++
++ @Override
++ public final int moonrise$getMinSection() {
++ return this.minSection;
++ }
++
++ @Override
++ public final int moonrise$getMaxSection() {
++ return this.maxSection;
++ }
++
++ /**
++ * Route to faster lookup.
++ * See {@link EntityGetter#isUnobstructed(Entity, VoxelShape)} for expected behavior
++ * @author Spottedleaf
++ */
++ @Override
++ public final boolean isUnobstructed(final Entity entity) {
++ final AABB boundingBox = entity.getBoundingBox();
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(boundingBox)) {
++ return false;
++ }
++
++ final List<Entity> entities = this.getEntities(
++ entity,
++ boundingBox.inflate(-ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON),
++ null
++ );
++
++ for (int i = 0, len = entities.size(); i < len; ++i) {
++ final Entity otherEntity = entities.get(i);
++
++ if (otherEntity.isSpectator() || otherEntity.isRemoved() || !otherEntity.blocksBuilding || otherEntity.isPassengerOfSameVehicle(entity)) {
++ continue;
++ }
++
++ return false;
++ }
++
++ return true;
++ }
++
++
++ private static net.minecraft.world.phys.BlockHitResult miss(final ClipContext clipContext) {
++ final Vec3 to = clipContext.getTo();
++ final Vec3 from = clipContext.getFrom();
++
++ return net.minecraft.world.phys.BlockHitResult.miss(to, Direction.getNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z));
++ }
++
++ private static final FluidState AIR_FLUIDSTATE = Fluids.EMPTY.defaultFluidState();
++
++ private static net.minecraft.world.phys.BlockHitResult fastClip(final Vec3 from, final Vec3 to, final Level level,
++ final ClipContext clipContext) {
++ final double adjX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.x - to.x);
++ final double adjY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.y - to.y);
++ final double adjZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.z - to.z);
++
++ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) {
++ return miss(clipContext);
++ }
++
++ final double toXAdj = to.x - adjX;
++ final double toYAdj = to.y - adjY;
++ final double toZAdj = to.z - adjZ;
++ final double fromXAdj = from.x + adjX;
++ final double fromYAdj = from.y + adjY;
++ final double fromZAdj = from.z + adjZ;
++
++ int currX = Mth.floor(fromXAdj);
++ int currY = Mth.floor(fromYAdj);
++ int currZ = Mth.floor(fromZAdj);
++
++ final BlockPos.MutableBlockPos currPos = new BlockPos.MutableBlockPos();
++
++ final double diffX = toXAdj - fromXAdj;
++ final double diffY = toYAdj - fromYAdj;
++ final double diffZ = toZAdj - fromZAdj;
++
++ final double dxDouble = Math.signum(diffX);
++ final double dyDouble = Math.signum(diffY);
++ final double dzDouble = Math.signum(diffZ);
++
++ final int dx = (int)dxDouble;
++ final int dy = (int)dyDouble;
++ final int dz = (int)dzDouble;
++
++ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX;
++ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY;
++ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ;
++
++ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj));
++ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj));
++ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj));
++
++ net.minecraft.world.level.chunk.LevelChunkSection[] lastChunk = null;
++ net.minecraft.world.level.chunk.PalettedContainer<net.minecraft.world.level.block.state.BlockState> lastSection = null;
++ int lastChunkX = Integer.MIN_VALUE;
++ int lastChunkY = Integer.MIN_VALUE;
++ int lastChunkZ = Integer.MIN_VALUE;
++
++ final int minSection = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)level).moonrise$getMinSection();
++
++ for (;;) {
++ currPos.set(currX, currY, currZ);
++
++ final int newChunkX = currX >> 4;
++ final int newChunkY = currY >> 4;
++ final int newChunkZ = currZ >> 4;
++
++ final int chunkDiff = ((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ));
++ final int chunkYDiff = newChunkY ^ lastChunkY;
++
++ if ((chunkDiff | chunkYDiff) != 0) {
++ if (chunkDiff != 0) {
++ lastChunk = level.getChunk(newChunkX, newChunkZ).getSections();
++ }
++ final int sectionY = newChunkY - minSection;
++ lastSection = sectionY >= 0 && sectionY < lastChunk.length ? lastChunk[sectionY].states : null;
++
++ lastChunkX = newChunkX;
++ lastChunkY = newChunkY;
++ lastChunkZ = newChunkZ;
++ }
++
++ final BlockState blockState;
++ if (lastSection != null && !(blockState = lastSection.get((currX & 15) | ((currZ & 15) << 4) | ((currY & 15) << (4+4)))).isAir()) {
++ final VoxelShape blockCollision = clipContext.getBlockShape(blockState, level, currPos);
++
++ final net.minecraft.world.phys.BlockHitResult blockHit = blockCollision.isEmpty() ? null : level.clipWithInteractionOverride(from, to, currPos, blockCollision, blockState);
++
++ final VoxelShape fluidCollision;
++ final FluidState fluidState;
++ if (clipContext.fluid != ClipContext.Fluid.NONE && (fluidState = blockState.getFluidState()) != AIR_FLUIDSTATE) {
++ fluidCollision = clipContext.getFluidShape(fluidState, level, currPos);
++
++ final net.minecraft.world.phys.BlockHitResult fluidHit = fluidCollision.clip(from, to, currPos);
++
++ if (fluidHit != null) {
++ if (blockHit == null) {
++ return fluidHit;
++ }
++
++ return from.distanceToSqr(blockHit.getLocation()) <= from.distanceToSqr(fluidHit.getLocation()) ? blockHit : fluidHit;
++ }
++ }
++
++ if (blockHit != null) {
++ return blockHit;
++ }
++ } // else: usually fall here
++
++ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) {
++ return miss(clipContext);
++ }
++
++ // inc the smallest normalized coordinate
++
++ if (normalizedCurrX < normalizedCurrY) {
++ if (normalizedCurrX < normalizedCurrZ) {
++ currX += dx;
++ normalizedCurrX += normalizedDiffX;
++ } else {
++ // x < y && x >= z <--> z < y && z <= x
++ currZ += dz;
++ normalizedCurrZ += normalizedDiffZ;
++ }
++ } else if (normalizedCurrY < normalizedCurrZ) {
++ // y <= x && y < z
++ currY += dy;
++ normalizedCurrY += normalizedDiffY;
++ } else {
++ // y <= x && z <= y <--> z <= y && z <= x
++ currZ += dz;
++ normalizedCurrZ += normalizedDiffZ;
++ }
++ }
++ }
++
++ /**
++ * @reason Route to optimized call
++ * @author Spottedleaf
++ */
++ @Override
++ public final net.minecraft.world.phys.BlockHitResult clip(final ClipContext clipContext) {
++ // can only do this in this class, as not everything that implements BlockGetter can retrieve chunks
++ return fastClip(clipContext.getFrom(), clipContext.getTo(), (Level)(Object)this, clipContext);
++ }
++
++ /**
++ * @reason Route to faster logic
++ * @author Spottedleaf
++ */
++ @Override
++ public final boolean collidesWithSuffocatingBlock(final Entity entity, final AABB box) {
++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder((Level)(Object)this, entity, box, null, null,
++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY,
++ (final BlockState state, final BlockPos pos) -> {
++ return state.isSuffocating((Level)(Object)Level.this, pos);
++ }
++ );
++ }
++
++ private static VoxelShape inflateAABBToVoxel(final AABB aabb, final double x, final double y, final double z) {
++ return net.minecraft.world.phys.shapes.Shapes.create(
++ aabb.minX - x,
++ aabb.minY - y,
++ aabb.minZ - z,
++
++ aabb.maxX + x,
++ aabb.maxY + y,
++ aabb.maxZ + z
++ );
++ }
++
++ /**
++ * @reason Use optimised OR operator join strategy, avoid streams
++ * @author Spottedleaf
++ */
++ @Override
++ public final java.util.Optional<net.minecraft.world.phys.Vec3> findFreePosition(final Entity entity, final VoxelShape boundsShape, final Vec3 fromPosition,
++ final double rangeX, final double rangeY, final double rangeZ) {
++ if (boundsShape.isEmpty()) {
++ return java.util.Optional.empty();
++ }
++
++ final double expandByX = rangeX * 0.5;
++ final double expandByY = rangeY * 0.5;
++ final double expandByZ = rangeZ * 0.5;
++
++ // note: it is useless to look at shapes outside of range / 2.0
++ final AABB collectionVolume = boundsShape.bounds().inflate(expandByX, expandByY, expandByZ);
++
++ final List<AABB> aabbs = new java.util.ArrayList<>();
++ final List<VoxelShape> voxels = new java.util.ArrayList<>();
++
++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder(
++ (Level)(Object)this, entity, collectionVolume, voxels, aabbs,
++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER,
++ null
++ );
++
++ // push voxels into aabbs
++ for (int i = 0, len = voxels.size(); i < len; ++i) {
++ aabbs.addAll(voxels.get(i).toAabbs());
++ }
++
++ // expand AABBs
++ final VoxelShape first = aabbs.isEmpty() ? net.minecraft.world.phys.shapes.Shapes.empty() : inflateAABBToVoxel(aabbs.get(0), expandByX, expandByY, expandByZ);
++ final VoxelShape[] rest = new VoxelShape[Math.max(0, aabbs.size() - 1)];
++
++ for (int i = 1, len = aabbs.size(); i < len; ++i) {
++ rest[i - 1] = inflateAABBToVoxel(aabbs.get(i), expandByX, expandByY, expandByZ);
++ }
++
++ // use optimized implementation of ORing the shapes together
++ final VoxelShape joined = net.minecraft.world.phys.shapes.Shapes.or(first, rest);
++
++ // find free space
++ // can use unoptimized join here (instead of join()), as closestPointTo uses toAabbs()
++ final VoxelShape freeSpace = net.minecraft.world.phys.shapes.Shapes.joinUnoptimized(boundsShape, joined, net.minecraft.world.phys.shapes.BooleanOp.ONLY_FIRST);
++
++ return freeSpace.closestPointTo(fromPosition);
++ }
++
++ /**
++ * @reason Route to faster logic
++ * @author Spottedleaf
++ */
++ @Override
++ public final java.util.Optional<net.minecraft.core.BlockPos> findSupportingBlock(final Entity entity, final AABB aabb) {
++ final int minBlockX = Mth.floor(aabb.minX - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1;
++ final int maxBlockX = Mth.floor(aabb.maxX + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1;
++
++ final int minBlockY = Mth.floor(aabb.minY - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1;
++ final int maxBlockY = Mth.floor(aabb.maxY + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1;
++
++ final int minBlockZ = Mth.floor(aabb.minZ - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1;
++ final int maxBlockZ = Mth.floor(aabb.maxZ + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1;
++
++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext collisionContext = null;
++
++ final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
++ BlockPos selected = null;
++ double selectedDistance = Double.MAX_VALUE;
++
++ final Vec3 entityPos = entity.position();
++
++ LevelChunk lastChunk = null;
++ int lastChunkX = Integer.MIN_VALUE;
++ int lastChunkZ = Integer.MIN_VALUE;
++
++ final ChunkSource chunkSource = this.getChunkSource();
++
++ for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) {
++ pos.setZ(currZ);
++ for (int currX = minBlockX; currX <= maxBlockX; ++currX) {
++ pos.setX(currX);
++
++ final int newChunkX = currX >> 4;
++ final int newChunkZ = currZ >> 4;
++
++ if (((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)) != 0) {
++ lastChunk = (LevelChunk)chunkSource.getChunk(newChunkX, newChunkZ, ChunkStatus.FULL, false);
++ }
++
++ if (lastChunk == null) {
++ continue;
++ }
++ for (int currY = minBlockY; currY <= maxBlockY; ++currY) {
++ int edgeCount = ((currX == minBlockX || currX == maxBlockX) ? 1 : 0) +
++ ((currY == minBlockY || currY == maxBlockY) ? 1 : 0) +
++ ((currZ == minBlockZ || currZ == maxBlockZ) ? 1 : 0);
++ if (edgeCount == 3) {
++ continue;
++ }
++
++ pos.setY(currY);
++
++ final double distance = pos.distToCenterSqr(entityPos);
++ if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(pos) >= 0)) {
++ continue;
++ }
++
++ final BlockState state = ((ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk)lastChunk).moonrise$getBlock(currX, currY, currZ);
++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape()) {
++ continue;
++ }
++
++ VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$getConstantCollisionShape();
++
++ if ((edgeCount != 1 || state.hasLargeCollisionShape()) && (edgeCount != 2 || state.getBlock() == Blocks.MOVING_PISTON)) {
++ if (collisionContext == null) {
++ collisionContext = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity);
++ }
++
++ if (blockCollision == null) {
++ blockCollision = state.getCollisionShape((Level)(Object)this, pos, collisionContext);
++ }
++
++ if (blockCollision.isEmpty()) {
++ continue;
++ }
++
++ // avoid VoxelShape#move by shifting the entity collision shape instead
++ final AABB shiftedAABB = aabb.move(-(double)currX, -(double)currY, -(double)currZ);
++
++ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation();
++ if (singleAABB != null) {
++ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) {
++ continue;
++ }
++
++ selected = pos.immutable();
++ selectedDistance = distance;
++ continue;
++ }
++
++ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) {
++ continue;
++ }
++
++ selected = pos.immutable();
++ selectedDistance = distance;
++ continue;
++ }
++ }
++ }
++ }
++
++ return java.util.Optional.ofNullable(selected);
++ }
++ // Paper end - optimise collisions
+
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator) { // Paper - create paper world config
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
-@@ -281,6 +338,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -281,6 +715,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
this.timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings
this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
+ this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system
++ // Paper start - optimise collisions
++ this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this);
++ this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this);
++ // Paper end - optimise collisions
}
// Paper start - Cancel hit for vanished players
-@@ -549,7 +607,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -549,7 +988,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
}
@@ -28122,7 +31790,7 @@ index e27d3547d1e19c137e05e6b8d075127a8bafb237..a022bbaa16054c56696a7a03e71ef042
this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
}
-@@ -813,6 +871,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -813,6 +1252,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
// Iterator<TickingBlockEntity> iterator = this.blockEntityTickers.iterator();
boolean flag = this.tickRateManager().runsNormally();
@@ -28131,7 +31799,7 @@ index e27d3547d1e19c137e05e6b8d075127a8bafb237..a022bbaa16054c56696a7a03e71ef042
int tilesThisCycle = 0;
var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<TickingBlockEntity>(); // Paper - Fix MC-117075; use removeAll
toRemove.add(null); // Paper - Fix MC-117075
-@@ -828,6 +888,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -828,6 +1269,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
// Spigot end
} else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
tickingblockentity.tick();
@@ -28143,7 +31811,7 @@ index e27d3547d1e19c137e05e6b8d075127a8bafb237..a022bbaa16054c56696a7a03e71ef042
}
}
this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
-@@ -850,6 +915,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -850,12 +1296,20 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
// Paper end - Prevent block entity and entity crashes
}
@@ -28151,7 +31819,21 @@ index e27d3547d1e19c137e05e6b8d075127a8bafb237..a022bbaa16054c56696a7a03e71ef042
}
// Paper start - Option to prevent armor stands from doing entity lookups
@Override
-@@ -949,7 +1015,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+ public boolean noCollision(@Nullable Entity entity, AABB box) {
+ if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false;
+- return LevelAccessor.super.noCollision(entity, box);
++ // Paper start - optimise collisions
++ final int flags = entity == null ? (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER | ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY) : ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY;
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder((Level)(Object)this, entity, box, null, null, flags, null)) {
++ return false;
++ }
++
++ return !ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getEntityHardCollisions((Level)(Object)this, entity, box, null, flags, null);
++ // Paper end - optimise collisions
+ }
+ // Paper end - Option to prevent armor stands from doing entity lookups
+
+@@ -949,7 +1403,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
// Paper end - Perf: Optimize capturedTileEntities lookup
// CraftBukkit end
@@ -28160,19 +31842,19 @@ index e27d3547d1e19c137e05e6b8d075127a8bafb237..a022bbaa16054c56696a7a03e71ef042
}
public void setBlockEntity(BlockEntity blockEntity) {
-@@ -1039,28 +1105,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1039,28 +1493,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
@Override
public List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
this.getProfiler().incrementCounter("getEntities");
- List<Entity> list = Lists.newArrayList();
-+ // Paper start - rewrite chunk system
-+ final List<Entity> ret = new java.util.ArrayList<>();
-
+-
- this.getEntities().get(box, (entity1) -> {
- if (entity1 != except && predicate.test(entity1)) {
- list.add(entity1);
- }
--
++ // Paper start - rewrite chunk system
++ final List<Entity> ret = new java.util.ArrayList<>();
+
- if (entity1 instanceof EnderDragon) {
- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities();
- int i = aentitycomplexpart.length;
@@ -28194,7 +31876,7 @@ index e27d3547d1e19c137e05e6b8d075127a8bafb237..a022bbaa16054c56696a7a03e71ef042
}
@Override
-@@ -1075,36 +1126,77 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -1075,36 +1514,77 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
this.getEntities(filter, box, predicate, result, Integer.MAX_VALUE);
}
@@ -28317,8 +31999,21 @@ index a0ae26d6197e1069ca09982b4f8b706c55ae8491..1a4dc4b2561dbaf01246b4fb46266b1a
@Nullable
ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create);
+diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
+index 45704653310efe9cb755a644674b54b8722c2c84..2ff21c5a9f7bcecb57ffaaafecc1462ca5456e48 100644
+--- a/src/main/java/net/minecraft/world/level/block/Block.java
++++ b/src/main/java/net/minecraft/world/level/block/Block.java
+@@ -279,7 +279,7 @@ public class Block extends BlockBehaviour implements ItemLike {
+ }
+
+ public static boolean isShapeFullBlock(VoxelShape shape) {
+- return (Boolean) Block.SHAPE_FULL_BLOCK_CACHE.getUnchecked(shape);
++ return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock(); // Paper - optimise collisions
+ }
+
+ public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {}
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
-index 6c4a339be29bb9c07b741a1ca12de2217c8687ba..a768b07dae4bf75b68e3bc1d3de4b68fc7d23842 100644
+index 6c4a339be29bb9c07b741a1ca12de2217c8687ba..0f289d8f9bda2fb2ca2cd2dfd667a975529b3e4c 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -762,7 +762,7 @@ public abstract class BlockBehaviour implements FeatureElement {
@@ -28326,11 +32021,11 @@ index 6c4a339be29bb9c07b741a1ca12de2217c8687ba..a768b07dae4bf75b68e3bc1d3de4b68f
}
- public abstract static class BlockStateBase extends StateHolder<Block, BlockState> {
-+ public abstract static class BlockStateBase extends StateHolder<Block, BlockState> implements ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState { // Paper - rewrite chunk system
++ public abstract static class BlockStateBase extends StateHolder<Block, BlockState> implements ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState, ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState { // Paper - rewrite chunk system // Paper - optimise collisions
private final int lightEmission;
private final boolean useShapeForLightOcclusion;
-@@ -794,6 +794,21 @@ public abstract class BlockBehaviour implements FeatureElement {
+@@ -794,6 +794,76 @@ public abstract class BlockBehaviour implements FeatureElement {
private FluidState fluidState;
private boolean isRandomlyTicking;
@@ -28348,11 +32043,66 @@ index 6c4a339be29bb9c07b741a1ca12de2217c8687ba..a768b07dae4bf75b68e3bc1d3de4b68f
+ return this.opacityIfCached;
+ }
+ // Paper end - rewrite chunk system
++ // Paper start - optimise collisions
++ private static final int RANDOM_OFFSET = 704237939;
++ private static final Direction[] DIRECTIONS_CACHED = Direction.values();
++ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger();
++ private final int id1 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET);
++ private final int id2 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET);
++ private boolean occludesFullBlock;
++ private boolean emptyCollisionShape;
++ private VoxelShape constantCollisionShape;
++ private AABB constantAABBCollision;
++
++ private static void initCaches(final VoxelShape shape) {
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock();
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$occludesFullBlock();
++ shape.toAabbs();
++ if (!shape.isEmpty()) {
++ shape.bounds();
++ }
++ }
++
++ @Override
++ public final boolean moonrise$hasCache() {
++ return this.cache != null;
++ }
++
++ @Override
++ public final boolean moonrise$occludesFullBlock() {
++ return this.occludesFullBlock;
++ }
++
++ @Override
++ public final boolean moonrise$emptyCollisionShape() {
++ return this.emptyCollisionShape;
++ }
++
++ @Override
++ public final int moonrise$uniqueId1() {
++ return this.id1;
++ }
++
++ @Override
++ public final int moonrise$uniqueId2() {
++ return this.id2;
++ }
++
++ @Override
++ public final VoxelShape moonrise$getConstantCollisionShape() {
++ return this.constantCollisionShape;
++ }
++
++ @Override
++ public final AABB moonrise$getConstantCollisionAABB() {
++ return this.constantAABBCollision;
++ }
++ // Paper end - optimise collisions
+
protected BlockStateBase(Block block, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<BlockState> codec) {
super(block, propertyMap, codec);
this.fluidState = Fluids.EMPTY.defaultFluidState();
-@@ -864,6 +879,10 @@ public abstract class BlockBehaviour implements FeatureElement {
+@@ -864,6 +934,43 @@ public abstract class BlockBehaviour implements FeatureElement {
this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
this.legacySolid = this.calculateSolid();
@@ -28360,11 +32110,44 @@ index 6c4a339be29bb9c07b741a1ca12de2217c8687ba..a768b07dae4bf75b68e3bc1d3de4b68f
+ this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion;
+ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque ? -1 : this.cache.lightBlock;
+ // Paper end - rewrite chunk system
++ // Paper start - optimise collisions
++ if (this.cache != null) {
++ final VoxelShape collisionShape = this.cache.collisionShape;
++ try {
++ this.constantCollisionShape = this.getCollisionShape(null, null, null);
++ this.constantAABBCollision = this.constantCollisionShape == null ? null : ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this.constantCollisionShape).moonrise$getSingleAABBRepresentation();
++ } catch (final Throwable throwable) {
++ this.constantCollisionShape = null;
++ this.constantAABBCollision = null;
++ }
++ this.occludesFullBlock = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock();
++ this.emptyCollisionShape = collisionShape.isEmpty();
++ // init caches
++ initCaches(collisionShape);
++ if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) {
++ for (final Direction direction : DIRECTIONS_CACHED) {
++ // initialise the directional face shape cache as well
++ final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction);
++ initCaches(shape);
++ }
++ }
++ if (this.cache.occlusionShapes != null) {
++ for (final VoxelShape shape : this.cache.occlusionShapes) {
++ initCaches(shape);
++ }
++ }
++ } else {
++ this.occludesFullBlock = false;
++ this.emptyCollisionShape = false;
++ this.constantCollisionShape = null;
++ this.constantAABBCollision = null;
++ }
++ // Paper end - optimise collisions
}
public Block getBlock() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-index db4d95ce98eb1490d5306d1f74b282d27264871a..fb7bdf43fdc4d816b1c1f1f063bc170561c9544f 100644
+index db4d95ce98eb1490d5306d1f74b282d27264871a..fba548c4e6d589323ec3ea5f6b269a6fe9faf6a1 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -57,7 +57,7 @@ import net.minecraft.world.ticks.SerializableTickContainer;
@@ -28385,7 +32168,7 @@ index db4d95ce98eb1490d5306d1f74b282d27264871a..fb7bdf43fdc4d816b1c1f1f063bc1705
private final Map<Structure, StructureStart> structureStarts = Maps.newHashMap();
private final Map<Structure, LongSet> structuresRefences = Maps.newHashMap();
protected final Map<BlockPos, CompoundTag> pendingBlockEntities = Maps.newHashMap();
-@@ -90,6 +90,53 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
+@@ -90,6 +90,57 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY);
// CraftBukkit end
@@ -28435,11 +32218,15 @@ index db4d95ce98eb1490d5306d1f74b282d27264871a..fb7bdf43fdc4d816b1c1f1f063bc1705
+ this.blockEmptinessMap = emptinessMap;
+ }
+ // Paper end - rewrite chunk system
++ // Paper start - get block chunk optimisation
++ private final int minSection;
++ private final int maxSection;
++ // Paper end - get block chunk optimisation
+
public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups
this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
-@@ -99,7 +146,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
+@@ -99,7 +150,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
this.inhabitedTime = inhabitedTime;
this.postProcessing = new ShortList[heightLimitView.getSectionsCount()];
this.blendingData = blendingData;
@@ -28448,7 +32235,7 @@ index db4d95ce98eb1490d5306d1f74b282d27264871a..fb7bdf43fdc4d816b1c1f1f063bc1705
if (sectionArray != null) {
if (this.sections.length == sectionArray.length) {
System.arraycopy(sectionArray, 0, this.sections, 0, this.sections.length);
-@@ -111,6 +158,12 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
+@@ -111,6 +162,16 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
ChunkAccess.replaceMissingSections(biomeRegistry, this.sections);
// CraftBukkit start
this.biomeRegistry = biomeRegistry;
@@ -28458,10 +32245,52 @@ index db4d95ce98eb1490d5306d1f74b282d27264871a..fb7bdf43fdc4d816b1c1f1f063bc1705
+ this.starlight$setSkyNibbles(ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine.getFilledEmptyLight(heightLimitView));
+ }
+ // Paper end - rewrite chunk system
++ // Paper start - get block chunk optimisation
++ this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(levelHeightAccessor);
++ this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(levelHeightAccessor);
++ // Paper end - get block chunk optimisation
}
public final Registry<Biome> biomeRegistry;
// CraftBukkit end
-@@ -514,12 +567,12 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
+@@ -442,22 +503,22 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
+
+ @Override
+ public Holder<Biome> getNoiseBiome(int biomeX, int biomeY, int biomeZ) {
+- try {
+- int l = QuartPos.fromBlock(this.getMinBuildHeight());
+- int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1;
+- int j1 = Mth.clamp(biomeY, l, i1);
+- int k1 = this.getSectionIndex(QuartPos.toBlock(j1));
+-
+- return this.sections[k1].getNoiseBiome(biomeX & 3, j1 & 3, biomeZ & 3);
+- } catch (Throwable throwable) {
+- CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting biome");
+- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Biome being got");
+-
+- crashreportsystemdetails.setDetail("Location", () -> {
+- return CrashReportCategory.formatLocation(this, biomeX, biomeY, biomeZ);
+- });
+- throw new ReportedException(crashreport);
++ // Paper start - get block chunk optimisation
++ int sectionY = (biomeY >> 2) - this.minSection;
++ int rel = biomeY & 3;
++
++ final LevelChunkSection[] sections = this.sections;
++
++ if (sectionY < 0) {
++ sectionY = 0;
++ rel = 0;
++ } else if (sectionY >= sections.length) {
++ sectionY = sections.length - 1;
++ rel = 3;
+ }
++
++ return sections[sectionY].getNoiseBiome(biomeX & 3, rel, biomeZ & 3);
++ // Paper end - get block chunk optimisation
+ }
+
+ // CraftBukkit start
+@@ -514,12 +575,12 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
}
public void initializeLightSources() {
@@ -28615,7 +32444,7 @@ index 365074be989aa4a178114fd5e9810f1a68640196..4af698930712389881601069a921f054
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a17860989c 100644
+index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..214bb6759d2670042d87b2fd0aae41df600a95ee 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -53,7 +53,7 @@ import net.minecraft.world.ticks.LevelChunkTicks;
@@ -28623,11 +32452,26 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
import org.slf4j.Logger;
-public class LevelChunk extends ChunkAccess {
-+public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk { // Paper - rewrite chunk system
++public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk, ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk { // Paper - rewrite chunk system // Paper - get block chunk optimisation
static final Logger LOGGER = LogUtils.getLogger();
private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() {
-@@ -119,6 +119,14 @@ public class LevelChunk extends ChunkAccess {
+@@ -109,6 +109,14 @@ public class LevelChunk extends ChunkAccess {
+ this.postLoad = entityLoader;
+ this.blockTicks = blockTickScheduler;
+ this.fluidTicks = fluidTickScheduler;
++ // Paper start - get block chunk optimisation
++ this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level);
++ this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(level);
++
++ final boolean empty = ((Object)this instanceof EmptyLevelChunk);
++ this.debug = !empty && this.level.isDebug();
++ this.defaultBlockState = empty ? VOID_AIR_BLOCKSTATE : AIR_BLOCKSTATE;
++ // Paper end - get block chunk optimisation
+ }
+
+ // CraftBukkit start
+@@ -119,6 +127,28 @@ public class LevelChunk extends ChunkAccess {
// Paper start
boolean loadedTicketLevel;
// Paper end
@@ -28639,10 +32483,24 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
+ return this.postProcessingDone;
+ }
+ // Paper end - rewrite chunk system
++ // Paper start - get block chunk optimisation
++ private static final BlockState AIR_BLOCKSTATE = Blocks.AIR.defaultBlockState();
++ private static final FluidState AIR_FLUIDSTATE = Fluids.EMPTY.defaultFluidState();
++ private static final BlockState VOID_AIR_BLOCKSTATE = Blocks.VOID_AIR.defaultBlockState();
++ private final int minSection;
++ private final int maxSection;
++ private final boolean debug;
++ private final BlockState defaultBlockState;
++
++ @Override
++ public final BlockState moonrise$getBlock(final int x, final int y, final int z) {
++ return this.getBlockStateFinal(x, y, z);
++ }
++ // Paper end - get block chunk optimisation
public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
-@@ -148,13 +156,19 @@ public class LevelChunk extends ChunkAccess {
+@@ -148,13 +178,19 @@ public class LevelChunk extends ChunkAccess {
}
}
@@ -28663,7 +32521,7 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
}
@Override
-@@ -337,7 +351,7 @@ public class LevelChunk extends ChunkAccess {
+@@ -337,7 +373,7 @@ public class LevelChunk extends ChunkAccess {
ProfilerFiller gameprofilerfiller = this.level.getProfiler();
gameprofilerfiller.push("updateSkyLightSources");
@@ -28672,7 +32530,7 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
gameprofilerfiller.popPush("queueCheckLight");
this.level.getChunkSource().getLightEngine().checkBlock(blockposition);
gameprofilerfiller.pop();
-@@ -597,11 +611,12 @@ public class LevelChunk extends ChunkAccess {
+@@ -597,11 +633,12 @@ public class LevelChunk extends ChunkAccess {
// CraftBukkit start
public void loadCallback() {
@@ -28686,7 +32544,7 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
if (server != null) {
/*
* If it's a new world, the first few chunks are generated inside
-@@ -610,6 +625,7 @@ public class LevelChunk extends ChunkAccess {
+@@ -610,6 +647,7 @@ public class LevelChunk extends ChunkAccess {
*/
org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
@@ -28694,7 +32552,7 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
if (this.needsDecoration) {
try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper
-@@ -638,13 +654,15 @@ public class LevelChunk extends ChunkAccess {
+@@ -638,13 +676,15 @@ public class LevelChunk extends ChunkAccess {
}
public void unloadCallback() {
@@ -28712,7 +32570,7 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
// Paper start
this.loadedTicketLevel = false;
// Paper end
-@@ -652,8 +670,27 @@ public class LevelChunk extends ChunkAccess {
+@@ -652,8 +692,27 @@ public class LevelChunk extends ChunkAccess {
@Override
public boolean isUnsaved() {
@@ -28741,7 +32599,7 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
// CraftBukkit end
public boolean isEmpty() {
-@@ -759,6 +796,7 @@ public class LevelChunk extends ChunkAccess {
+@@ -759,6 +818,7 @@ public class LevelChunk extends ChunkAccess {
this.pendingBlockEntities.clear();
this.upgradeData.upgrade(this);
@@ -28749,10 +32607,153 @@ index 602ad80c2b93d320bf2a25832d25a58cb8c72e4b..443e5e1b1c0e7c93f61c1905c78c29a1
}
@Nullable
+diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+index 90d1c3e23e753c29660f7d993b3c90ac022941c3..1f767bb9342611a66c1be79ee23df39a4cad1102 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+@@ -13,7 +13,7 @@ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.material.FluidState;
+
+-public class LevelChunkSection {
++public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevelChunkSection { // Paper - optimise collisions
+
+ public static final int SECTION_WIDTH = 16;
+ public static final int SECTION_HEIGHT = 16;
+@@ -26,6 +26,15 @@ public class LevelChunkSection {
+ // CraftBukkit start - read/write
+ private PalettedContainer<Holder<Biome>> biomes;
+
++ // Paper start - optimise collisions
++ private int specialCollidingBlocks;
++
++ @Override
++ public final int moonrise$getSpecialCollidingBlocks() {
++ return this.specialCollidingBlocks;
++ }
++ // Paper end - optimise collisions
++
+ public LevelChunkSection(PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) {
+ // CraftBukkit end
+ this.states = datapaletteblock;
+@@ -92,6 +101,15 @@ public class LevelChunkSection {
+ ++this.tickingFluidCount;
+ }
+
++ // Paper start - optimise collisions
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) {
++ ++this.specialCollidingBlocks;
++ }
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(iblockdata1)) {
++ --this.specialCollidingBlocks;
++ }
++ // Paper end - optimise collisions
++
+ return iblockdata1;
+ }
+
+@@ -112,40 +130,58 @@ public class LevelChunkSection {
+ }
+
+ public void recalcBlockCounts() {
+- class a implements PalettedContainer.CountConsumer<BlockState> {
++ // Paper start - optimise collisions
++ // reset, then recalculate
++ this.nonEmptyBlockCount = (short)0;
++ this.tickingBlockCount = (short)0;
++ this.tickingFluidCount = (short)0;
++ this.specialCollidingBlocks = (short)0;
++
++ if (this.maybeHas((final BlockState state) -> !state.isAir())) {
++ final PalettedContainer.Data<BlockState> data = this.states.data;
++ final Palette<BlockState> palette = data.palette();
++ final int paletteSize = palette.getSize();
++ final net.minecraft.util.BitStorage storage = data.storage();
++
++ final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap counts = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(paletteSize);
++ if (paletteSize == 1) {
++ counts.addTo(0, storage.getSize());
++ } else {
++ storage.getAll((final int paletteIdx) -> {
++ counts.addTo(paletteIdx, 1);
++ });
++ }
+
+- public int nonEmptyBlockCount;
+- public int tickingBlockCount;
+- public int tickingFluidCount;
++ for (final java.util.Iterator<it.unimi.dsi.fastutil.ints.Int2IntMap.Entry> iterator = counts.int2IntEntrySet().fastIterator(); iterator.hasNext();) {
++ final it.unimi.dsi.fastutil.ints.Int2IntMap.Entry entry = iterator.next();
++ final int paletteIdx = entry.getIntKey();
++ final int paletteCount = entry.getIntValue();
+
+- a(final LevelChunkSection chunksection) {}
++ final BlockState state = palette.valueFor(paletteIdx);
+
+- public void accept(BlockState iblockdata, int i) {
+- FluidState fluid = iblockdata.getFluidState();
++ if (state.isAir()) {
++ continue;
++ }
+
+- if (!iblockdata.isAir()) {
+- this.nonEmptyBlockCount += i;
+- if (iblockdata.isRandomlyTicking()) {
+- this.tickingBlockCount += i;
+- }
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) {
++ this.specialCollidingBlocks += paletteCount;
+ }
++ this.nonEmptyBlockCount += paletteCount;
++ if (state.isRandomlyTicking()) {
++ this.tickingBlockCount += paletteCount;
++ }
++
++ final FluidState fluid = state.getFluidState();
+
+ if (!fluid.isEmpty()) {
+- this.nonEmptyBlockCount += i;
++ //this.nonEmptyBlockCount += count; // fix vanilla bug: make non empty block count correct
+ if (fluid.isRandomlyTicking()) {
+- this.tickingFluidCount += i;
++ this.tickingFluidCount += paletteCount;
+ }
+ }
+-
+ }
+ }
+-
+- a a0 = new a(this);
+-
+- this.states.count(a0);
+- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount;
+- this.tickingBlockCount = (short) a0.tickingBlockCount;
+- this.tickingFluidCount = (short) a0.tickingFluidCount;
++ // Paper end - optimise collisions
+ }
+
+ public PalettedContainer<BlockState> getStates() {
+@@ -163,6 +199,7 @@ public class LevelChunkSection {
+
+ datapaletteblock.read(buf);
+ this.biomes = datapaletteblock;
++ this.recalcBlockCounts(); // Paper - optimise collisions
+ }
+
+ public void readBiomes(FriendlyByteBuf buf) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-index 2fa0097a9374a89177e4f1068d1bfed30b8ff122..fa9df6ebcd90d4e9e5836a37212b1f60665783b1 100644
+index 2fa0097a9374a89177e4f1068d1bfed30b8ff122..339cac6b34b9f2f53852cfcee821bec9e0286c50 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+@@ -28,7 +28,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+ private static final int MIN_PALETTE_BITS = 0;
+ private final PaletteResize<T> dummyPaletteResize = (newSize, added) -> 0;
+ public final IdMap<T> registry;
+- private volatile PalettedContainer.Data<T> data;
++ public volatile PalettedContainer.Data<T> data; // Paper - optimise collisions - public
+ private final PalettedContainer.Strategy strategy;
+ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
+
@@ -155,7 +155,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
return this.get(this.strategy.getIndex(x, y, z));
}
@@ -30032,6 +34033,1531 @@ index 82e4fad11121167445df97060fb717fa86191297..b3e2bb9245be1bb2f587117b0f6016cb
}
public int getLightSectionCount() {
+diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
+index c8f7c43134e7c51ce8af5b3c1a28c11db67715a2..29123f3a2f211c08d1a9ccf62ca9bc9822f90111 100644
+--- a/src/main/java/net/minecraft/world/phys/AABB.java
++++ b/src/main/java/net/minecraft/world/phys/AABB.java
+@@ -326,7 +326,7 @@ public class AABB {
+ }
+
+ @Nullable
+- private static Direction getDirection(
++ public static Direction getDirection( // Paper - optimise collisions - public
+ AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ
+ ) {
+ if (deltaX > 1.0E-7) {
+diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
+index 4fee67f7214b464b9e09862778e3ef187fcb8b72..31a54af04ab072a433d6df9fe37beb12243fea80 100644
+--- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
++++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
+@@ -20,7 +20,7 @@ public class ArrayVoxelShape extends VoxelShape {
+ );
+ }
+
+- ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) {
++ public ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { // Paper - optimise collisions - public
+ super(shape);
+ int i = shape.getXSize() + 1;
+ int j = shape.getYSize() + 1;
+@@ -34,6 +34,7 @@ public class ArrayVoxelShape extends VoxelShape {
+ new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")
+ );
+ }
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this).moonrise$initCache(); // Paper - optimise collisions
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
+index e8f3307727e7e3da9a7629cafc6e1ee53790b75d..97ef481156ec5d821779f126ab98a8f28cbaf30b 100644
+--- a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
++++ b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
+@@ -4,13 +4,13 @@ import java.util.BitSet;
+ import net.minecraft.core.Direction;
+
+ public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape {
+- private final BitSet storage;
+- private int xMin;
+- private int yMin;
+- private int zMin;
+- private int xMax;
+- private int yMax;
+- private int zMax;
++ public final BitSet storage; // Paper - optimise collisions - public
++ public int xMin; // Paper - optimise collisions - public
++ public int yMin; // Paper - optimise collisions - public
++ public int zMin; // Paper - optimise collisions - public
++ public int xMax; // Paper - optimise collisions - public
++ public int yMax; // Paper - optimise collisions - public
++ public int zMax; // Paper - optimise collisions - public
+
+ public BitSetDiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) {
+ super(sizeX, sizeY, sizeZ);
+@@ -150,47 +150,109 @@ public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape {
+ return bitSetDiscreteVoxelShape;
+ }
+
+- protected static void forAllBoxes(DiscreteVoxelShape voxelSet, DiscreteVoxelShape.IntLineConsumer callback, boolean coalesce) {
+- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = new BitSetDiscreteVoxelShape(voxelSet);
++ // Paper start - optimise collisions
++ public static void forAllBoxes(final DiscreteVoxelShape shape, final DiscreteVoxelShape.IntLineConsumer consumer, final boolean mergeAdjacent) {
++ // Paper - remove debug
++ // called with the shape of a VoxelShape, so we can expect the cache to exist
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cache = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape) shape).moonrise$getOrCreateCachedShapeData();
++
++ final int sizeX = cache.sizeX();
++ final int sizeY = cache.sizeY();
++ final int sizeZ = cache.sizeZ();
++
++ int indexX;
++ int indexY = 0;
++ int indexZ;
++
++ int incY = sizeZ;
++ int incX = sizeZ * sizeY;
++
++ long[] bitset = cache.voxelSet();
++
++ // index = z + y*size_z + x*(size_z*size_y)
++
++ if (!mergeAdjacent) {
++ // due to the odd selection of loop order (which does affect behavior, unfortunately) we can't simply
++ // increment an index in the Z loop, and have to perform this trash (keeping track of 3 counters) to avoid
++ // the multiplication
++ for (int y = 0; y < sizeY; ++y, indexY += incY) {
++ indexX = indexY;
++ for (int x = 0; x < sizeX; ++x, indexX += incX) {
++ indexZ = indexX;
++ for (int z = 0; z < sizeZ; ++z, ++indexZ) {
++ if ((bitset[indexZ >>> 6] & (1L << indexZ)) != 0L) {
++ consumer.consume(x, y, z, x + 1, y + 1, z + 1);
++ }
++ }
++ }
++ }
++ } else {
++ // same notes about loop order as the above
++ // this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize())
+
+- for (int i = 0; i < bitSetDiscreteVoxelShape.ySize; i++) {
+- for (int j = 0; j < bitSetDiscreteVoxelShape.xSize; j++) {
+- int k = -1;
++ // only clone when we may write to it
++ bitset = ca.spottedleaf.moonrise.common.util.MixinWorkarounds.clone(bitset);
+
+- for (int l = 0; l <= bitSetDiscreteVoxelShape.zSize; l++) {
+- if (bitSetDiscreteVoxelShape.isFullWide(j, i, l)) {
+- if (coalesce) {
+- if (k == -1) {
+- k = l;
+- }
+- } else {
+- callback.consume(j, i, l, j + 1, i + 1, l + 1);
++ for (int y = 0; y < sizeY; ++y, indexY += incY) {
++ indexX = indexY;
++ for (int x = 0; x < sizeX; ++x, indexX += incX) {
++ for (int zIdx = indexX, endIndex = indexX + sizeZ; zIdx < endIndex; ) {
++ final int firstSetZ = ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.firstSet(bitset, zIdx, endIndex);
++
++ if (firstSetZ == -1) {
++ break;
++ }
++
++ int lastSetZ = ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex);
++ if (lastSetZ == -1) {
++ lastSetZ = endIndex;
+ }
+- } else if (k != -1) {
+- int m = j;
+- int n = i;
+- bitSetDiscreteVoxelShape.clearZStrip(k, l, j, i);
+-
+- while (bitSetDiscreteVoxelShape.isZStripFull(k, l, m + 1, i)) {
+- bitSetDiscreteVoxelShape.clearZStrip(k, l, m + 1, i);
+- m++;
++
++ ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ);
++
++ // try to merge neighbouring on the X axis
++ int endX = x + 1; // exclusive
++ for (int neighbourIdxStart = firstSetZ + incX, neighbourIdxEnd = lastSetZ + incX;
++ endX < sizeX && ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.isRangeSet(bitset, neighbourIdxStart, neighbourIdxEnd);
++ neighbourIdxStart += incX, neighbourIdxEnd += incX) {
++
++ ++endX;
++ ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd);
+ }
+
+- while (bitSetDiscreteVoxelShape.isXZRectangleFull(j, m + 1, k, l, n + 1)) {
+- for (int o = j; o <= m; o++) {
+- bitSetDiscreteVoxelShape.clearZStrip(k, l, o, n + 1);
++ // try to merge neighbouring on the Y axis
++
++ int endY; // exclusive
++ int firstSetZY, lastSetZY;
++ y_merge:
++ for (endY = y + 1, firstSetZY = firstSetZ + incY, lastSetZY = lastSetZ + incY; endY < sizeY;
++ firstSetZY += incY, lastSetZY += incY) {
++
++ // test the whole XZ range
++ for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX;
++ ++testX, start += incX, end += incX) {
++ if (!ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.isRangeSet(bitset, start, end)) {
++ break y_merge;
++ }
+ }
+
+- n++;
++ ++endY;
++
++ // passed, so we can clear it
++ for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX;
++ ++testX, start += incX, end += incX) {
++ ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, start, end);
++ }
+ }
+
+- callback.consume(j, i, k, m + 1, n + 1, l);
+- k = -1;
++ consumer.consume(x, y, firstSetZ - indexX, endX, endY, lastSetZ - indexX);
++ zIdx = lastSetZ;
+ }
+ }
+ }
+ }
+ }
++ // Paper end - optimise collisions
+
+ private boolean isZStripFull(int z1, int z2, int x, int y) {
+ return x < this.xSize && y < this.ySize && this.storage.nextClearBit(this.getIndex(x, y, z1)) >= this.getIndex(x, y, z2);
+diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
+index d812949c7329ae2696b38dc792fa011ba87decb9..7743495c7ec3fc5e17947144457cef7bbe0f4b38 100644
+--- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
++++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java
+@@ -7,6 +7,7 @@ import net.minecraft.util.Mth;
+ public final class CubeVoxelShape extends VoxelShape {
+ protected CubeVoxelShape(DiscreteVoxelShape voxels) {
+ super(voxels);
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this).moonrise$initCache(); // Paper - optimise collisions
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
+index 01693ba050b12b9debcdaefceeff9cbcd503b369..1d36f8dcffd22cf844448d3d8351fb8718cf5227 100644
+--- a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
++++ b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java
+@@ -3,12 +3,79 @@ package net.minecraft.world.phys.shapes;
+ import net.minecraft.core.AxisCycle;
+ import net.minecraft.core.Direction;
+
+-public abstract class DiscreteVoxelShape {
++public abstract class DiscreteVoxelShape implements ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape { // Paper - optimise collisions
+ private static final Direction.Axis[] AXIS_VALUES = Direction.Axis.values();
+ protected final int xSize;
+ protected final int ySize;
+ protected final int zSize;
+
++ // Paper start - optimise collisions
++ // ignore race conditions on field read/write: the shape is static, so it doesn't matter
++ private ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData;
++
++ @Override
++ public final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getOrCreateCachedShapeData() {
++ if (this.cachedShapeData != null) {
++ return this.cachedShapeData;
++ }
++
++ final DiscreteVoxelShape discreteVoxelShape = (DiscreteVoxelShape)(Object)this;
++
++ final int sizeX = discreteVoxelShape.getXSize();
++ final int sizeY = discreteVoxelShape.getYSize();
++ final int sizeZ = discreteVoxelShape.getZSize();
++
++ final int maxIndex = sizeX * sizeY * sizeZ; // exclusive
++
++ final int longsRequired = (maxIndex + (Long.SIZE - 1)) >>> 6;
++ long[] voxelSet;
++
++ final boolean isEmpty = discreteVoxelShape.isEmpty();
++
++ if (discreteVoxelShape instanceof BitSetDiscreteVoxelShape bitsetShape) {
++ voxelSet = bitsetShape.storage.toLongArray();
++ if (voxelSet.length < longsRequired) {
++ // happens when the later long values are 0L, so we need to resize
++ voxelSet = java.util.Arrays.copyOf(voxelSet, longsRequired);
++ }
++ } else {
++ voxelSet = new long[longsRequired];
++ if (!isEmpty) {
++ final int mulX = sizeZ * sizeY;
++ for (int x = 0; x < sizeX; ++x) {
++ for (int y = 0; y < sizeY; ++y) {
++ for (int z = 0; z < sizeZ; ++z) {
++ if (discreteVoxelShape.isFull(x, y, z)) {
++ // index = z + y*size_z + x*(size_z*size_y)
++ final int index = z + y * sizeZ + x * mulX;
++
++ voxelSet[index >>> 6] |= 1L << index;
++ }
++ }
++ }
++ }
++ }
++ }
++
++ final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && discreteVoxelShape.isFull(0, 0, 0);
++
++ final int minFullX = discreteVoxelShape.firstFull(Direction.Axis.X);
++ final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y);
++ final int minFullZ = discreteVoxelShape.firstFull(Direction.Axis.Z);
++
++ final int maxFullX = discreteVoxelShape.lastFull(Direction.Axis.X);
++ final int maxFullY = discreteVoxelShape.lastFull(Direction.Axis.Y);
++ final int maxFullZ = discreteVoxelShape.lastFull(Direction.Axis.Z);
++
++ return this.cachedShapeData = new ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData(
++ sizeX, sizeY, sizeZ, voxelSet,
++ minFullX, minFullY, minFullZ,
++ maxFullX, maxFullY, maxFullZ,
++ isEmpty, hasSingleAABB
++ );
++ }
++ // Paper end - optimise collisions
++
+ protected DiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) {
+ if (sizeX >= 0 && sizeY >= 0 && sizeZ >= 0) {
+ this.xSize = sizeX;
+diff --git a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
+index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e002ef5b82 100644
+--- a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
++++ b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java
+@@ -4,8 +4,8 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList;
+ import it.unimi.dsi.fastutil.doubles.DoubleList;
+
+ public class OffsetDoubleList extends AbstractDoubleList {
+- private final DoubleList delegate;
+- private final double offset;
++ public final DoubleList delegate; // Paper - optimise collisions - public
++ public final double offset; // Paper - optimise collisions - public
+
+ public OffsetDoubleList(DoubleList oldList, double offset) {
+ this.delegate = oldList;
+diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+index 0fdd2cdd8d215ca1523eda8ad7316cdd5f41a6b5..7ede56f77af1d40e10fde2e660d5794e4b37dc5d 100644
+--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+@@ -16,9 +16,15 @@ public final class Shapes {
+ public static final double EPSILON = 1.0E-7;
+ public static final double BIG_EPSILON = 1.0E-6;
+ private static final VoxelShape BLOCK = Util.make(() -> {
+- DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1);
+- discreteVoxelShape.fill(0, 0, 0);
+- return new CubeVoxelShape(discreteVoxelShape);
++ // Paper start - optimise collisions
++ final DiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(1, 1, 1);
++ shape.fill(0, 0, 0);
++
++ return new ArrayVoxelShape(
++ shape,
++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE
++ );
++ // Paper end - optimise collisions
+ });
+ public static final VoxelShape INFINITY = box(
+ Double.NEGATIVE_INFINITY,
+@@ -43,6 +49,30 @@ public final class Shapes {
+ return BLOCK;
+ }
+
++ // Paper start - optimise collisions
++ private static final DoubleArrayList[] PARTS_BY_BITS = new DoubleArrayList[] {
++ DoubleArrayList.wrap(generateCubeParts(1 << 0)),
++ DoubleArrayList.wrap(generateCubeParts(1 << 1)),
++ DoubleArrayList.wrap(generateCubeParts(1 << 2)),
++ DoubleArrayList.wrap(generateCubeParts(1 << 3))
++ };
++
++ private static double[] generateCubeParts(final int parts) {
++ // note: parts is a power of two, so we do not need to worry about loss of precision here
++ // note: parts is from [2^0, 2^3]
++ final double inc = 1.0 / (double)parts;
++
++ final double[] ret = new double[parts + 1];
++ double val = 0.0;
++ for (int i = 0; i <= parts; ++i) {
++ ret[i] = val;
++ val += inc;
++ }
++
++ return ret;
++ }
++ // Paper end - optimise collisions
++
+ public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
+ if (!(minX > maxX) && !(minY > maxY) && !(minZ > maxZ)) {
+ return create(minX, minY, minZ, maxX, maxY, maxZ);
+@@ -52,39 +82,42 @@ public final class Shapes {
+ }
+
+ public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
++ // Paper start - optimise collisions
+ if (!(maxX - minX < 1.0E-7) && !(maxY - minY < 1.0E-7) && !(maxZ - minZ < 1.0E-7)) {
+- int i = findBits(minX, maxX);
+- int j = findBits(minY, maxY);
+- int k = findBits(minZ, maxZ);
+- if (i < 0 || j < 0 || k < 0) {
++ final int bitsX = findBits(minX, maxX);
++ final int bitsY = findBits(minY, maxY);
++ final int bitsZ = findBits(minZ, maxZ);
++ if (bitsX >= 0 && bitsY >= 0 && bitsZ >= 0) {
++ if (bitsX == 0 && bitsY == 0 && bitsZ == 0) {
++ return BLOCK;
++ } else {
++ final int sizeX = 1 << bitsX;
++ final int sizeY = 1 << bitsY;
++ final int sizeZ = 1 << bitsZ;
++ final BitSetDiscreteVoxelShape shape = BitSetDiscreteVoxelShape.withFilledBounds(
++ sizeX, sizeY, sizeZ,
++ (int)Math.round(minX * (double)sizeX), (int)Math.round(minY * (double)sizeY), (int)Math.round(minZ * (double)sizeZ),
++ (int)Math.round(maxX * (double)sizeX), (int)Math.round(maxY * (double)sizeY), (int)Math.round(maxZ * (double)sizeZ)
++ );
++ return new ArrayVoxelShape(
++ shape,
++ PARTS_BY_BITS[bitsX],
++ PARTS_BY_BITS[bitsY],
++ PARTS_BY_BITS[bitsZ]
++ );
++ }
++ } else {
+ return new ArrayVoxelShape(
+ BLOCK.shape,
+- DoubleArrayList.wrap(new double[]{minX, maxX}),
+- DoubleArrayList.wrap(new double[]{minY, maxY}),
+- DoubleArrayList.wrap(new double[]{minZ, maxZ})
+- );
+- } else if (i == 0 && j == 0 && k == 0) {
+- return block();
+- } else {
+- int l = 1 << i;
+- int m = 1 << j;
+- int n = 1 << k;
+- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(
+- l,
+- m,
+- n,
+- (int)Math.round(minX * (double)l),
+- (int)Math.round(minY * (double)m),
+- (int)Math.round(minZ * (double)n),
+- (int)Math.round(maxX * (double)l),
+- (int)Math.round(maxY * (double)m),
+- (int)Math.round(maxZ * (double)n)
++ minX == 0.0 && maxX == 1.0 ? ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minX, maxX }),
++ minY == 0.0 && maxY == 1.0 ? ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minY, maxY }),
++ minZ == 0.0 && maxZ == 1.0 ? ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minZ, maxZ })
+ );
+- return new CubeVoxelShape(bitSetDiscreteVoxelShape);
+ }
+ } else {
+- return empty();
++ return EMPTY;
+ }
++ // Paper end - optimise collisions
+ }
+
+ public static VoxelShape create(AABB box) {
+@@ -119,80 +152,54 @@ public final class Shapes {
+ return join(first, second, BooleanOp.OR);
+ }
+
+- public static VoxelShape or(VoxelShape first, VoxelShape... others) {
+- return Arrays.stream(others).reduce(first, Shapes::or);
++ // Paper start - optimise collisions
++ public static VoxelShape or(VoxelShape shape, VoxelShape... others) {
++ int size = others.length;
++ if (size == 0) {
++ return shape;
++ }
++
++ // reduce complexity of joins by splitting the merges
++
++ // add extra slot for first shape
++ ++size;
++ final VoxelShape[] tmp = Arrays.copyOf(others, size);
++ // insert first shape
++ tmp[size - 1] = shape;
++
++ while (size > 1) {
++ int newSize = 0;
++ for (int i = 0; i < size; i += 2) {
++ final int next = i + 1;
++ if (next >= size) {
++ // nothing to merge with, so leave it for next iteration
++ tmp[newSize++] = tmp[i];
++ break;
++ } else {
++ // merge with adjacent
++ final VoxelShape first = tmp[i];
++ final VoxelShape second = tmp[next];
++
++ tmp[newSize++] = Shapes.or(first, second);
++ }
++ }
++ size = newSize;
++ }
++
++ return tmp[0];
++ // Paper end - optimise collisions
+ }
+
+ public static VoxelShape join(VoxelShape first, VoxelShape second, BooleanOp function) {
+- return joinUnoptimized(first, second, function).optimize();
++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.joinOptimized(first, second, function); // Paper - optimise collisions
+ }
+
+ public static VoxelShape joinUnoptimized(VoxelShape one, VoxelShape two, BooleanOp function) {
+- if (function.apply(false, false)) {
+- throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
+- } else if (one == two) {
+- return function.apply(true, true) ? one : empty();
+- } else {
+- boolean bl = function.apply(true, false);
+- boolean bl2 = function.apply(false, true);
+- if (one.isEmpty()) {
+- return bl2 ? two : empty();
+- } else if (two.isEmpty()) {
+- return bl ? one : empty();
+- } else {
+- IndexMerger indexMerger = createIndexMerger(1, one.getCoords(Direction.Axis.X), two.getCoords(Direction.Axis.X), bl, bl2);
+- IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, one.getCoords(Direction.Axis.Y), two.getCoords(Direction.Axis.Y), bl, bl2);
+- IndexMerger indexMerger3 = createIndexMerger(
+- (indexMerger.size() - 1) * (indexMerger2.size() - 1), one.getCoords(Direction.Axis.Z), two.getCoords(Direction.Axis.Z), bl, bl2
+- );
+- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.join(
+- one.shape, two.shape, indexMerger, indexMerger2, indexMerger3, function
+- );
+- return (VoxelShape)(indexMerger instanceof DiscreteCubeMerger
+- && indexMerger2 instanceof DiscreteCubeMerger
+- && indexMerger3 instanceof DiscreteCubeMerger
+- ? new CubeVoxelShape(bitSetDiscreteVoxelShape)
+- : new ArrayVoxelShape(bitSetDiscreteVoxelShape, indexMerger.getList(), indexMerger2.getList(), indexMerger3.getList()));
+- }
+- }
++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.joinUnoptimized(one, two, function); // Paper - optimise collisions
+ }
+
+ public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
+- if (predicate.apply(false, false)) {
+- throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
+- } else {
+- boolean bl = shape1.isEmpty();
+- boolean bl2 = shape2.isEmpty();
+- if (!bl && !bl2) {
+- if (shape1 == shape2) {
+- return predicate.apply(true, true);
+- } else {
+- boolean bl3 = predicate.apply(true, false);
+- boolean bl4 = predicate.apply(false, true);
+-
+- for (Direction.Axis axis : AxisCycle.AXIS_VALUES) {
+- if (shape1.max(axis) < shape2.min(axis) - 1.0E-7) {
+- return bl3 || bl4;
+- }
+-
+- if (shape2.max(axis) < shape1.min(axis) - 1.0E-7) {
+- return bl3 || bl4;
+- }
+- }
+-
+- IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), bl3, bl4);
+- IndexMerger indexMerger2 = createIndexMerger(
+- indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), bl3, bl4
+- );
+- IndexMerger indexMerger3 = createIndexMerger(
+- (indexMerger.size() - 1) * (indexMerger2.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), bl3, bl4
+- );
+- return joinIsNotEmpty(indexMerger, indexMerger2, indexMerger3, shape1.shape, shape2.shape, predicate);
+- }
+- } else {
+- return predicate.apply(!bl, !bl2);
+- }
+- }
++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isJoinNonEmpty(shape1, shape2, predicate); // Paper - optimise collisions
+ }
+
+ private static boolean joinIsNotEmpty(
+@@ -219,70 +226,120 @@ public final class Shapes {
+ return maxDist;
+ }
+
+- public static boolean blockOccudes(VoxelShape shape, VoxelShape neighbor, Direction direction) {
+- if (shape == block() && neighbor == block()) {
++ // Paper start - optimise collisions
++ public static boolean blockOccudes(final VoxelShape first, final VoxelShape second, final Direction direction) {
++ final boolean firstBlock = first == BLOCK;
++ final boolean secondBlock = second == BLOCK;
++
++ if (firstBlock & secondBlock) {
+ return true;
+- } else if (neighbor.isEmpty()) {
++ }
++
++ if (first.isEmpty() | second.isEmpty()) {
++ return false;
++ }
++
++ // we optimise getOpposite, so we can use it
++ // secondly, use our cache to retrieve sliced shape
++ final VoxelShape newFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getFaceShapeClamped(direction);
++ if (newFirst.isEmpty()) {
+ return false;
+- } else {
+- Direction.Axis axis = direction.getAxis();
+- Direction.AxisDirection axisDirection = direction.getAxisDirection();
+- VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : neighbor;
+- VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? neighbor : shape;
+- BooleanOp booleanOp = axisDirection == Direction.AxisDirection.POSITIVE ? BooleanOp.ONLY_FIRST : BooleanOp.ONLY_SECOND;
+- return DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0, 1.0E-7)
+- && DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0, 1.0E-7)
+- && !joinIsNotEmpty(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), booleanOp);
+ }
++ final VoxelShape newSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getFaceShapeClamped(direction.getOpposite());
++ if (newSecond.isEmpty()) {
++ return false;
++ }
++
++ return !joinIsNotEmpty(newFirst, newSecond, BooleanOp.ONLY_FIRST);
++ // Paper end - optimise collisions
+ }
+
+ public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) {
+- if (shape == block()) {
+- return block();
+- } else {
+- Direction.Axis axis = direction.getAxis();
+- boolean bl;
+- int i;
+- if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
+- bl = DoubleMath.fuzzyEquals(shape.max(axis), 1.0, 1.0E-7);
+- i = shape.shape.getSize(axis) - 1;
+- } else {
+- bl = DoubleMath.fuzzyEquals(shape.min(axis), 0.0, 1.0E-7);
+- i = 0;
+- }
++ return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$getFaceShapeClamped(direction); // Paper - optimise collisions
++ }
+
+- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i));
+- }
++ // Paper start - optimise collisions
++ private static boolean mergedMayOccludeBlock(final VoxelShape shape1, final VoxelShape shape2) {
++ // if the combined bounds of the two shapes cannot occlude, then neither can the merged
++ final AABB bounds1 = shape1.bounds();
++ final AABB bounds2 = shape2.bounds();
++
++ final double minX = Math.min(bounds1.minX, bounds2.minX);
++ final double minY = Math.min(bounds1.minY, bounds2.minY);
++ final double minZ = Math.min(bounds1.minZ, bounds2.minZ);
++
++ final double maxX = Math.max(bounds1.maxX, bounds2.maxX);
++ final double maxY = Math.max(bounds1.maxY, bounds2.maxY);
++ final double maxZ = Math.max(bounds1.maxZ, bounds2.maxZ);
++
++ return (minX <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && maxX >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) &&
++ (minY <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && maxY >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) &&
++ (minZ <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && maxZ >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON));
+ }
++ // Paper end - optimise collisions
+
+- public static boolean mergedFaceOccludes(VoxelShape one, VoxelShape two, Direction direction) {
+- if (one != block() && two != block()) {
+- Direction.Axis axis = direction.getAxis();
+- Direction.AxisDirection axisDirection = direction.getAxisDirection();
+- VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? one : two;
+- VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? two : one;
+- if (!DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0, 1.0E-7)) {
+- voxelShape = empty();
+- }
++ // Paper start - optimise collisions
++ public static boolean mergedFaceOccludes(final VoxelShape first, final VoxelShape second, final Direction direction) {
++ // see if any of the shapes on their own occludes, only if cached
++ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$occludesFullBlockIfCached() || ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$occludesFullBlockIfCached()) {
++ return true;
++ }
+
+- if (!DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0, 1.0E-7)) {
+- voxelShape2 = empty();
+- }
++ if (first.isEmpty() & second.isEmpty()) {
++ return false;
++ }
+
+- return !joinIsNotEmpty(
+- block(),
+- joinUnoptimized(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), BooleanOp.OR),
+- BooleanOp.ONLY_FIRST
+- );
+- } else {
++ // we optimise getOpposite, so we can use it
++ // secondly, use our cache to retrieve sliced shape
++ final VoxelShape newFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getFaceShapeClamped(direction);
++ final VoxelShape newSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getFaceShapeClamped(direction.getOpposite());
++
++ // see if any of the shapes on their own occludes, only if cached
++ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$occludesFullBlockIfCached() || ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newSecond).moonrise$occludesFullBlockIfCached()) {
+ return true;
+ }
++
++ final boolean firstEmpty = newFirst.isEmpty();
++ final boolean secondEmpty = newSecond.isEmpty();
++
++ if (firstEmpty & secondEmpty) {
++ return false;
++ }
++
++ if (firstEmpty | secondEmpty) {
++ return secondEmpty ? ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$occludesFullBlock() : ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newSecond).moonrise$occludesFullBlock();
++ }
++
++ if (newFirst == newSecond) {
++ return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$occludesFullBlock();
++ }
++
++ return mergedMayOccludeBlock(newFirst, newSecond) && ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$orUnoptimized(newSecond)).moonrise$occludesFullBlock();
+ }
++ // Paper end - optimise collisions
++
++ // Paper start - optimise collisions
++ public static boolean faceShapeOccludes(final VoxelShape shape1, final VoxelShape shape2) {
++ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$occludesFullBlockIfCached() || ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape2).moonrise$occludesFullBlockIfCached()) {
++ return true;
++ }
++
++ final boolean s1Empty = shape1.isEmpty();
++ final boolean s2Empty = shape2.isEmpty();
++ if (s1Empty & s2Empty) {
++ return false;
++ }
++
++ if (s1Empty | s2Empty) {
++ return s2Empty ? ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$occludesFullBlock() : ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape2).moonrise$occludesFullBlock();
++ }
++
++ if (shape1 == shape2) {
++ return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$occludesFullBlock();
++ }
+
+- public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) {
+- return one == block()
+- || two == block()
+- || (!one.isEmpty() || !two.isEmpty()) && !joinIsNotEmpty(block(), joinUnoptimized(one, two, BooleanOp.OR), BooleanOp.ONLY_FIRST);
++ return mergedMayOccludeBlock(shape1, shape2) && ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$orUnoptimized(shape2)).moonrise$occludesFullBlock();
++ // Paper end - optimise collisions
+ }
+
+ @VisibleForTesting
+diff --git a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
+index b07f1c58e00d232e7c83e6df3499e4b677645609..b88c71f27996d24d29048e06a69a004617eb53a2 100644
+--- a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
++++ b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java
+@@ -12,6 +12,7 @@ public class SliceShape extends VoxelShape {
+ super(makeSlice(shape.shape, axis, sliceWidth));
+ this.delegate = shape;
+ this.axis = axis;
++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this).moonrise$initCache(); // Paper - optimise collisions
+ }
+
+ private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape voxelSet, Direction.Axis axis, int sliceWidth) {
+diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
+index d440003ca4403511a964f61bcf67ac2cd75c5359..11824d39e72fa003b3a56aa9b8d679fe8e23a1a4 100644
+--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
+@@ -15,38 +15,505 @@ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.Vec3;
+
+-public abstract class VoxelShape {
+- protected final DiscreteVoxelShape shape;
++public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape { // Paper - optimise collisions
++ public final DiscreteVoxelShape shape; // Paper - optimise collisions - public
+ @Nullable
+ private VoxelShape[] faces;
+
++ // Paper start - optimise collisions
++ private double offsetX;
++ private double offsetY;
++ private double offsetZ;
++ private AABB singleAABBRepresentation;
++ private double[] rootCoordinatesX;
++ private double[] rootCoordinatesY;
++ private double[] rootCoordinatesZ;
++ private ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData;
++ private boolean isEmpty;
++ private ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs;
++ private AABB cachedBounds;
++ private Boolean isFullBlock;
++ private Boolean occludesFullBlock;
++
++ // must be power of two
++ private static final int MERGED_CACHE_SIZE = 16;
++ private ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache[] mergedORCache;
++
++ @Override
++ public final double moonrise$offsetX() {
++ return this.offsetX;
++ }
++
++ @Override
++ public final double moonrise$offsetY() {
++ return this.offsetY;
++ }
++
++ @Override
++ public final double moonrise$offsetZ() {
++ return this.offsetZ;
++ }
++
++ @Override
++ public final AABB moonrise$getSingleAABBRepresentation() {
++ return this.singleAABBRepresentation;
++ }
++
++ @Override
++ public final double[] moonrise$rootCoordinatesX() {
++ return this.rootCoordinatesX;
++ }
++
++ @Override
++ public final double[] moonrise$rootCoordinatesY() {
++ return this.rootCoordinatesY;
++ }
++
++ @Override
++ public final double[] moonrise$rootCoordinatesZ() {
++ return this.rootCoordinatesZ;
++ }
++
++ private static double[] extractRawArray(final DoubleList list) {
++ if (list instanceof it.unimi.dsi.fastutil.doubles.DoubleArrayList rawList) {
++ final double[] raw = rawList.elements();
++ final int expected = rawList.size();
++ if (raw.length == expected) {
++ return raw;
++ } else {
++ return java.util.Arrays.copyOf(raw, expected);
++ }
++ } else {
++ return list.toDoubleArray();
++ }
++ }
++
++ @Override
++ public final void moonrise$initCache() {
++ this.cachedShapeData = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)this.shape).moonrise$getOrCreateCachedShapeData();
++ this.isEmpty = this.cachedShapeData.isEmpty();
++
++ final DoubleList xList = this.getCoords(Direction.Axis.X);
++ final DoubleList yList = this.getCoords(Direction.Axis.Y);
++ final DoubleList zList = this.getCoords(Direction.Axis.Z);
++
++ if (xList instanceof OffsetDoubleList offsetDoubleList) {
++ this.offsetX = offsetDoubleList.offset;
++ this.rootCoordinatesX = extractRawArray(offsetDoubleList.delegate);
++ } else {
++ this.rootCoordinatesX = extractRawArray(xList);
++ }
++
++ if (yList instanceof OffsetDoubleList offsetDoubleList) {
++ this.offsetY = offsetDoubleList.offset;
++ this.rootCoordinatesY = extractRawArray(offsetDoubleList.delegate);
++ } else {
++ this.rootCoordinatesY = extractRawArray(yList);
++ }
++
++ if (zList instanceof OffsetDoubleList offsetDoubleList) {
++ this.offsetZ = offsetDoubleList.offset;
++ this.rootCoordinatesZ = extractRawArray(offsetDoubleList.delegate);
++ } else {
++ this.rootCoordinatesZ = extractRawArray(zList);
++ }
++
++ if (this.cachedShapeData.hasSingleAABB()) {
++ this.singleAABBRepresentation = new AABB(
++ this.rootCoordinatesX[0] + this.offsetX, this.rootCoordinatesY[0] + this.offsetY, this.rootCoordinatesZ[0] + this.offsetZ,
++ this.rootCoordinatesX[1] + this.offsetX, this.rootCoordinatesY[1] + this.offsetY, this.rootCoordinatesZ[1] + this.offsetZ
++ );
++ this.cachedBounds = this.singleAABBRepresentation;
++ }
++ }
++
++ @Override
++ public final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getCachedVoxelData() {
++ return this.cachedShapeData;
++ }
++
++ private VoxelShape[] faceShapeClampedCache;
++
++ @Override
++ public final VoxelShape moonrise$getFaceShapeClamped(final Direction direction) {
++ if (this.isEmpty) {
++ return (VoxelShape)(Object)this;
++ }
++ if ((VoxelShape)(Object)this == Shapes.block()) {
++ return (VoxelShape)(Object)this;
++ }
++
++ VoxelShape[] cache = this.faceShapeClampedCache;
++ if (cache != null) {
++ final VoxelShape ret = cache[direction.ordinal()];
++ if (ret != null) {
++ return ret;
++ }
++ }
++
++
++ if (cache == null) {
++ this.faceShapeClampedCache = cache = new VoxelShape[6];
++ }
++
++ final Direction.Axis axis = direction.getAxis();
++
++ final VoxelShape ret;
++
++ if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
++ if (DoubleMath.fuzzyEquals(this.max(axis), 1.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) {
++ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1));
++ } else {
++ ret = Shapes.empty();
++ }
++ } else {
++ if (DoubleMath.fuzzyEquals(this.min(axis), 0.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) {
++ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, 0));
++ } else {
++ ret = Shapes.empty();
++ }
++ }
++
++ cache[direction.ordinal()] = ret;
++
++ return ret;
++ }
++
++ private static VoxelShape tryForceBlock(final VoxelShape other) {
++ if (other == Shapes.block()) {
++ return other;
++ }
++
++ final AABB otherAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)other).moonrise$getSingleAABBRepresentation();
++ if (otherAABB == null) {
++ return other;
++ }
++
++ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)Shapes.block()).moonrise$getSingleAABBRepresentation().equals(otherAABB)) {
++ return Shapes.block();
++ }
++
++ return other;
++ }
++
++ private boolean computeOccludesFullBlock() {
++ if (this.isEmpty) {
++ this.occludesFullBlock = Boolean.FALSE;
++ return false;
++ }
++
++ if (this.moonrise$isFullBlock()) {
++ this.occludesFullBlock = Boolean.TRUE;
++ return true;
++ }
++
++ final AABB singleAABB = this.singleAABBRepresentation;
++ if (singleAABB != null) {
++ // check if the bounding box encloses the full cube
++ final boolean ret =
++ (singleAABB.minY <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && singleAABB.maxY >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) &&
++ (singleAABB.minX <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && singleAABB.maxX >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) &&
++ (singleAABB.minZ <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && singleAABB.maxZ >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON));
++ this.occludesFullBlock = Boolean.valueOf(ret);
++ return ret;
++ }
++
++ final boolean ret = !Shapes.joinIsNotEmpty(Shapes.block(), ((VoxelShape)(Object)this), BooleanOp.ONLY_FIRST);
++ this.occludesFullBlock = Boolean.valueOf(ret);
++ return ret;
++ }
++
++ @Override
++ public final boolean moonrise$occludesFullBlock() {
++ final Boolean ret = this.occludesFullBlock;
++ if (ret != null) {
++ return ret.booleanValue();
++ }
++
++ return this.computeOccludesFullBlock();
++ }
++
++ @Override
++ public final boolean moonrise$occludesFullBlockIfCached() {
++ final Boolean ret = this.occludesFullBlock;
++ return ret != null ? ret.booleanValue() : false;
++ }
++
++ private static int hash(final VoxelShape key) {
++ return it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(key));
++ }
++
++ @Override
++ public final VoxelShape moonrise$orUnoptimized(final VoxelShape other) {
++ // don't cache simple cases
++ if (((VoxelShape)(Object)this) == other) {
++ return other;
++ }
++
++ if (this.isEmpty) {
++ return other;
++ }
++
++ if (other.isEmpty()) {
++ return (VoxelShape)(Object)this;
++ }
++
++ // try this cache first
++ final int thisCacheKey = hash(other) & (MERGED_CACHE_SIZE - 1);
++ final ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache cached = this.mergedORCache == null ? null : this.mergedORCache[thisCacheKey];
++ if (cached != null && cached.key() == other) {
++ return cached.result();
++ }
++
++ // try other cache
++ final int otherCacheKey = hash((VoxelShape)(Object)this) & (MERGED_CACHE_SIZE - 1);
++ final ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache otherCache = ((VoxelShape)(Object)other).mergedORCache == null ? null : ((VoxelShape)(Object)other).mergedORCache[otherCacheKey];
++ if (otherCache != null && otherCache.key() == (VoxelShape)(Object)this) {
++ return otherCache.result();
++ }
++
++ // note: unsure if joinUnoptimized(1, 2, OR) == joinUnoptimized(2, 1, OR) for all cases
++ final VoxelShape result = Shapes.joinUnoptimized((VoxelShape)(Object)this, other, BooleanOp.OR);
++
++ if (cached != null && otherCache == null) {
++ // try to use second cache instead of replacing an entry in this cache
++ if (((VoxelShape)(Object)other).mergedORCache == null) {
++ ((VoxelShape)(Object)other).mergedORCache = new ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache[MERGED_CACHE_SIZE];
++ }
++ ((VoxelShape)(Object)other).mergedORCache[otherCacheKey] = new ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache((VoxelShape)(Object)this, result);
++ } else {
++ // line is not occupied or other cache line is full
++ // always bias to replace this cache, as this cache is the first we check
++ if (this.mergedORCache == null) {
++ this.mergedORCache = new ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache[MERGED_CACHE_SIZE];
++ }
++ this.mergedORCache[thisCacheKey] = new ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache(other, result);
++ }
++
++ return result;
++ }
++
++ private static DoubleList offsetList(final DoubleList src, final double by) {
++ if (src instanceof OffsetDoubleList offsetDoubleList) {
++ return new OffsetDoubleList(offsetDoubleList.delegate, by + offsetDoubleList.offset);
++ }
++ return new OffsetDoubleList(src, by);
++ }
++
++ private List<AABB> toAabbsUncached() {
++ final List<AABB> ret = new java.util.ArrayList<>();
++ if (this.singleAABBRepresentation != null) {
++ ret.add(this.singleAABBRepresentation);
++ } else {
++ final double[] coordsX = this.rootCoordinatesX;
++ final double[] coordsY = this.rootCoordinatesY;
++ final double[] coordsZ = this.rootCoordinatesZ;
++
++ final double offX = this.offsetX;
++ final double offY = this.offsetY;
++ final double offZ = this.offsetZ;
++
++ this.shape.forAllBoxes((final int minX, final int minY, final int minZ,
++ final int maxX, final int maxY, final int maxZ) -> {
++ ret.add(new AABB(
++ coordsX[minX] + offX,
++ coordsY[minY] + offY,
++ coordsZ[minZ] + offZ,
++
++
++ coordsX[maxX] + offX,
++ coordsY[maxY] + offY,
++ coordsZ[maxZ] + offZ
++ ));
++ }, true);
++ }
++
++ // cache result
++ this.cachedToAABBs = new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(ret, false, 0.0, 0.0, 0.0);
++
++ return ret;
++ }
++
++ private boolean computeFullBlock() {
++ Boolean ret;
++ if (this.isEmpty) {
++ ret = Boolean.FALSE;
++ } else if ((VoxelShape)(Object)this == Shapes.block()) {
++ ret = Boolean.TRUE;
++ } else {
++ final AABB singleAABB = this.singleAABBRepresentation;
++ if (singleAABB == null) {
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData;
++ final int sMinX = shapeData.minFullX();
++ final int sMinY = shapeData.minFullY();
++ final int sMinZ = shapeData.minFullZ();
++
++ final int sMaxX = shapeData.maxFullX();
++ final int sMaxY = shapeData.maxFullY();
++ final int sMaxZ = shapeData.maxFullZ();
++
++ if (Math.abs(this.rootCoordinatesX[sMinX] + this.offsetX) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++ Math.abs(this.rootCoordinatesY[sMinY] + this.offsetY) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++ Math.abs(this.rootCoordinatesZ[sMinZ] + this.offsetZ) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++
++ Math.abs(1.0 - (this.rootCoordinatesX[sMaxX] + this.offsetX)) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++ Math.abs(1.0 - (this.rootCoordinatesY[sMaxY] + this.offsetY)) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++ Math.abs(1.0 - (this.rootCoordinatesZ[sMaxZ] + this.offsetZ)) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) {
++
++ // index = z + y*sizeZ + x*(sizeZ*sizeY)
++
++ final int sizeY = shapeData.sizeY();
++ final int sizeZ = shapeData.sizeZ();
++
++ final long[] bitset = shapeData.voxelSet();
++
++ ret = Boolean.TRUE;
++
++ check_full:
++ for (int x = sMinX; x < sMaxX; ++x) {
++ for (int y = sMinY; y < sMaxY; ++y) {
++ final int baseIndex = y*sizeZ + x*(sizeZ*sizeY);
++ if (!ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.isRangeSet(bitset, baseIndex + sMinZ, baseIndex + sMaxZ)) {
++ ret = Boolean.FALSE;
++ break check_full;
++ }
++ }
++ }
++ } else {
++ ret = Boolean.FALSE;
++ }
++ } else {
++ ret = Boolean.valueOf(
++ Math.abs(singleAABB.minX) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++ Math.abs(singleAABB.minY) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++ Math.abs(singleAABB.minZ) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++
++ Math.abs(1.0 - singleAABB.maxX) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++ Math.abs(1.0 - singleAABB.maxY) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON &&
++ Math.abs(1.0 - singleAABB.maxZ) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON
++ );
++ }
++ }
++
++ this.isFullBlock = ret;
++
++ return ret.booleanValue();
++ }
++
++ @Override
++ public final boolean moonrise$isFullBlock() {
++ final Boolean ret = this.isFullBlock;
++
++ if (ret != null) {
++ return ret.booleanValue();
++ }
++
++ return this.computeFullBlock();
++ }
++
++ private static BlockHitResult clip(final AABB aabb, final Vec3 from, final Vec3 to, final BlockPos offset) {
++ final double[] minDistanceArr = new double[] { 1.0 };
++ final double diffX = to.x - from.x;
++ final double diffY = to.y - from.y;
++ final double diffZ = to.z - from.z;
++
++ final Direction direction = AABB.getDirection(aabb.move(offset), from, minDistanceArr, null, diffX, diffY, diffZ);
++
++ if (direction == null) {
++ return null;
++ }
++
++ final double minDistance = minDistanceArr[0];
++ return new BlockHitResult(from.add(minDistance * diffX, minDistance * diffY, minDistance * diffZ), direction, offset, false);
++ }
++ // Paper end - optimise collisions
++
+ protected VoxelShape(DiscreteVoxelShape voxels) {
+ this.shape = voxels;
+ }
+
+ public double min(Direction.Axis axis) {
+- int i = this.shape.firstFull(axis);
+- return i >= this.shape.getSize(axis) ? Double.POSITIVE_INFINITY : this.get(axis, i);
++ // Paper start - optimise collisions
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData;
++ switch (axis) {
++ case X: {
++ final int idx = shapeData.minFullX();
++ return idx >= shapeData.sizeX() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesX[idx] + this.offsetX);
++ }
++ case Y: {
++ final int idx = shapeData.minFullY();
++ return idx >= shapeData.sizeY() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesY[idx] + this.offsetY);
++ }
++ case Z: {
++ final int idx = shapeData.minFullZ();
++ return idx >= shapeData.sizeZ() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesZ[idx] + this.offsetZ);
++ }
++ default: {
++ // should never get here
++ return Double.POSITIVE_INFINITY;
++ }
++ }
++ // Paper end - optimise collisions
+ }
+
+ public double max(Direction.Axis axis) {
+- int i = this.shape.lastFull(axis);
+- return i <= 0 ? Double.NEGATIVE_INFINITY : this.get(axis, i);
++ // Paper start - optimise collisions
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData;
++ switch (axis) {
++ case X: {
++ final int idx = shapeData.maxFullX();
++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesX[idx] + this.offsetX);
++ }
++ case Y: {
++ final int idx = shapeData.maxFullY();
++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesY[idx] + this.offsetY);
++ }
++ case Z: {
++ final int idx = shapeData.maxFullZ();
++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesZ[idx] + this.offsetZ);
++ }
++ default: {
++ // should never get here
++ return Double.NEGATIVE_INFINITY;
++ }
++ }
++ // Paper end - optimise collisions
+ }
+
+ public AABB bounds() {
+- if (this.isEmpty()) {
+- throw (UnsupportedOperationException)Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape."));
+- } else {
+- return new AABB(
+- this.min(Direction.Axis.X),
+- this.min(Direction.Axis.Y),
+- this.min(Direction.Axis.Z),
+- this.max(Direction.Axis.X),
+- this.max(Direction.Axis.Y),
+- this.max(Direction.Axis.Z)
+- );
++ // Paper start - optimise collisions
++ if (this.isEmpty) {
++ throw Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape."));
++ }
++ AABB cached = this.cachedBounds;
++ if (cached != null) {
++ return cached;
+ }
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData;
++
++ final double[] coordsX = this.rootCoordinatesX;
++ final double[] coordsY = this.rootCoordinatesY;
++ final double[] coordsZ = this.rootCoordinatesZ;
++
++ final double offX = this.offsetX;
++ final double offY = this.offsetY;
++ final double offZ = this.offsetZ;
++
++ // note: if not empty, then there is one full AABB so no bounds checks are needed on the minFull/maxFull indices
++ cached = new AABB(
++ coordsX[shapeData.minFullX()] + offX,
++ coordsY[shapeData.minFullY()] + offY,
++ coordsZ[shapeData.minFullZ()] + offZ,
++
++ coordsX[shapeData.maxFullX()] + offX,
++ coordsY[shapeData.maxFullY()] + offY,
++ coordsZ[shapeData.maxFullZ()] + offZ
++ );
++
++ this.cachedBounds = cached;
++ return cached;
++ // Paper end - optimise collisions
+ }
+
+ public VoxelShape singleEncompassing() {
+@@ -69,28 +536,95 @@ public abstract class VoxelShape {
+ public abstract DoubleList getCoords(Direction.Axis axis);
+
+ public boolean isEmpty() {
+- return this.shape.isEmpty();
++ return this.isEmpty; // Paper - optimise collisions
+ }
+
+ public VoxelShape move(double x, double y, double z) {
+- return (VoxelShape)(this.isEmpty()
+- ? Shapes.empty()
+- : new ArrayVoxelShape(
+- this.shape,
+- new OffsetDoubleList(this.getCoords(Direction.Axis.X), x),
+- new OffsetDoubleList(this.getCoords(Direction.Axis.Y), y),
+- new OffsetDoubleList(this.getCoords(Direction.Axis.Z), z)
+- ));
++ // Paper start - optimise collisions
++ if (this.isEmpty) {
++ return Shapes.empty();
++ }
++
++ final ArrayVoxelShape ret = new ArrayVoxelShape(
++ this.shape,
++ offsetList(this.getCoords(Direction.Axis.X), x),
++ offsetList(this.getCoords(Direction.Axis.Y), y),
++ offsetList(this.getCoords(Direction.Axis.Z), z)
++ );
++
++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs = this.cachedToAABBs;
++ if (cachedToAABBs != null) {
++ ((VoxelShape)(Object)ret).cachedToAABBs = ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs.offset(cachedToAABBs, x, y, z);
++ }
++
++ return ret;
++ // Paper end - optimise collisions
+ }
+
+ public VoxelShape optimize() {
+- VoxelShape[] voxelShapes = new VoxelShape[]{Shapes.empty()};
+- this.forAllBoxes(
+- (minX, minY, minZ, maxX, maxY, maxZ) -> voxelShapes[0] = Shapes.joinUnoptimized(
+- voxelShapes[0], Shapes.box(minX, minY, minZ, maxX, maxY, maxZ), BooleanOp.OR
+- )
+- );
+- return voxelShapes[0];
++ // Paper start - optimise collisions
++ if (this.isEmpty) {
++ return Shapes.empty();
++ }
++
++ if (this.singleAABBRepresentation != null) {
++ // note: the isFullBlock() is fuzzy, and Shapes.create() is also fuzzy which would return block()
++ return this.moonrise$isFullBlock() ? Shapes.block() : (VoxelShape)(Object)this;
++ }
++
++ final List<AABB> aabbs = this.toAabbs();
++
++ if (aabbs.size() == 1) {
++ final AABB singleAABB = aabbs.get(0);
++ final VoxelShape ret = Shapes.create(singleAABB);
++
++ // forward AABB cache
++ if (((VoxelShape)(Object)ret).cachedToAABBs == null) {
++ ((VoxelShape)(Object)ret).cachedToAABBs = this.cachedToAABBs;
++ }
++
++ return ret;
++ } else {
++ // reduce complexity of joins by splitting the merges (old complexity: n^2, new: nlogn)
++
++ // set up flat array so that this merge is done in-place
++ final VoxelShape[] tmp = new VoxelShape[aabbs.size()];
++
++ // initialise as unmerged
++ for (int i = 0, len = aabbs.size(); i < len; ++i) {
++ tmp[i] = Shapes.create(aabbs.get(i));
++ }
++
++ int size = aabbs.size();
++ while (size > 1) {
++ int newSize = 0;
++ for (int i = 0; i < size; i += 2) {
++ final int next = i + 1;
++ if (next >= size) {
++ // nothing to merge with, so leave it for next iteration
++ tmp[newSize++] = tmp[i];
++ break;
++ } else {
++ // merge with adjacent
++ final VoxelShape first = tmp[i];
++ final VoxelShape second = tmp[next];
++
++ tmp[newSize++] = Shapes.joinUnoptimized(first, second, BooleanOp.OR);
++ }
++ }
++ size = newSize;
++ }
++
++ final VoxelShape ret = tmp[0];
++
++ // forward AABB cache
++ if (((VoxelShape)(Object)ret).cachedToAABBs == null) {
++ ((VoxelShape)(Object)ret).cachedToAABBs = this.cachedToAABBs;
++ }
++
++ return ret;
++ }
++ // Paper end - optimise collisions
+ }
+
+ public void forAllEdges(Shapes.DoubleLineConsumer consumer) {
+@@ -127,9 +661,24 @@ public abstract class VoxelShape {
+ }
+
+ public List<AABB> toAabbs() {
+- List<AABB> list = Lists.newArrayList();
+- this.forAllBoxes((x1, y1, z1, x2, y2, z2) -> list.add(new AABB(x1, y1, z1, x2, y2, z2)));
+- return list;
++ // Paper start - optimise collisions
++ ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs = this.cachedToAABBs;
++ if (cachedToAABBs != null) {
++ if (!cachedToAABBs.isOffset()) {
++ return cachedToAABBs.aabbs();
++ }
++
++ // all we need to do is offset the cache
++ cachedToAABBs = cachedToAABBs.removeOffset();
++ // update cache
++ this.cachedToAABBs = cachedToAABBs;
++
++ return cachedToAABBs.aabbs();
++ }
++
++ // make new cache
++ return this.toAabbsUncached();
++ // Paper end - optimise collisions
+ }
+
+ public double min(Direction.Axis axis, double from, double to) {
+@@ -155,42 +704,63 @@ public abstract class VoxelShape {
+ }
+
+ @Nullable
+- public BlockHitResult clip(Vec3 start, Vec3 end, BlockPos pos) {
+- if (this.isEmpty()) {
++ // Paper start - optimise collisions
++ public BlockHitResult clip(final Vec3 from, final Vec3 to, final BlockPos offset) {
++ if (this.isEmpty) {
+ return null;
+- } else {
+- Vec3 vec3 = end.subtract(start);
+- if (vec3.lengthSqr() < 1.0E-7) {
+- return null;
+- } else {
+- Vec3 vec32 = start.add(vec3.scale(0.001));
+- return this.shape
+- .isFullWide(
+- this.findIndex(Direction.Axis.X, vec32.x - (double)pos.getX()),
+- this.findIndex(Direction.Axis.Y, vec32.y - (double)pos.getY()),
+- this.findIndex(Direction.Axis.Z, vec32.z - (double)pos.getZ())
+- )
+- ? new BlockHitResult(vec32, Direction.getNearest(vec3.x, vec3.y, vec3.z).getOpposite(), pos, true)
+- : AABB.clip(this.toAabbs(), start, end, pos);
++ }
++
++ final Vec3 directionOpposite = to.subtract(from);
++ if (directionOpposite.lengthSqr() < ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) {
++ return null;
++ }
++
++ final Vec3 fromBehind = from.add(directionOpposite.scale(0.001));
++ final double fromBehindOffsetX = fromBehind.x - (double)offset.getX();
++ final double fromBehindOffsetY = fromBehind.y - (double)offset.getY();
++ final double fromBehindOffsetZ = fromBehind.z - (double)offset.getZ();
++
++ final AABB singleAABB = this.singleAABBRepresentation;
++ if (singleAABB != null) {
++ if (singleAABB.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) {
++ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true);
+ }
++ return clip(singleAABB, from, to, offset);
++ }
++
++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.strictlyContains((VoxelShape)(Object)this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) {
++ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true);
+ }
++
++ return AABB.clip(((VoxelShape)(Object)this).toAabbs(), from, to, offset);
++ // Paper end - optimise collisions
+ }
+
+- public Optional<Vec3> closestPointTo(Vec3 target) {
+- if (this.isEmpty()) {
++ // Paper start - optimise collisions
++ public Optional<Vec3> closestPointTo(Vec3 point) {
++ if (this.isEmpty) {
+ return Optional.empty();
+- } else {
+- Vec3[] vec3s = new Vec3[1];
+- this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> {
+- double d = Mth.clamp(target.x(), minX, maxX);
+- double e = Mth.clamp(target.y(), minY, maxY);
+- double f = Mth.clamp(target.z(), minZ, maxZ);
+- if (vec3s[0] == null || target.distanceToSqr(d, e, f) < target.distanceToSqr(vec3s[0])) {
+- vec3s[0] = new Vec3(d, e, f);
+- }
+- });
+- return Optional.of(vec3s[0]);
+ }
++
++ Vec3 ret = null;
++ double retDistance = Double.MAX_VALUE;
++
++ final List<AABB> aabbs = this.toAabbs();
++ for (int i = 0, len = aabbs.size(); i < len; ++i) {
++ final AABB aabb = aabbs.get(i);
++ final double x = Mth.clamp(point.x, aabb.minX, aabb.maxX);
++ final double y = Mth.clamp(point.y, aabb.minY, aabb.maxY);
++ final double z = Mth.clamp(point.z, aabb.minZ, aabb.maxZ);
++
++ double dist = point.distanceToSqr(x, y, z);
++ if (dist < retDistance) {
++ ret = new Vec3(x, y, z);
++ retDistance = dist;
++ }
++ }
++
++ return Optional.ofNullable(ret);
++ // Paper end - optimise collisions
+ }
+
+ public VoxelShape getFaceShape(Direction facing) {
+@@ -226,8 +796,29 @@ public abstract class VoxelShape {
+ }
+ }
+
+- public double collide(Direction.Axis axis, AABB box, double maxDist) {
+- return this.collideX(AxisCycle.between(axis, Direction.Axis.X), box, maxDist);
++ // Paper start - optimise collisions
++ public double collide(final Direction.Axis axis, final AABB source, final double source_move) {
++ if (this.isEmpty) {
++ return source_move;
++ }
++ if (Math.abs(source_move) < ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) {
++ return 0.0;
++ }
++ switch (axis) {
++ case X: {
++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.collideX((VoxelShape)(Object)this, source, source_move);
++ }
++ case Y: {
++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.collideY((VoxelShape)(Object)this, source, source_move);
++ }
++ case Z: {
++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.collideZ((VoxelShape)(Object)this, source, source_move);
++ }
++ default: {
++ throw new RuntimeException("Unknown axis: " + axis);
++ }
++ }
++ // Paper end - optimise collisions
+ }
+
+ protected double collideX(AxisCycle axisCycle, AABB box, double maxDist) {
diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java
index 47c2b2da9799690291396effb9e1b06d71efc6fd..c42c0d1e4da30aa15f32d4ca524aeabd26fc50cf 100644
--- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java
diff --git a/patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch b/patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
index 05b7688a22..48f04e61e0 100644
--- a/patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
+++ b/patches/server/0994-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch
@@ -29,10 +29,10 @@ index 02367ef1371dde94ff6c4cd40bd32e800d6ccaaf..7b0fc7135bc107103dcaed6dc0707b18
this.x = x;
this.y = y;
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index a022bbaa16054c56696a7a03e71ef042340d88ec..645bdb59d791016083fa14015d94ea2c0d4d2cf0 100644
+index 6559ca1b3025eae1837986d404369a9b80b988f7..8fa92ac2a96fdfb8ecca2be0edd4186d07f26cc5 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -399,7 +399,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -780,7 +780,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// Paper end
public boolean isInWorldBounds(BlockPos pos) {
@@ -42,10 +42,10 @@ index a022bbaa16054c56696a7a03e71ef042340d88ec..645bdb59d791016083fa14015d94ea2c
public static boolean isInSpawnableBounds(BlockPos pos) {
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-index fb7bdf43fdc4d816b1c1f1f063bc170561c9544f..2822a9b010e6d45f9562950a94f1942784db9784 100644
+index fba548c4e6d589323ec3ea5f6b269a6fe9faf6a1..a7fc4b027cee8e1ed2678be7060040494a65682a 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-@@ -181,6 +181,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
+@@ -189,6 +189,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
return GameEventListenerRegistry.NOOP;
}
diff --git a/patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch b/patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch
index 1f1ac95450..f54307018e 100644
--- a/patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch
+++ b/patches/server/0996-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch
@@ -26,7 +26,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-index 4827998c3b5ad72578de1310ab1c67671c21c5a2..42ff2150543108c393f108767963cde49d08efa8 100644
+index bf787241e33bdc5e8ff3e77ab953c792dc7f83cd..1ac7a3fc583c54aa1113bb575c21c1e5ee482816 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -3774,7 +3774,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
@@ -62,10 +62,10 @@ index bb8e962e63c7a2d931f9bd7f7c002aa35cfa5fd3..0fa131a6c98adb498fc8d534e0e39647
default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) {
// Paper start - Add predicate for blocks when raytracing
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 645bdb59d791016083fa14015d94ea2c0d4d2cf0..d8adcbb64040cbd24df884f1ef79d4dff7f93a43 100644
+index 8fa92ac2a96fdfb8ecca2be0edd4186d07f26cc5..0eeae82b1357c339dc0ea69a74ebbd6896c0c3b2 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -391,10 +391,87 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -772,10 +772,87 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
return null;
}
diff --git a/patches/server/1000-Entity-Activation-Range-2.0.patch b/patches/server/1000-Entity-Activation-Range-2.0.patch
index ddcaef5bad..019c1073c5 100644
--- a/patches/server/1000-Entity-Activation-Range-2.0.patch
+++ b/patches/server/1000-Entity-Activation-Range-2.0.patch
@@ -111,7 +111,7 @@ index eee9aa0d82c7446f3d32a9655abceb5279ea5226..a12864921a35a1fb3b6081d72e650582
} else {
passenger.stopRiding();
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 690d3f669092f0a0a39a93a401c2e8a1626650b4..ffee1fd60e863d1453796ee498fc5a3b13d26de8 100644
+index 4f4b7e738fe604808d837a38d23bf437bc1d5329..abc217e9166bce12590f37aa0e21d6202596e786 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -419,6 +419,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -340,10 +340,10 @@ index 0b7f52021441d633c37543e8ae485e81c292b747..d7f8464bf3eed0e42a5fc7f14a5b243d
+
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index d8adcbb64040cbd24df884f1ef79d4dff7f93a43..0a03a735ccdfa81c0607d5516b93a15eec43f3aa 100644
+index 0eeae82b1357c339dc0ea69a74ebbd6896c0c3b2..6048b243205a7f856fe5169303a4482723e208f9 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -156,6 +156,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -157,6 +157,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public Map<BlockPos, BlockEntity> capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates
public List<ItemEntity> captureDrops;
public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
diff --git a/patches/server/1002-Anti-Xray.patch b/patches/server/1002-Anti-Xray.patch
index 400053becc..c64656ab06 100644
--- a/patches/server/1002-Anti-Xray.patch
+++ b/patches/server/1002-Anti-Xray.patch
@@ -1168,10 +1168,10 @@ index 9b1a6d8351fb473eec75a2fd08fb892b770e3586..0d0b07c9199be9ca0d5ac3feb1d44f14
}
// Paper end - Send empty chunk
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 0a03a735ccdfa81c0607d5516b93a15eec43f3aa..6bbaf4a60190047a36e463c774630bdc1cf049aa 100644
+index 6048b243205a7f856fe5169303a4482723e208f9..f4e84f2be9a8f4852b5c26fc5ac063fa6375a341 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -171,6 +171,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -172,6 +172,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
// Paper end - add paper world config
@@ -1179,24 +1179,24 @@ index 0a03a735ccdfa81c0607d5516b93a15eec43f3aa..6bbaf4a60190047a36e463c774630bdc
public final co.aikar.timings.WorldTimingsHandler timings; // Paper
public static BlockPos lastPhysicsProblem; // Spigot
private org.spigotmc.TickLimiter entityLimiter;
-@@ -262,7 +263,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -639,7 +640,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
- // Paper end - rewrite chunk system
+ // Paper end - optimise collisions
- protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator) { // Paper - create paper world config
+ protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - create paper world config & Anti-Xray
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
this.generator = gen;
-@@ -345,6 +346,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime);
- this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime);
- this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system
+@@ -726,6 +727,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+ this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this);
+ this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this);
+ // Paper end - optimise collisions
+ this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
}
// Paper start - Cancel hit for vanished players
-@@ -622,6 +624,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1003,6 +1005,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
// CraftBukkit end
BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
@@ -1205,10 +1205,10 @@ index 0a03a735ccdfa81c0607d5516b93a15eec43f3aa..6bbaf4a60190047a36e463c774630bdc
if (iblockdata1 == null) {
// CraftBukkit start - remove blockstate if failed (or the same)
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-index 2822a9b010e6d45f9562950a94f1942784db9784..97f8ef86a0e398b7e4aa3445d5e413addbe3a9e3 100644
+index a7fc4b027cee8e1ed2678be7060040494a65682a..75c8125e20b70433fe9d143a3193d821043327c3 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java
-@@ -155,7 +155,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
+@@ -159,7 +159,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
}
}
@@ -1217,7 +1217,7 @@ index 2822a9b010e6d45f9562950a94f1942784db9784..97f8ef86a0e398b7e4aa3445d5e413ad
// CraftBukkit start
this.biomeRegistry = biomeRegistry;
// Paper start - rewrite chunk system
-@@ -168,10 +168,10 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
+@@ -176,10 +176,10 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom
public final Registry<Biome> biomeRegistry;
// CraftBukkit end
@@ -1231,7 +1231,7 @@ index 2822a9b010e6d45f9562950a94f1942784db9784..97f8ef86a0e398b7e4aa3445d5e413ad
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 443e5e1b1c0e7c93f61c1905c78c29a17860989c..b2a06fc1192cd6050d6d7ea8620a4fa5a12182cc 100644
+index 214bb6759d2670042d87b2fd0aae41df600a95ee..d4429eedd9164d4b7c367345a8c662a1129d0430 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -91,7 +91,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
@@ -1244,10 +1244,10 @@ index 443e5e1b1c0e7c93f61c1905c78c29a17860989c..b2a06fc1192cd6050d6d7ea8620a4fa5
this.level = (ServerLevel) world; // CraftBukkit - type
this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap();
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-index 90d1c3e23e753c29660f7d993b3c90ac022941c3..f2e11bff414b521295bde721e01ae2166a6a3fd6 100644
+index 1f767bb9342611a66c1be79ee23df39a4cad1102..d4bd71f70f80ea74947e6d478ff636be2ab7d432 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-@@ -33,9 +33,12 @@ public class LevelChunkSection {
+@@ -42,9 +42,12 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.collis
this.recalcBlockCounts();
}
@@ -1263,7 +1263,7 @@ index 90d1c3e23e753c29660f7d993b3c90ac022941c3..f2e11bff414b521295bde721e01ae216
}
public BlockState getBlockState(int x, int y, int z) {
-@@ -172,10 +175,13 @@ public class LevelChunkSection {
+@@ -209,10 +212,13 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.collis
this.biomes = datapaletteblock;
}
@@ -1281,7 +1281,7 @@ index 90d1c3e23e753c29660f7d993b3c90ac022941c3..f2e11bff414b521295bde721e01ae216
public int getSerializedSize() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-index fa9df6ebcd90d4e9e5836a37212b1f60665783b1..926c81a25180d508d662eb3fa35f771636164694 100644
+index 339cac6b34b9f2f53852cfcee821bec9e0286c50..13d3c877b006a4975e7370713e3919c661e7890f 100644
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -28,6 +28,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
@@ -1289,7 +1289,7 @@ index fa9df6ebcd90d4e9e5836a37212b1f60665783b1..926c81a25180d508d662eb3fa35f7716
private final PaletteResize<T> dummyPaletteResize = (newSize, added) -> 0;
public final IdMap<T> registry;
+ private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values
- private volatile PalettedContainer.Data<T> data;
+ public volatile PalettedContainer.Data<T> data; // Paper - optimise collisions - public
private final PalettedContainer.Strategy strategy;
// private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
@@ -40,14 +41,19 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
diff --git a/patches/server/1004-Add-Alternate-Current-redstone-implementation.patch b/patches/server/1004-Add-Alternate-Current-redstone-implementation.patch
index 6554a372c9..cbf27f48a7 100644
--- a/patches/server/1004-Add-Alternate-Current-redstone-implementation.patch
+++ b/patches/server/1004-Add-Alternate-Current-redstone-implementation.patch
@@ -2035,10 +2035,10 @@ index 8a3f58e6dfdb0af767be334087748f93c06ec797..7ba34da235ea536b929e1c8bc2cc39e4
EntityCallbacks() {}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 6bbaf4a60190047a36e463c774630bdc1cf049aa..0ef67317a922f28202c84c7a1f81f3c534d2b838 100644
+index f4e84f2be9a8f4852b5c26fc5ac063fa6375a341..3ad4c2d22c191bfc0e25a31661daebc1161e5656 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -1581,4 +1581,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1969,4 +1969,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
}
// Paper end - notify observers even if grow failed
diff --git a/patches/server/1011-Optimize-Voxel-Shape-Merging.patch b/patches/server/1011-Optimize-Voxel-Shape-Merging.patch
index 4ec779560c..114ee7cb28 100644
--- a/patches/server/1011-Optimize-Voxel-Shape-Merging.patch
+++ b/patches/server/1011-Optimize-Voxel-Shape-Merging.patch
@@ -68,10 +68,10 @@ index e164c524aef4fa81fe96ac43454eecff1c38b9c1..9cfbbc61fcfc678f0988d6d45c7994d1
this.firstIndices = new int[k];
this.secondIndices = new int[k];
diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-index 0fdd2cdd8d215ca1523eda8ad7316cdd5f41a6b5..86df4ef44d0a5107ee929dfd40d8ccb0779e8bfc 100644
+index 7ede56f77af1d40e10fde2e660d5794e4b37dc5d..c348171c150bf69d24303d0862e45ab78baddcab 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-@@ -286,9 +286,21 @@ public final class Shapes {
+@@ -343,9 +343,21 @@ public final class Shapes {
}
@VisibleForTesting
@@ -94,7 +94,7 @@ index 0fdd2cdd8d215ca1523eda8ad7316cdd5f41a6b5..86df4ef44d0a5107ee929dfd40d8ccb0
if (first instanceof CubePointRange && second instanceof CubePointRange) {
long l = lcm(i, j);
if ((long)size * l <= 256L) {
-@@ -296,15 +308,22 @@ public final class Shapes {
+@@ -353,15 +365,22 @@ public final class Shapes {
}
}
diff --git a/patches/server/1024-Optimise-random-block-ticking.patch b/patches/server/1024-Optimise-random-block-ticking.patch
deleted file mode 100644
index 8f81d5dd83..0000000000
--- a/patches/server/1024-Optimise-random-block-ticking.patch
+++ /dev/null
@@ -1,456 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sun, 20 Jun 2021 16:19:26 -0700
-Subject: [PATCH] Optimise random block ticking
-
-Massive performance improvement for random block ticking.
-The performance increase comes from the fact that the vast
-majority of attempted block ticks (~95% in my testing) fail
-because the randomly selected block is not tickable.
-
-Now only tickable blocks are targeted, however this means that
-the maximum number of block ticks occurs per chunk. However,
-not all chunks are going to be targeted. The percent chance
-of a chunk being targeted is based on how many tickable blocks
-are in the chunk.
-This means that while block ticks are spread out less, the
-total number of blocks ticked per world tick remains the same.
-Therefore, the chance of a random tickable block being ticked
-remains the same.
-
-diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..7d93652c1abbb6aee6eb7c26cf35d4d032ef7b69
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java
-@@ -0,0 +1,65 @@
-+package io.papermc.paper.util.math;
-+
-+import net.minecraft.util.RandomSource;
-+import net.minecraft.world.level.levelgen.LegacyRandomSource;
-+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
-+import org.checkerframework.checker.nullness.qual.NonNull;
-+import org.checkerframework.framework.qual.DefaultQualifier;
-+
-+@DefaultQualifier(NonNull.class)
-+public final class ThreadUnsafeRandom extends LegacyRandomSource {
-+
-+ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them.
-+ private static final long multiplier = 0x5DEECE66DL;
-+ private static final long addend = 0xBL;
-+ private static final long mask = (1L << 48) - 1;
-+
-+ private static long initialScramble(long seed) {
-+ return (seed ^ multiplier) & mask;
-+ }
-+
-+ private long seed;
-+
-+ public ThreadUnsafeRandom(long seed) {
-+ super(seed);
-+ }
-+
-+ @Override
-+ public RandomSource fork() {
-+ return new ThreadUnsafeRandom(this.nextLong());
-+ }
-+
-+ @Override
-+ public PositionalRandomFactory forkPositional() {
-+ throw new UnsupportedOperationException();
-+ }
-+
-+ @Override
-+ public void setSeed(long seed) {
-+ // note: called by Random constructor
-+ this.seed = initialScramble(seed);
-+ }
-+
-+ @Override
-+ public int next(int bits) {
-+ // avoid the expensive CAS logic used by superclass
-+ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits));
-+ }
-+
-+ // Taken from
-+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
-+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c
-+ // Original license is public domain
-+ public static int fastRandomBounded(final long randomInteger, final long limit) {
-+ // randomInteger must be [0, pow(2, 32))
-+ // limit must be [0, pow(2, 32))
-+ return (int)((randomInteger * limit) >>> 32);
-+ }
-+
-+ @Override
-+ public int nextInt(int bound) {
-+ // yes this breaks random's spec
-+ // however there's nothing that uses this class that relies on it
-+ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound);
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 7ba34da235ea536b929e1c8bc2cc39e48b33f5aa..2a752deb8cc44b49588be37198fe394a9c95683e 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -819,6 +819,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
- entityplayer.stopSleepInBed(false, false);
- });
- }
-+ // Paper start - optimise random block ticking
-+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
-+ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong());
-+ // Paper end
-
- public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
- ChunkPos chunkcoordintpair = chunk.getPos();
-@@ -828,8 +832,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
- ProfilerFiller gameprofilerfiller = this.getProfiler();
-
- gameprofilerfiller.push("thunder");
-+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
-+
- if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder
-- BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15));
-+ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper
-
- if (this.isRainingAt(blockposition)) {
- DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
-@@ -861,7 +867,10 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
- if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow
- for (int l = 0; l < randomTickSpeed; ++l) {
- if (this.random.nextInt(48) == 0) {
-- this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15));
-+ // Paper start
-+ this.getRandomBlockPosition(j, 0, k, 15, blockposition);
-+ this.tickPrecipitation(blockposition, chunk);
-+ // Paper end
- }
- }
- } // Paper - Option to disable ice and snow
-@@ -869,36 +878,37 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
- gameprofilerfiller.popPush("tickBlocks");
- timings.chunkTicksBlocks.startTiming(); // Paper
- if (randomTickSpeed > 0) {
-- LevelChunkSection[] achunksection = chunk.getSections();
--
-- for (int i1 = 0; i1 < achunksection.length; ++i1) {
-- LevelChunkSection chunksection = achunksection[i1];
--
-- if (chunksection.isRandomlyTicking()) {
-- int j1 = chunk.getSectionYFromSectionIndex(i1);
-- int k1 = SectionPos.sectionToBlockCoord(j1);
--
-- for (int l1 = 0; l1 < randomTickSpeed; ++l1) {
-- BlockPos blockposition1 = this.getBlockRandomPos(j, k1, k, 15);
--
-- gameprofilerfiller.push("randomTick");
-- BlockState iblockdata = chunksection.getBlockState(blockposition1.getX() - j, blockposition1.getY() - k1, blockposition1.getZ() - k);
--
-- if (iblockdata.isRandomlyTicking()) {
-- iblockdata.randomTick(this, blockposition1, this.random);
-- }
-+ // Paper start - optimize random block ticking
-+ LevelChunkSection[] sections = chunk.getSections();
-+ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this);
-+ for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
-+ LevelChunkSection section = sections[sectionIndex];
-+ if (section == null || section.tickingList.size() == 0) continue;
-+
-+ int yPos = (sectionIndex + minSection) << 4;
-+ for (int a = 0; a < randomTickSpeed; ++a) {
-+ int tickingBlocks = section.tickingList.size();
-+ int index = this.randomTickRandom.nextInt(16 * 16 * 16);
-+ if (index >= tickingBlocks) {
-+ continue;
-+ }
-
-- FluidState fluid = iblockdata.getFluidState();
-+ long raw = section.tickingList.getRaw(index);
-+ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw);
-+ int randomX = location & 15;
-+ int randomY = ((location >>> (4 + 4)) & 255) | yPos;
-+ int randomZ = (location >>> 4) & 15;
-
-- if (fluid.isRandomlyTicking()) {
-- fluid.randomTick(this, blockposition1, this.random);
-- }
-+ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ);
-+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw);
-
-- gameprofilerfiller.pop();
-- }
-+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom);
- }
-+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock).
-+ // TODO CHECK ON UPDATE (ping the Canadian)
- }
- }
-+ // Paper end - optimise random block ticking
-
- timings.chunkTicksBlocks.stopTiming(); // Paper
- gameprofilerfiller.pop();
-@@ -906,17 +916,25 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
-
- @VisibleForTesting
- public void tickPrecipitation(BlockPos pos) {
-- BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos);
-- BlockPos blockposition2 = blockposition1.below();
-+ // Paper start - optimise chunk ticking
-+ tickPrecipitation(pos.mutable(), this.getChunkAt(pos));
-+ }
-+ public void tickPrecipitation(BlockPos.MutableBlockPos blockposition1, final LevelChunk chunk) {
-+ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition1.getX() & 15, blockposition1.getZ() & 15) + 1;
-+ int downY = normalY - 1;
-+ blockposition1.setY(normalY);
-+ // Paper end - optimise chunk ticking
- Biome biomebase = (Biome) this.getBiome(blockposition1).value();
-
-- if (biomebase.shouldFreeze(this, blockposition2)) {
-- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
-+ blockposition1.setY(downY);
-+ if (biomebase.shouldFreeze(this, blockposition1)) {
-+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
- }
-
- if (this.isRaining()) {
- int i = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT);
-
-+ blockposition1.setY(normalY); // Paper - optimise chunk ticking
- if (i > 0 && biomebase.shouldSnow(this, blockposition1)) {
- BlockState iblockdata = this.getBlockState(blockposition1);
-
-@@ -934,12 +952,13 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
- }
- }
-
-- Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition2);
-+ blockposition1.setY(downY); // Paper - optimise chunk ticking
-+ Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition1); // Paper - optimise chunk ticking
-
- if (biomebase_precipitation != Biome.Precipitation.NONE) {
-- BlockState iblockdata2 = this.getBlockState(blockposition2);
-+ BlockState iblockdata2 = this.getBlockState(blockposition1); // Paper - optimise chunk ticking
-
-- iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition2, biomebase_precipitation);
-+ iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition1, biomebase_precipitation); // Paper - optimise chunk ticking
- }
- }
-
-diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java
-index 68648c5a5e3ff079f832092af0f2f801c42d1ede..8bafd5fd7499ba4a04bf706cfd1e156073716e21 100644
---- a/src/main/java/net/minecraft/util/BitStorage.java
-+++ b/src/main/java/net/minecraft/util/BitStorage.java
-@@ -20,4 +20,15 @@ public interface BitStorage {
- void unpack(int[] out);
-
- BitStorage copy();
-+
-+ // Paper start
-+ void forEach(DataBitConsumer consumer);
-+
-+ @FunctionalInterface
-+ interface DataBitConsumer {
-+
-+ void accept(int location, int data);
-+
-+ }
-+ // Paper end
- }
-diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java
-index 9f438d9c6eb05e43d24e4af68188a3d4c46a938c..8d7d763bf51cac556057645e6169c9447993189b 100644
---- a/src/main/java/net/minecraft/util/SimpleBitStorage.java
-+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java
-@@ -315,6 +315,28 @@ public class SimpleBitStorage implements BitStorage {
- return this.bits;
- }
-
-+ // Paper start
-+ @Override
-+ public final void forEach(DataBitConsumer consumer) {
-+ int i = 0;
-+ long[] along = this.data;
-+ int j = along.length;
-+
-+ for (int k = 0; k < j; ++k) {
-+ long l = along[k];
-+
-+ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) {
-+ consumer.accept(i, (int) (l & this.mask));
-+ l >>= this.bits;
-+ ++i;
-+ if (i >= this.size) {
-+ return;
-+ }
-+ }
-+ }
-+ }
-+ // Paper end
-+
- @Override
- public void getAll(IntConsumer action) {
- int i = 0;
-diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java
-index 50040c497a819cd1229042ab3cb057d34a32cacc..01f5b946fabbe34f31110e75973dab9f39897346 100644
---- a/src/main/java/net/minecraft/util/ZeroBitStorage.java
-+++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java
-@@ -46,6 +46,15 @@ public class ZeroBitStorage implements BitStorage {
- return 0;
- }
-
-+ // Paper start
-+ @Override
-+ public void forEach(DataBitConsumer consumer) {
-+ for(int i = 0; i < this.size; ++i) {
-+ consumer.accept(i, 0);
-+ }
-+ }
-+ // Paper end
-+
- @Override
- public void getAll(IntConsumer action) {
- for (int i = 0; i < this.size; i++) {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-index 4bfa947531c4a67989e18032754dabf4c69e989c..caf4120721be8f2f7e2d737abbf73296cbe170b5 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-@@ -88,7 +88,7 @@ public class Turtle extends Animal {
- }
-
- public void setHomePos(BlockPos pos) {
-- this.entityData.set(Turtle.HOME_POS, pos);
-+ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos...
- }
-
- public BlockPos getHomePos() {
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 0ef67317a922f28202c84c7a1f81f3c534d2b838..81f28afeea34d5e4ebb7f8fa7e3f5f124069a5ab 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -1500,10 +1500,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- public abstract RecipeManager getRecipeManager();
-
- public BlockPos getBlockRandomPos(int x, int y, int z, int l) {
-+ // Paper start - allow use of mutable pos
-+ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos();
-+ this.getRandomBlockPosition(x, y, z, l, ret);
-+ return ret.immutable();
-+ }
-+ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) {
-+ // Paper end
- this.randValue = this.randValue * 3 + 1013904223;
- int i1 = this.randValue >> 2;
-
-- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15));
-+ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call
-+ return out; // Paper
- }
-
- public boolean noSave() {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-index f2e11bff414b521295bde721e01ae2166a6a3fd6..8cd6c1d838e0332125fde3fc36475034aa4effa0 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-@@ -25,6 +25,7 @@ public class LevelChunkSection {
- public final PalettedContainer<BlockState> states;
- // CraftBukkit start - read/write
- private PalettedContainer<Holder<Biome>> biomes;
-+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
-
- public LevelChunkSection(PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) {
- // CraftBukkit end
-@@ -77,6 +78,9 @@ public class LevelChunkSection {
- --this.nonEmptyBlockCount;
- if (iblockdata1.isRandomlyTicking()) {
- --this.tickingBlockCount;
-+ // Paper start
-+ this.tickingList.remove(x, y, z);
-+ // Paper end
- }
- }
-
-@@ -88,6 +92,9 @@ public class LevelChunkSection {
- ++this.nonEmptyBlockCount;
- if (state.isRandomlyTicking()) {
- ++this.tickingBlockCount;
-+ // Paper start
-+ this.tickingList.add(x, y, z, state);
-+ // Paper end
- }
- }
-
-@@ -115,40 +122,34 @@ public class LevelChunkSection {
- }
-
- public void recalcBlockCounts() {
-- class a implements PalettedContainer.CountConsumer<BlockState> {
--
-- public int nonEmptyBlockCount;
-- public int tickingBlockCount;
-- public int tickingFluidCount;
--
-- a(final LevelChunkSection chunksection) {}
--
-- public void accept(BlockState iblockdata, int i) {
-+ // Paper start - unfuck this
-+ this.tickingList.clear();
-+ this.nonEmptyBlockCount = 0;
-+ this.tickingBlockCount = 0;
-+ this.tickingFluidCount = 0;
-+ // Don't run this on clearly empty sections
-+ if (this.maybeHas((BlockState state) -> !state.isAir() || !state.getFluidState().isEmpty())) {
-+ this.states.forEachLocation((BlockState iblockdata, int i) -> {
- FluidState fluid = iblockdata.getFluidState();
-
- if (!iblockdata.isAir()) {
-- this.nonEmptyBlockCount += i;
-+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
- if (iblockdata.isRandomlyTicking()) {
-- this.tickingBlockCount += i;
-+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1);
-+ this.tickingList.add(i, iblockdata);
- }
- }
-
- if (!fluid.isEmpty()) {
-- this.nonEmptyBlockCount += i;
-+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1);
- if (fluid.isRandomlyTicking()) {
-- this.tickingFluidCount += i;
-+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1);
- }
- }
-
-- }
-+ });
- }
--
-- a a0 = new a(this);
--
-- this.states.count(a0);
-- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount;
-- this.tickingBlockCount = (short) a0.tickingBlockCount;
-- this.tickingFluidCount = (short) a0.tickingFluidCount;
-+ // Paper end - unfuck this
- }
-
- public PalettedContainer<BlockState> getStates() {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-index 926c81a25180d508d662eb3fa35f771636164694..81368bf186365878db2e1ed305bb7bf36c26f61f 100644
---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-@@ -381,6 +381,14 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
- }
- }
-
-+ // Paper start
-+ public void forEachLocation(PalettedContainer.CountConsumer<T> consumer) {
-+ this.data.storage.forEach((int location, int data) -> {
-+ consumer.accept(this.data.palette.valueFor(data), location);
-+ });
-+ }
-+ // Paper end
-+
- @FunctionalInterface
- public interface CountConsumer<T> {
- void accept(T object, int count);
diff --git a/patches/server/1025-Registry-Modification-API.patch b/patches/server/1024-Registry-Modification-API.patch
index dc555c750d..dc555c750d 100644
--- a/patches/server/1025-Registry-Modification-API.patch
+++ b/patches/server/1024-Registry-Modification-API.patch
diff --git a/patches/server/1026-Add-registry-entry-and-builders.patch b/patches/server/1025-Add-registry-entry-and-builders.patch
index 1b920c14d0..1b920c14d0 100644
--- a/patches/server/1026-Add-registry-entry-and-builders.patch
+++ b/patches/server/1025-Add-registry-entry-and-builders.patch
diff --git a/patches/server/1027-Improved-Watchdog-Support.patch b/patches/server/1026-Improved-Watchdog-Support.patch
index cacfe63010..f7fdd3fd7b 100644
--- a/patches/server/1027-Improved-Watchdog-Support.patch
+++ b/patches/server/1026-Improved-Watchdog-Support.patch
@@ -262,7 +262,7 @@ index 7d2896918ff5fed37e5de5a22c37b0c7f32634a8..7d82cc6b847124cf4225428ba3103095
}
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index bad83045ff9409678a0699cdc0781efd8ef30a44..22c705e4e3f852102c46ae0c885feb1b085545b6 100644
+index 369b3485f452ac157b3ebf88b4f1970605d302d2..5f20606cc2c79ad9a4c4d4d6c9e6a2a31a88b282 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -604,7 +604,7 @@ public abstract class PlayerList {
@@ -287,10 +287,10 @@ index 2510589400b3012b827efcab477c6483d9d55901..43487a9ee202c5b0e5a416519939111f
}
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 81f28afeea34d5e4ebb7f8fa7e3f5f124069a5ab..0d202ce8eb88bfdb8ca3306593d758fa483d8612 100644
+index 3ad4c2d22c191bfc0e25a31661daebc1161e5656..c1f392cd7928a142192c87cf46ef2a4fc2495562 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -994,6 +994,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
+@@ -1375,6 +1375,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
try {
tickConsumer.accept(entity);
} catch (Throwable throwable) {
@@ -299,10 +299,10 @@ index 81f28afeea34d5e4ebb7f8fa7e3f5f124069a5ab..0d202ce8eb88bfdb8ca3306593d758fa
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
MinecraftServer.LOGGER.error(msg, throwable);
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index b2a06fc1192cd6050d6d7ea8620a4fa5a12182cc..d388fbcbff63928f0e9140c02400a63ba8f19d9c 100644
+index d4429eedd9164d4b7c367345a8c662a1129d0430..db55e9cc3d42ba01f75f6697924baaeccb564b90 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -1006,6 +1006,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
+@@ -1028,6 +1028,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
gameprofilerfiller.pop();
} catch (Throwable throwable) {
@@ -344,7 +344,7 @@ index c6e8441e299f477ddb22c1ce2618710763978f1a..e8e93538dfd71de86515d9405f728db1
}
}
diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java
-index f3fa0340babfc5eb627066115164e2d885c602c2..9ce945dce3ac8aa1d4eb55c41115f989b985d46d 100644
+index 39e56b95aaafbcd8ebe68fdefaace83702e9510d..3ba27955548a26367a87d6b87c3c61beb299dfb9 100644
--- a/src/main/java/org/spigotmc/RestartCommand.java
+++ b/src/main/java/org/spigotmc/RestartCommand.java
@@ -139,7 +139,7 @@ public class RestartCommand extends Command
diff --git a/patches/server/1028-Proxy-ItemStack-to-CraftItemStack.patch b/patches/server/1027-Proxy-ItemStack-to-CraftItemStack.patch
index ad3c75feb2..ad3c75feb2 100644
--- a/patches/server/1028-Proxy-ItemStack-to-CraftItemStack.patch
+++ b/patches/server/1027-Proxy-ItemStack-to-CraftItemStack.patch
diff --git a/patches/server/1029-Make-a-PDC-view-accessible-directly-from-ItemStack.patch b/patches/server/1028-Make-a-PDC-view-accessible-directly-from-ItemStack.patch
index 95338b76bf..95338b76bf 100644
--- a/patches/server/1029-Make-a-PDC-view-accessible-directly-from-ItemStack.patch
+++ b/patches/server/1028-Make-a-PDC-view-accessible-directly-from-ItemStack.patch
diff --git a/patches/server/1030-Prioritize-Minecraft-commands-in-function-parsing-an.patch b/patches/server/1029-Prioritize-Minecraft-commands-in-function-parsing-an.patch
index 6e36d9a7b3..6e36d9a7b3 100644
--- a/patches/server/1030-Prioritize-Minecraft-commands-in-function-parsing-an.patch
+++ b/patches/server/1029-Prioritize-Minecraft-commands-in-function-parsing-an.patch
diff --git a/patches/server/1031-optimize-dirt-and-snow-spreading.patch b/patches/server/1030-optimize-dirt-and-snow-spreading.patch
index 49de7fcab9..49de7fcab9 100644
--- a/patches/server/1031-optimize-dirt-and-snow-spreading.patch
+++ b/patches/server/1030-optimize-dirt-and-snow-spreading.patch
diff --git a/patches/server/1032-Fix-NPE-for-Jukebox-setRecord.patch b/patches/server/1031-Fix-NPE-for-Jukebox-setRecord.patch
index e15a77ee75..e15a77ee75 100644
--- a/patches/server/1032-Fix-NPE-for-Jukebox-setRecord.patch
+++ b/patches/server/1031-Fix-NPE-for-Jukebox-setRecord.patch
diff --git a/patches/server/1033-Fix-CraftWorld-isChunkGenerated.patch b/patches/server/1032-Fix-CraftWorld-isChunkGenerated.patch
index f91e8b2bd0..f91e8b2bd0 100644
--- a/patches/server/1033-Fix-CraftWorld-isChunkGenerated.patch
+++ b/patches/server/1032-Fix-CraftWorld-isChunkGenerated.patch
diff --git a/patches/server/1034-Add-debug-for-chunk-system-unload-crash.patch b/patches/server/1033-Add-debug-for-chunk-system-unload-crash.patch
index 401b927f05..401b927f05 100644
--- a/patches/server/1034-Add-debug-for-chunk-system-unload-crash.patch
+++ b/patches/server/1033-Add-debug-for-chunk-system-unload-crash.patch
diff --git a/patches/server/1035-fix-horse-inventories.patch b/patches/server/1034-fix-horse-inventories.patch
index e8388a0c72..e8388a0c72 100644
--- a/patches/server/1035-fix-horse-inventories.patch
+++ b/patches/server/1034-fix-horse-inventories.patch
diff --git a/patches/server/1036-Only-call-EntityDamageEvents-before-actuallyHurt.patch b/patches/server/1035-Only-call-EntityDamageEvents-before-actuallyHurt.patch
index f91219dbe1..f91219dbe1 100644
--- a/patches/server/1036-Only-call-EntityDamageEvents-before-actuallyHurt.patch
+++ b/patches/server/1035-Only-call-EntityDamageEvents-before-actuallyHurt.patch
diff --git a/patches/server/1037-Fix-entity-tracker-desync-when-new-players-are-added.patch b/patches/server/1036-Fix-entity-tracker-desync-when-new-players-are-added.patch
index 294794eaea..024bf5125d 100644
--- a/patches/server/1037-Fix-entity-tracker-desync-when-new-players-are-added.patch
+++ b/patches/server/1036-Fix-entity-tracker-desync-when-new-players-are-added.patch
@@ -60,7 +60,7 @@ index 344966d3deb640eb99bc9c9e318e6e6780421907..6df79aab2f0a75bbe347dc92e9ed5d62
} else if (this.seenBy.remove(player.connection)) {
this.serverEntity.removePairing(player);
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
-index 1d849ce4e2c85f149af25318b8ffb6dcef6c6788..b4971813d267dffc6507502345e5c8b991541eca 100644
+index 12d86f27d04bffed8c3844e36b42fbc2f84dacff..8ea2f24695f5dad55e21f238b69442513e7a90c6 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -96,6 +96,13 @@ public class ServerEntity {
@@ -75,9 +75,9 @@ index 1d849ce4e2c85f149af25318b8ffb6dcef6c6788..b4971813d267dffc6507502345e5c8b9
+ // Paper end - fix desync when a player is added to the tracker
+
public void sendChanges() {
- List<Entity> list = this.entity.getPassengers();
-
-@@ -140,7 +147,7 @@ public class ServerEntity {
+ // Paper start - optimise collisions
+ if (((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this.entity).moonrise$isHardColliding()) {
+@@ -145,7 +152,7 @@ public class ServerEntity {
}
}
@@ -86,7 +86,7 @@ index 1d849ce4e2c85f149af25318b8ffb6dcef6c6788..b4971813d267dffc6507502345e5c8b9
int i;
int j;
-@@ -180,7 +187,7 @@ public class ServerEntity {
+@@ -185,7 +192,7 @@ public class ServerEntity {
long i1 = this.positionCodec.encodeZ(vec3d);
boolean flag6 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
@@ -95,7 +95,7 @@ index 1d849ce4e2c85f149af25318b8ffb6dcef6c6788..b4971813d267dffc6507502345e5c8b9
if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) {
if (flag2) {
packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.onGround());
-@@ -244,6 +251,7 @@ public class ServerEntity {
+@@ -249,6 +256,7 @@ public class ServerEntity {
}
this.entity.hasImpulse = false;