From 4a47be61649e410294b504fede87199167821e99 Mon Sep 17 00:00:00 2001 From: blablubbabc Date: Fri, 26 Oct 2018 19:59:42 +1100 Subject: [PATCH] Add ray tracing and bounding box API --- .../craftbukkit/CraftFluidCollisionMode.java | 24 +++ .../org/bukkit/craftbukkit/CraftWorld.java | 160 +++++++++++++++++- .../bukkit/craftbukkit/block/CraftBlock.java | 48 +++++- .../craftbukkit/entity/CraftEntity.java | 7 + .../craftbukkit/entity/CraftLivingEntity.java | 25 +++ .../craftbukkit/util/CraftRayTraceResult.java | 42 +++++ 6 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/bukkit/craftbukkit/CraftFluidCollisionMode.java create mode 100644 src/main/java/org/bukkit/craftbukkit/util/CraftRayTraceResult.java diff --git a/src/main/java/org/bukkit/craftbukkit/CraftFluidCollisionMode.java b/src/main/java/org/bukkit/craftbukkit/CraftFluidCollisionMode.java new file mode 100644 index 0000000000..faf2fd4ecd --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/CraftFluidCollisionMode.java @@ -0,0 +1,24 @@ +package org.bukkit.craftbukkit; + +import org.bukkit.FluidCollisionMode; +import net.minecraft.server.FluidCollisionOption; + +public class CraftFluidCollisionMode { + + private CraftFluidCollisionMode() {} + + public static FluidCollisionOption toNMS(FluidCollisionMode fluidCollisionMode) { + if (fluidCollisionMode == null) return null; + + switch (fluidCollisionMode) { + case ALWAYS: + return FluidCollisionOption.ALWAYS; + case SOURCE_ONLY: + return FluidCollisionOption.SOURCE_ONLY; + case NEVER: + return FluidCollisionOption.NEVER; + default: + return null; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index cc10e2778f..16fbf732d9 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -12,6 +12,7 @@ import java.util.Iterator; import java.util.Random; import java.util.Set; import java.util.UUID; +import java.util.function.Predicate; import net.minecraft.server.*; @@ -22,6 +23,7 @@ import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; import org.bukkit.Difficulty; import org.bukkit.Effect; +import org.bukkit.FluidCollisionMode; import org.bukkit.GameRule; import org.bukkit.Location; import org.bukkit.Particle; @@ -43,6 +45,7 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.metadata.BlockMetadataStore; import org.bukkit.craftbukkit.potion.CraftPotionUtil; import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftRayTraceResult; import org.bukkit.entity.*; import org.bukkit.entity.Entity; import org.bukkit.entity.minecart.CommandMinecart; @@ -62,7 +65,9 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.messaging.StandardMessenger; import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionType; +import org.bukkit.util.BoundingBox; import org.bukkit.util.Consumer; +import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; public class CraftWorld implements World { @@ -691,19 +696,162 @@ public class CraftWorld implements World { @Override public Collection getNearbyEntities(Location location, double x, double y, double z) { - if (location == null || !location.getWorld().equals(this)) { - return Collections.emptyList(); - } + return this.getNearbyEntities(location, x, y, z, null); + } - AxisAlignedBB bb = new AxisAlignedBB(location.getX() - x, location.getY() - y, location.getZ() - z, location.getX() + x, location.getY() + y, location.getZ() + z); + @Override + public Collection getNearbyEntities(Location location, double x, double y, double z, Predicate filter) { + Validate.notNull(location, "Location is null!"); + Validate.isTrue(this.equals(location.getWorld()), "Location is from different world!"); + + BoundingBox aabb = BoundingBox.of(location, x, y, z); + return this.getNearbyEntities(aabb, filter); + } + + @Override + public Collection getNearbyEntities(BoundingBox boundingBox) { + return this.getNearbyEntities(boundingBox, null); + } + + @Override + public Collection getNearbyEntities(BoundingBox boundingBox, Predicate filter) { + Validate.notNull(boundingBox, "Bounding box is null!"); + + AxisAlignedBB bb = new AxisAlignedBB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ()); List entityList = getHandle().getEntities((net.minecraft.server.Entity) null, bb, null); List bukkitEntityList = new ArrayList(entityList.size()); - for (Object entity : entityList) { - bukkitEntityList.add(((net.minecraft.server.Entity) entity).getBukkitEntity()); + + for (net.minecraft.server.Entity entity : entityList) { + Entity bukkitEntity = entity.getBukkitEntity(); + if (filter == null || filter.test(bukkitEntity)) { + bukkitEntityList.add(bukkitEntity); + } } + return bukkitEntityList; } + @Override + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance) { + return this.rayTraceEntities(start, direction, maxDistance, null); + } + + @Override + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize) { + return this.rayTraceEntities(start, direction, maxDistance, raySize, null); + } + + @Override + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, Predicate filter) { + return this.rayTraceEntities(start, direction, maxDistance, 0.0D, filter); + } + + @Override + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate filter) { + Validate.notNull(start, "Start location is null!"); + Validate.isTrue(this.equals(start.getWorld()), "Start location is from different world!"); + start.checkFinite(); + + Validate.notNull(direction, "Direction is null!"); + direction.checkFinite(); + + Validate.isTrue(direction.lengthSquared() > 0, "Direction's magnitude is 0!"); + + if (maxDistance < 0.0D) { + return null; + } + + Vector startPos = start.toVector(); + Vector dir = direction.clone().normalize().multiply(maxDistance); + BoundingBox aabb = BoundingBox.of(startPos, startPos).expandDirectional(dir).expand(raySize); + Collection entities = this.getNearbyEntities(aabb, filter); + + Entity nearestHitEntity = null; + RayTraceResult nearestHitResult = null; + double nearestDistanceSq = Double.MAX_VALUE; + + for (Entity entity : entities) { + BoundingBox boundingBox = entity.getBoundingBox().expand(raySize); + RayTraceResult hitResult = boundingBox.rayTrace(startPos, direction, maxDistance); + + if (hitResult != null) { + double distanceSq = startPos.distanceSquared(hitResult.getHitPosition()); + + if (distanceSq < nearestDistanceSq) { + nearestHitEntity = entity; + nearestHitResult = hitResult; + nearestDistanceSq = distanceSq; + } + } + } + + return (nearestHitEntity == null) ? null : new RayTraceResult(nearestHitResult.getHitPosition(), nearestHitEntity, nearestHitResult.getHitBlockFace()); + } + + @Override + public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance) { + return this.rayTraceBlocks(start, direction, maxDistance, FluidCollisionMode.NEVER, false); + } + + @Override + public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { + return this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, false); + } + + @Override + public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks) { + Validate.notNull(start, "Start location is null!"); + Validate.isTrue(this.equals(start.getWorld()), "Start location is from different world!"); + start.checkFinite(); + + Validate.notNull(direction, "Direction is null!"); + direction.checkFinite(); + + Validate.isTrue(direction.lengthSquared() > 0, "Direction's magnitude is 0!"); + Validate.notNull(fluidCollisionMode, "Fluid collision mode is null!"); + + if (maxDistance < 0.0D) { + return null; + } + + Vector dir = direction.clone().normalize().multiply(maxDistance); + Vec3D startPos = new Vec3D(start.getX(), start.getY(), start.getZ()); + Vec3D endPos = new Vec3D(start.getX() + dir.getX(), start.getY() + dir.getY(), start.getZ() + dir.getZ()); + MovingObjectPosition nmsHitResult = this.getHandle().rayTrace(startPos, endPos, CraftFluidCollisionMode.toNMS(fluidCollisionMode), ignorePassableBlocks, false); + + 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); + Vector startVec = null; + double blockHitDistance = maxDistance; + + // limiting the entity search range if we found a block hit: + if (blockHit != null) { + startVec = start.toVector(); + blockHitDistance = startVec.distance(blockHit.getHitPosition()); + } + + RayTraceResult entityHit = this.rayTraceEntities(start, direction, blockHitDistance, raySize, filter); + if (blockHit == null) { + return entityHit; + } + + if (entityHit == null) { + return blockHit; + } + + // Cannot be null as blockHit == null returns above + double entityHitDistanceSquared = startVec.distanceSquared(entityHit.getHitPosition()); + if (entityHitDistanceSquared < (blockHitDistance * blockHitDistance)) { + return entityHit; + } + + return blockHit; + } + public List getPlayers() { List list = new ArrayList(world.players.size()); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java index 1b2a8b6356..3dbeb376fc 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -8,7 +8,9 @@ import java.util.List; import net.minecraft.server.*; +import org.apache.commons.lang.Validate; import org.bukkit.Chunk; +import org.bukkit.FluidCollisionMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; @@ -18,15 +20,18 @@ import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.PistonMoveReaction; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.CraftChunk; +import org.bukkit.craftbukkit.CraftFluidCollisionMode; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftRayTraceResult; import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.Plugin; import org.bukkit.util.BlockVector; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; public class CraftBlock implements Block { private final net.minecraft.server.GeneratorAccess world; @@ -602,4 +607,45 @@ public class CraftBlock implements Block { public boolean isPassable() { return this.getData0().getCollisionShape(world, position).isEmpty(); } + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { + Validate.notNull(start, "Start location is null!"); + Validate.isTrue(this.getWorld().equals(start.getWorld()), "Start location is from different world!"); + start.checkFinite(); + + Validate.notNull(direction, "Direction is null!"); + direction.checkFinite(); + Validate.isTrue(direction.lengthSquared() > 0, "Direction's magnitude is 0!"); + + Validate.notNull(fluidCollisionMode, "Fluid collision mode is null!"); + if (maxDistance < 0.0D) { + return null; + } + + Vector dir = direction.clone().normalize().multiply(maxDistance); + Vec3D startPos = new Vec3D(start.getX(), start.getY(), start.getZ()); + Vec3D endPos = new Vec3D(start.getX() + dir.getX(), start.getY() + dir.getY(), start.getZ() + dir.getZ()); + + // Similar to to nms.World#rayTrace: + IBlockData blockData = world.getType(position); + Fluid fluid = world.b(position); // PAIL getFluid + boolean collidableBlock = blockData.getBlock().d(blockData); // PAIL isCollidable + boolean collideWithFluid = CraftFluidCollisionMode.toNMS(fluidCollisionMode).d.test(fluid); // PAIL predicate + + if (!collidableBlock && !collideWithFluid) { + return null; + } + + MovingObjectPosition nmsHitResult = null; + if (collidableBlock) { + nmsHitResult = net.minecraft.server.Block.a(blockData, world.getMinecraftWorld(), position, startPos, endPos); // PAIL rayTrace + } + + if (nmsHitResult == null && collideWithFluid) { + nmsHitResult = VoxelShapes.a(0.0D, 0.0D, 0.0D, 1.0D, (double) fluid.f(), 1.0D).a(startPos, endPos, position); // PAIL create, getHeight, rayTrace + } + + return CraftRayTraceResult.fromNMS(this.getWorld(), nmsHitResult); + } } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index eb1bf55e59..f58e5f7068 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -28,6 +28,7 @@ import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.permissions.ServerOperator; import org.bukkit.plugin.Plugin; +import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; public abstract class CraftEntity implements org.bukkit.entity.Entity { @@ -273,6 +274,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return getHandle().width; } + @Override + public BoundingBox getBoundingBox() { + AxisAlignedBB bb = getHandle().getBoundingBox(); + return new BoundingBox(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); + } + public boolean isOnGround() { if (entity instanceof EntityArrow) { return ((EntityArrow) entity).inGround; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index cc9e48d585..3a15b47dd6 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -36,6 +36,7 @@ import net.minecraft.server.MobEffect; import net.minecraft.server.MobEffectList; import org.apache.commons.lang.Validate; +import org.bukkit.FluidCollisionMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.attribute.Attribute; @@ -78,6 +79,7 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; import org.bukkit.util.BlockIterator; +import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; public class CraftLivingEntity extends CraftEntity implements LivingEntity { @@ -170,6 +172,29 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { return getLineOfSight(transparent, maxDistance, 2); } + @Override + public Block getTargetBlockExact(int maxDistance) { + return this.getTargetBlockExact(maxDistance, FluidCollisionMode.NEVER); + } + + @Override + public Block getTargetBlockExact(int maxDistance, FluidCollisionMode fluidCollisionMode) { + RayTraceResult hitResult = this.rayTraceBlocks(maxDistance, fluidCollisionMode); + return (hitResult != null ? hitResult.getHitBlock() : null); + } + + @Override + public RayTraceResult rayTraceBlocks(double maxDistance) { + return this.rayTraceBlocks(maxDistance, FluidCollisionMode.NEVER); + } + + @Override + public RayTraceResult rayTraceBlocks(double maxDistance, FluidCollisionMode fluidCollisionMode) { + Location eyeLocation = this.getEyeLocation(); + Vector direction = eyeLocation.getDirection(); + return this.getWorld().rayTraceBlocks(eyeLocation, direction, maxDistance, fluidCollisionMode, false); + } + public int getRemainingAir() { return getHandle().getAirTicks(); } diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftRayTraceResult.java b/src/main/java/org/bukkit/craftbukkit/util/CraftRayTraceResult.java new file mode 100644 index 0000000000..d500824b99 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftRayTraceResult.java @@ -0,0 +1,42 @@ +package org.bukkit.craftbukkit.util; + +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.entity.Entity; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; +import net.minecraft.server.BlockPosition; +import net.minecraft.server.MovingObjectPosition; +import net.minecraft.server.MovingObjectPosition.EnumMovingObjectType; +import net.minecraft.server.Vec3D; + +public class CraftRayTraceResult { + + private CraftRayTraceResult() {} + + public static RayTraceResult fromNMS(World world, MovingObjectPosition nmsHitResult) { + if (nmsHitResult == null || nmsHitResult.type == EnumMovingObjectType.MISS) return null; + + Vec3D nmsHitPos = nmsHitResult.pos; + Vector hitPosition = new Vector(nmsHitPos.x, nmsHitPos.y, nmsHitPos.z); + BlockFace hitBlockFace = null; + + if (nmsHitResult.direction != null) { + hitBlockFace = CraftBlock.notchToBlockFace(nmsHitResult.direction); + } + + if (nmsHitResult.entity != null) { + Entity hitEntity = nmsHitResult.entity.getBukkitEntity(); + return new RayTraceResult(hitPosition, hitEntity, hitBlockFace); + } + + Block hitBlock = null; + BlockPosition nmsBlockPos = nmsHitResult.a(); // PAIL: getBlockPosition + if (nmsBlockPos != null && world != null) { + hitBlock = world.getBlockAt(nmsBlockPos.getX(), nmsBlockPos.getY(), nmsBlockPos.getZ()); + } + return new RayTraceResult(hitPosition, hitBlock, hitBlockFace); + } +}