DREAMFIRE Docs ← Back to site
Loading...
Searching...
No Matches
DreamRaycast.java
Go to the documentation of this file.
1/*
2 * MIT License
3 *
4 * Copyright (c) 2025 Dreamfire Studio
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24package com.dreamfirestudios.dreamcore.DreamRaycast;
25
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;
38
39import java.util.Objects;
40import java.util.Set;
41import java.util.function.Predicate;
42
72public final class DreamRaycast {
73
74 private DreamRaycast() {}
75
76 // ===== Types =====================================================================================================
77
81 public static final class RaycastHit {
82 private final @Nullable Block block;
83 private final @Nullable Entity entity;
84 private final @Nullable Location hitPosition;
85 private final double distance;
86
94 public RaycastHit(@Nullable Block block, @Nullable Entity entity, @Nullable Location hitPosition, double distance) {
95 this.block = block;
96 this.entity = entity;
97 this.hitPosition = hitPosition;
98 this.distance = distance;
99 }
101 public @Nullable Block block() { return block; }
103 public @Nullable Entity entity() { return entity; }
105 public @Nullable Location hitPosition() { return hitPosition; }
107 public double distance() { return distance; }
109 public boolean hitBlock() { return block != null; }
111 public boolean hitEntity() { return entity != null; }
112 }
113
118 @FunctionalInterface
119 public interface PathRenderer {
127 void render(@NotNull World world, @NotNull Location point, double t, double max);
128 }
129
130 // ===== Public convenience wrappers ==============================================================================
131
145 public static @Nullable Block rayCastFromPlayerIgnore(Player player, int range, @Nullable Particle particle, Set<Material> ignored) {
146 RaycastHit hit = raycast(player, range, 0.0, e -> true, ignored, false,
147 particle == null ? null : simpleParticle(particle));
148 return hit == null ? null : hit.block();
149 }
150
159 public static @Nullable Block rayCastFromPlayerMust(Player player, int range, @Nullable Particle particle, Set<Material> mustMatch) {
160 RaycastHit hit = raycast(player, range, 0.0, e -> true, mustMatch, true,
161 particle == null ? null : simpleParticle(particle));
162 return hit == null ? null : hit.block();
163 }
164
179 public static @Nullable RaycastHit raycastEntities(Player player, double range, double raySize,
180 @NotNull Predicate<Entity> entityFilter,
181 @Nullable PathRenderer renderer) {
182 Objects.requireNonNull(player, "player");
183 Objects.requireNonNull(entityFilter, "entityFilter");
184 World world = requireWorld(player);
185
186 Location start = player.getEyeLocation();
187 Vector dir = start.getDirection().normalize();
188
189 // Render path (optional)
190 if (renderer != null) drawPath(renderer, world, start, dir, range, 0.3);
191
192 RayTraceResult ent = world.rayTraceEntities(start, dir, range, raySize, entityFilter);
193 if (ent == null || ent.getHitEntity() == null || ent.getHitPosition() == null) return null;
194
195 double dist = ent.getHitPosition().toLocation(world).distance(start);
196 return new RaycastHit(null, ent.getHitEntity(), ent.getHitPosition().toLocation(world), dist);
197 }
198
220 public static @Nullable RaycastHit raycast(Player player,
221 double range,
222 double raySize,
223 @NotNull Predicate<Entity> entityFilter,
224 @NotNull Set<Material> materials,
225 boolean mustMatch,
226 @Nullable PathRenderer renderer) {
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");
232
233 World world = requireWorld(player);
234 Location start = player.getEyeLocation();
235 Vector dir = start.getDirection().normalize();
236
237 // Block hit (respect include/ignore rule)
238 RaycastHit blockHit = traceBlocksWithMaterialFilter(world, start, dir, range, materials, mustMatch);
239
240 // Entity hit (optional)
241 RaycastHit entityHit = null;
242 if (raySize > 0.0) {
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);
247 }
248 }
249
250 // Choose closest
251 RaycastHit chosen = closest(start, blockHit, entityHit);
252
253 // Render path (to chosen distance if available; otherwise full range)
254 if (renderer != null) {
255 double max = chosen == null ? range : chosen.distance();
256 drawPath(renderer, world, start, dir, max, 0.3);
257 }
258
259 return chosen;
260 }
261
262 // ===== Internals =================================================================================================
263
265 private static World requireWorld(Player p) {
266 World w = p.getWorld();
267 if (w == null) throw new IllegalStateException("Player world is null");
268 return w;
269 }
270
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;
276 }
277
288 private static @Nullable RaycastHit traceBlocksWithMaterialFilter(World world,
289 Location start,
290 Vector dir,
291 double range,
292 Set<Material> materials,
293 boolean mustMatch) {
294 double remaining = range;
295 Location cursor = start.clone();
296 final double epsilon = 0.01;
297
298 while (remaining > 0) {
299 RayTraceResult br = world.rayTraceBlocks(
300 cursor, dir, remaining,
301 FluidCollisionMode.NEVER, // ignore fluids by default
302 true // ignore passable blocks so we stop on solids
303 );
304 if (br == null || br.getHitBlock() == null || br.getHitPosition() == null) return null;
305
306 Block hit = br.getHitBlock();
307 Location pos = br.getHitPosition().toLocation(world);
308 double dist = pos.distance(cursor);
309
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);
314 }
315
316 // Skip this block and continue from just beyond the hit point
317 double advance = Math.max(dist + epsilon, epsilon);
318 cursor.add(dir.clone().multiply(advance));
319 remaining -= advance;
320 }
321 return null;
322 }
323
333 private static void drawPath(PathRenderer renderer, World world, Location start, Vector dir, double maxDistance, double step) {
334 Vector unit = dir.clone().normalize();
335 double drawn = 0;
336 while (drawn <= maxDistance) {
337 Location p = start.clone().add(unit.clone().multiply(drawn));
338 renderer.render(world, p, drawn, maxDistance);
339 drawn += step;
340 }
341 }
342
353 public static PathRenderer simpleParticle(Particle particle) {
354 Objects.requireNonNull(particle, "particle");
355 return (world, point, t, max) -> world.spawnParticle(particle, point, 1, 0, 0, 0, 0);
356 }
357}
Result of a raycast; at most one of block() or entity() is non-null.
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.
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.