24package com.dreamfirestudios.dreamcore.DreamRaycast;
26import org.bukkit.FluidCollisionMode;
27import org.bukkit.Location;
28import org.bukkit.Material;
29import org.bukkit.Particle;
30import org.bukkit.World;
31import org.bukkit.block.Block;
32import org.bukkit.entity.Entity;
33import org.bukkit.entity.Player;
34import org.bukkit.util.RayTraceResult;
35import org.bukkit.util.Vector;
36import org.jetbrains.annotations.NotNull;
37import org.jetbrains.annotations.Nullable;
39import java.util.Objects;
41import java.util.function.Predicate;
82 private final @Nullable Block block;
83 private final @Nullable Entity entity;
84 private final @Nullable Location hitPosition;
85 private final double distance;
94 public RaycastHit(@Nullable Block block, @Nullable Entity entity, @Nullable Location hitPosition,
double distance) {
97 this.hitPosition = hitPosition;
98 this.distance = distance;
109 public boolean hitBlock() {
return block !=
null; }
127 void render(@NotNull World world, @NotNull Location point,
double t,
double max);
145 public static @Nullable Block
rayCastFromPlayerIgnore(Player player,
int range, @Nullable Particle particle, Set<Material> ignored) {
148 return hit ==
null ? null : hit.block();
159 public static @Nullable Block
rayCastFromPlayerMust(Player player,
int range, @Nullable Particle particle, Set<Material> mustMatch) {
162 return hit ==
null ? null : hit.block();
180 @NotNull Predicate<Entity> entityFilter,
182 Objects.requireNonNull(player,
"player");
183 Objects.requireNonNull(entityFilter,
"entityFilter");
184 World world = requireWorld(player);
186 Location start = player.getEyeLocation();
187 Vector dir = start.getDirection().normalize();
190 if (renderer !=
null) drawPath(renderer, world, start, dir, range, 0.3);
192 RayTraceResult ent = world.rayTraceEntities(start, dir, range, raySize, entityFilter);
193 if (ent ==
null || ent.getHitEntity() ==
null || ent.getHitPosition() ==
null)
return null;
195 double dist = ent.getHitPosition().toLocation(world).distance(start);
196 return new RaycastHit(
null, ent.getHitEntity(), ent.getHitPosition().toLocation(world), dist);
223 @NotNull Predicate<Entity> entityFilter,
224 @NotNull Set<Material> materials,
227 Objects.requireNonNull(player,
"player");
228 Objects.requireNonNull(entityFilter,
"entityFilter");
229 Objects.requireNonNull(materials,
"materials");
230 if (range <= 0)
throw new IllegalArgumentException(
"range must be > 0");
231 if (raySize < 0)
throw new IllegalArgumentException(
"raySize must be >= 0");
233 World world = requireWorld(player);
234 Location start = player.getEyeLocation();
235 Vector dir = start.getDirection().normalize();
238 RaycastHit blockHit = traceBlocksWithMaterialFilter(world, start, dir, range, materials, mustMatch);
243 RayTraceResult er = world.rayTraceEntities(start, dir, range, raySize, entityFilter);
244 if (er !=
null && er.getHitEntity() !=
null && er.getHitPosition() !=
null) {
245 double d = er.getHitPosition().toLocation(world).distance(start);
246 entityHit =
new RaycastHit(
null, er.getHitEntity(), er.getHitPosition().toLocation(world), d);
251 RaycastHit chosen = closest(start, blockHit, entityHit);
254 if (renderer !=
null) {
255 double max = chosen ==
null ? range : chosen.distance();
256 drawPath(renderer, world, start, dir, max, 0.3);
265 private static World requireWorld(Player p) {
266 World w = p.getWorld();
267 if (w ==
null)
throw new IllegalStateException(
"Player world is null");
272 private static @Nullable RaycastHit closest(Location start, @Nullable RaycastHit a, @Nullable RaycastHit b) {
273 if (a ==
null)
return b;
274 if (b ==
null)
return a;
275 return a.distance() <= b.distance() ? a : b;
288 private static @Nullable RaycastHit traceBlocksWithMaterialFilter(World world,
292 Set<Material> materials,
294 double remaining = range;
295 Location cursor = start.clone();
296 final double epsilon = 0.01;
298 while (remaining > 0) {
299 RayTraceResult br = world.rayTraceBlocks(
300 cursor, dir, remaining,
301 FluidCollisionMode.NEVER,
304 if (br ==
null || br.getHitBlock() ==
null || br.getHitPosition() ==
null)
return null;
306 Block hit = br.getHitBlock();
307 Location pos = br.getHitPosition().toLocation(world);
308 double dist = pos.distance(cursor);
310 boolean inSet = materials.contains(hit.getType());
311 if (mustMatch ? inSet : !inSet) {
312 double totalDist = start.distance(pos);
313 return new RaycastHit(hit,
null, pos, totalDist);
317 double advance = Math.max(dist + epsilon, epsilon);
318 cursor.add(dir.clone().multiply(advance));
319 remaining -= advance;
333 private static void drawPath(PathRenderer renderer, World world, Location start, Vector dir,
double maxDistance,
double step) {
334 Vector unit = dir.clone().normalize();
336 while (drawn <= maxDistance) {
337 Location p = start.clone().add(unit.clone().multiply(drawn));
338 renderer.render(world, p, drawn, maxDistance);
354 Objects.requireNonNull(particle,
"particle");
355 return (world, point, t, max) -> world.spawnParticle(particle, point, 1, 0, 0, 0, 0);
Result of a raycast; at most one of block() or entity() is non-null.
boolean hitBlock()
True if a block was hit.
boolean hitEntity()
True if an entity was hit.
Location hitPosition()
Gets the exact world-space hit location (if available).
RaycastHit(@Nullable Block block, @Nullable Entity entity, @Nullable Location hitPosition, double distance)
Initializes a raycast hit record.
Entity entity()
Gets the hit entity (if any).
Block block()
Gets the hit block (if any).
double distance()
Gets the distance from the ray origin to the hit.
Utility helpers for performing player-centric raycasts against blocks and entities,...
static PathRenderer simpleParticle(Particle particle)
Creates a basic renderer that spawns one particle per step at the path point.
static RaycastHit raycast(Player player, double range, double raySize, @NotNull Predicate< Entity > entityFilter, @NotNull Set< Material > materials, boolean mustMatch, @Nullable PathRenderer renderer)
Combined raycast that tests blocks (with material include/ignore rules) and optionally entities.
static RaycastHit raycastEntities(Player player, double range, double raySize, @NotNull Predicate< Entity > entityFilter, @Nullable PathRenderer renderer)
Entity-only raycast (no block test).
static Block rayCastFromPlayerIgnore(Player player, int range, @Nullable Particle particle, Set< Material > ignored)
Raycasts from the player's eye for the first non-ignored block (no entity test).
static Block rayCastFromPlayerMust(Player player, int range, @Nullable Particle particle, Set< Material > mustMatch)
Raycasts from the player's eye for the first block that MUST match any of the provided materials (no ...
Draws a path along the ray; t is distance from start in blocks, max is total distance drawn.
void render(@NotNull World world, @NotNull Location point, double t, double max)
Called for each step along the visualized ray.