From e83e6803211b823482e111f22fbe3f0c2f8fa861 Mon Sep 17 00:00:00 2001 From: TonytheMacaroni Date: Sat, 4 Nov 2023 18:00:51 -0400 Subject: [PATCH] Add predicate for block when raytracing (#9691) * Add predicate for block data when raytracing blocks * Match based on block, instead of block data * Use instanceof instead of casting * Use Position instead of Location, add overload for rayTraceEntities * Implement requested changes * Invert predicate * Cleanup --- patches/api/Add-Position.patch | 12 ++ ...predicate-for-blocks-when-raytracing.patch | 119 ++++++++++++++++++ ...predicate-for-blocks-when-raytracing.patch | 116 +++++++++++++++++ patches/server/MC-Utils.patch | 4 + 4 files changed, 251 insertions(+) create mode 100644 patches/api/Add-predicate-for-blocks-when-raytracing.patch create mode 100644 patches/server/Add-predicate-for-blocks-when-raytracing.patch diff --git a/patches/api/Add-Position.patch b/patches/api/Add-Position.patch index a7f40dd4a7..648c1c1bf5 100644 --- a/patches/api/Add-Position.patch +++ b/patches/api/Add-Position.patch @@ -273,6 +273,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + boolean isFine(); + + /** ++ * Checks if each component of this position is finite. ++ */ ++ default boolean isFinite() { ++ return Double.isFinite(this.x()) && Double.isFinite(this.y()) && Double.isFinite(this.z()); ++ } ++ ++ /** + * Returns a position offset by the specified amounts. + * + * @param x x value to offset @@ -415,6 +422,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override ++ public boolean isFinite() { ++ return io.papermc.paper.math.FinePosition.super.isFinite() && Float.isFinite(this.getYaw()) && Float.isFinite(this.getPitch()); ++ } ++ ++ @Override + public @NotNull Location toLocation(@NotNull World world) { + return new Location(world, this.x(), this.y(), this.z(), this.getYaw(), this.getPitch()); + } diff --git a/patches/api/Add-predicate-for-blocks-when-raytracing.patch b/patches/api/Add-predicate-for-blocks-when-raytracing.patch new file mode 100644 index 0000000000..20f69eca0c --- /dev/null +++ b/patches/api/Add-predicate-for-blocks-when-raytracing.patch @@ -0,0 +1,119 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TonytheMacaroni +Date: Wed, 6 Sep 2023 19:24:53 -0400 +Subject: [PATCH] Add predicate for blocks when raytracing + + +diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/World.java ++++ b/src/main/java/org/bukkit/World.java +@@ -0,0 +0,0 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient + @Nullable + public RayTraceResult rayTraceEntities(@NotNull Location start, @NotNull Vector direction, double maxDistance, double raySize, @Nullable Predicate filter); + ++ // Paper start ++ /** ++ * Performs a ray trace that checks for entity collisions. ++ *

++ * This may not consider entities in currently unloaded chunks. Some ++ * implementations may impose artificial restrictions on the maximum ++ * distance. ++ * ++ * @param start the start position ++ * @param direction the ray direction ++ * @param maxDistance the maximum distance ++ * @param raySize entity bounding boxes will be uniformly expanded (or ++ * shrinked) by this value before doing collision checks ++ * @param filter only entities that fulfill this predicate are considered, ++ * or null to consider all entities ++ * @return the closest ray trace hit result, or null if there ++ * is no hit ++ */ ++ @Nullable ++ public RayTraceResult rayTraceEntities(@NotNull io.papermc.paper.math.Position start, @NotNull Vector direction, double maxDistance, double raySize, @Nullable Predicate filter); ++ // Paper end ++ + /** + * Performs a ray trace that checks for block collisions using the blocks' + * precise collision shapes. +@@ -0,0 +0,0 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient + @Nullable + public RayTraceResult rayTraceBlocks(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks); + ++ // Paper start ++ /** ++ * Performs a ray trace that checks for block collisions using the blocks' ++ * precise collision shapes. ++ *

++ * If collisions with passable blocks are ignored, fluid collisions are ++ * ignored as well regardless of the fluid collision mode. ++ *

++ * Portal blocks are only considered passable if the ray starts within ++ * them. Apart from that collisions with portal blocks will be considered ++ * even if collisions with passable blocks are otherwise ignored. ++ *

++ * This may cause loading of chunks! Some implementations may impose ++ * artificial restrictions on the maximum distance. ++ * ++ * @param start the start position ++ * @param direction the ray direction ++ * @param maxDistance the maximum distance ++ * @param fluidCollisionMode the fluid collision mode ++ * @param ignorePassableBlocks whether to ignore passable but collidable ++ * blocks (ex. tall grass, signs, fluids, ..) ++ * @param canCollide predicate for blocks the ray can potentially collide ++ * with, or null to consider all blocks ++ * @return the ray trace hit result, or null if there is no hit ++ */ ++ @Nullable ++ public RayTraceResult rayTraceBlocks(@NotNull io.papermc.paper.math.Position start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, @Nullable Predicate canCollide); ++ // Paper end ++ + /** + * Performs a ray trace that checks for both block and entity collisions. + *

+@@ -0,0 +0,0 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient + @Nullable + public RayTraceResult rayTrace(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, @Nullable Predicate filter); + ++ // Paper start ++ /** ++ * Performs a ray trace that checks for both block and entity collisions. ++ *

++ * Block collisions use the blocks' precise collision shapes. The ++ * raySize parameter is only taken into account for entity ++ * collision checks. ++ *

++ * If collisions with passable blocks are ignored, fluid collisions are ++ * ignored as well regardless of the fluid collision mode. ++ *

++ * Portal blocks are only considered passable if the ray starts within them. ++ * Apart from that collisions with portal blocks will be considered even if ++ * collisions with passable blocks are otherwise ignored. ++ *

++ * This may cause loading of chunks! Some implementations may impose ++ * artificial restrictions on the maximum distance. ++ * ++ * @param start the start position ++ * @param direction the ray direction ++ * @param maxDistance the maximum distance ++ * @param fluidCollisionMode the fluid collision mode ++ * @param ignorePassableBlocks whether to ignore passable but collidable ++ * blocks (ex. tall grass, signs, fluids, ..) ++ * @param raySize entity bounding boxes will be uniformly expanded (or ++ * shrinked) by this value before doing collision checks ++ * @param filter only entities that fulfill this predicate are considered, ++ * or null to consider all entities ++ * @param canCollide predicate for blocks the ray can potentially collide ++ * with, or null to consider all blocks ++ * @return the closest ray trace hit result with either a block or an ++ * entity, or null if there is no hit ++ */ ++ @Nullable ++ public RayTraceResult rayTrace(@NotNull io.papermc.paper.math.Position start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, @Nullable Predicate filter, @Nullable Predicate canCollide); ++ // Paper end ++ + /** + * Gets the default spawn {@link Location} of this world + * diff --git a/patches/server/Add-predicate-for-blocks-when-raytracing.patch b/patches/server/Add-predicate-for-blocks-when-raytracing.patch new file mode 100644 index 0000000000..6508d550fb --- /dev/null +++ b/patches/server/Add-predicate-for-blocks-when-raytracing.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TonytheMacaroni +Date: Wed, 6 Sep 2023 19:24:16 -0400 +Subject: [PATCH] Add predicate for blocks when raytracing + + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -0,0 +0,0 @@ public interface BlockGetter extends LevelHeightAccessor { + + // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace + default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { ++ // Paper start ++ return clip(raytrace1, blockposition, null); ++ } ++ ++ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition, java.util.function.Predicate canCollide) { ++ // Paper end + // Paper start - Prevent raytrace from loading chunks + BlockState iblockdata = this.getBlockStateIfLoaded(blockposition); + if (iblockdata == null) { +@@ -0,0 +0,0 @@ public interface BlockGetter extends LevelHeightAccessor { + return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); + } + // Paper end +- if (iblockdata.isAir()) return null; // Paper - optimise air cases ++ if (iblockdata.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - optimise air cases and check canCollide predicate + FluidState fluid = iblockdata.getFluidState(); // Paper - don't need to go to world state again + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); +@@ -0,0 +0,0 @@ public interface BlockGetter extends LevelHeightAccessor { + // CraftBukkit end + + default BlockHitResult clip(ClipContext context) { ++ // Paper start ++ return clip(context, (java.util.function.Predicate) null); ++ } ++ ++ default BlockHitResult clip(ClipContext context, java.util.function.Predicate canCollide) { ++ // Paper end + return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> { +- return this.clip(raytrace1, blockposition); // CraftBukkit - moved into separate method ++ return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - use method with canCollide predicate + }, (raytrace1) -> { + Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate filter) { ++ // Paper start ++ return rayTraceEntities((io.papermc.paper.math.Position) start, direction, maxDistance, raySize, filter); ++ } ++ ++ public RayTraceResult rayTraceEntities(io.papermc.paper.math.Position start, Vector direction, double maxDistance, double raySize, Predicate filter) { + Preconditions.checkArgument(start != null, "Location start cannot be null"); +- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); +- start.checkFinite(); ++ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); ++ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); ++ // Paper end + + Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); + direction.checkFinite(); +@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks) { ++ // Paper start ++ return this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, null); ++ } ++ ++ @Override ++ public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, Predicate canCollide) { + Preconditions.checkArgument(start != null, "Location start cannot be null"); +- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); +- start.checkFinite(); ++ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); ++ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); ++ // Paper end + + Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); + direction.checkFinite(); +@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + + Vector dir = direction.clone().normalize().multiply(maxDistance); +- Vec3 startPos = CraftLocation.toVec3D(start); ++ Vec3 startPos = io.papermc.paper.util.MCUtil.toVec3(start); // Paper + Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); +- HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), null)); ++ HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), null), canCollide); // Paper - use method with canCollide predicate + + return CraftRayTraceResult.fromNMS(this, nmsHitResult); + } + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter) { +- RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks); ++ // Paper start ++ return this.rayTrace(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, raySize, filter, null); ++ } ++ ++ @Override ++ public RayTraceResult rayTrace(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter, Predicate canCollide) { ++ RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, canCollide); ++ // Paper end + Vector startVec = null; + double blockHitDistance = maxDistance; + diff --git a/patches/server/MC-Utils.patch b/patches/server/MC-Utils.patch index 1262009f6e..f5d99c50ff 100644 --- a/patches/server/MC-Utils.patch +++ b/patches/server/MC-Utils.patch @@ -3972,6 +3972,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return Position.block(vector.getX(), vector.getY(), vector.getZ()); + } + ++ public static Vec3 toVec3(Position position) { ++ return new Vec3(position.x(), position.y(), position.z()); ++ } ++ + public static boolean isEdgeOfChunk(BlockPos pos) { + final int modX = pos.getX() & 15; + final int modZ = pos.getZ() & 15;