Skip to content

Commit

Permalink
Add RayTraceConfigurationBuilder (#11907)
Browse files Browse the repository at this point in the history
  • Loading branch information
notTamion authored Jan 25, 2025
1 parent 336ea9d commit 939bb78
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package io.papermc.paper.raytracing;

import java.util.function.Predicate;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
import org.checkerframework.checker.index.qual.NonNegative;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;

/**
* A builder for configuring a raytrace with a starting location
* and direction.
*/
@NullMarked
public interface PositionedRayTraceConfigurationBuilder {

/**
* Sets the starting location.
*
* @param start the new starting location
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder start(Location start);

/**
* Sets the direction.
*
* @param direction the new direction
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder direction(Vector direction);

/**
* Sets the maximum distance.
*
* @param maxDistance the new maxDistance
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder maxDistance(@NonNegative double maxDistance);

/**
* Sets the FluidCollisionMode when looking for block collisions.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
*
* @param fluidCollisionMode the new FluidCollisionMode
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder fluidCollisionMode(FluidCollisionMode fluidCollisionMode);

/**
* Sets whether the raytrace should ignore passable blocks when looking for
* block collisions.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
* <p>
* 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.
*
* @param ignorePassableBlocks if the raytrace should ignore passable blocks
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder ignorePassableBlocks(boolean ignorePassableBlocks);

/**
* Sets the size of the raytrace when looking for entity collisions.
*
* @param raySize the new raytrace size
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder raySize(@NonNegative double raySize);

/**
* Sets the current entity filter when looking for entity collisions.
*
* @param entityFilter predicate for entities the ray can potentially collide with
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder entityFilter(Predicate<? super Entity> entityFilter);

/**
* Sets the current block filter when looking for block collisions.
*
* @param blockFilter predicate for blocks the ray can potentially collide with
* @return a reference to this object
*/
@Contract(value = "_ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder blockFilter(Predicate<? super Block> blockFilter);

/**
* Sets the targets for the rayTrace.
*
* @param first the first target
* @param others the other targets
* @return a reference to this object
*/
@Contract(value = "_, _ -> this", mutates = "this")
PositionedRayTraceConfigurationBuilder targets(RayTraceTarget first, RayTraceTarget... others);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.papermc.paper.raytracing;

/**
* List of Targets a builder can target.
*/
public enum RayTraceTarget {
ENTITY,
BLOCK
}
15 changes: 15 additions & 0 deletions paper-api/src/main/java/org/bukkit/World.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.bukkit;

import java.io.File;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilder;
import org.bukkit.generator.ChunkGenerator;

import java.util.ArrayList;
Expand Down Expand Up @@ -1938,6 +1939,20 @@ default Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {
@Nullable RayTraceResult rayTrace(io.papermc.paper.math.@NotNull Position start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, @Nullable Predicate<? super Entity> filter, @Nullable Predicate<? super Block> canCollide);
// Paper end

/**
* Performs a ray trace that checks for collisions with the specified
* targets.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param builderConsumer a consumer to configure the ray trace configuration.
* The received builder is not valid for use outside the consumer
* @return the closest ray trace hit result with either a block or an
* entity, or <code>null</code> if there is no hit
*/
@Nullable RayTraceResult rayTrace(@NotNull Consumer<PositionedRayTraceConfigurationBuilder> builderConsumer);

/**
* Gets the default spawn {@link Location} of this world
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.papermc.paper.raytracing;

import com.google.common.base.Preconditions;
import java.util.EnumSet;
import java.util.OptionalDouble;
import java.util.function.Predicate;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

@NullMarked
public class PositionedRayTraceConfigurationBuilderImpl implements PositionedRayTraceConfigurationBuilder {

public @Nullable Location start;
public @Nullable Vector direction;
public OptionalDouble maxDistance = OptionalDouble.empty();
public FluidCollisionMode fluidCollisionMode = FluidCollisionMode.NEVER;
public boolean ignorePassableBlocks;
public double raySize = 0.0D;
public @Nullable Predicate<? super Entity> entityFilter;
public @Nullable Predicate<? super Block> blockFilter;
public EnumSet<RayTraceTarget> targets = EnumSet.noneOf(RayTraceTarget.class);

@Override
public PositionedRayTraceConfigurationBuilder start(final Location start) {
Preconditions.checkArgument(start != null, "start must not be null");
this.start = start.clone();
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder direction(final Vector direction) {
Preconditions.checkArgument(direction != null, "direction must not be null");
this.direction = direction.clone();
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder maxDistance(final double maxDistance) {
Preconditions.checkArgument(maxDistance >= 0, "maxDistance must be non-negative");
this.maxDistance = OptionalDouble.of(maxDistance);
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder fluidCollisionMode(final FluidCollisionMode fluidCollisionMode) {
Preconditions.checkArgument(fluidCollisionMode != null, "fluidCollisionMode must not be null");
this.fluidCollisionMode = fluidCollisionMode;
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder ignorePassableBlocks(final boolean ignorePassableBlocks) {
this.ignorePassableBlocks = ignorePassableBlocks;
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder raySize(final double raySize) {
Preconditions.checkArgument(raySize >= 0, "raySize must be non-negative");
this.raySize = raySize;
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder entityFilter(final Predicate<? super Entity> entityFilter) {
Preconditions.checkArgument(entityFilter != null, "entityFilter must not be null");
this.entityFilter = entityFilter;
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder blockFilter(final Predicate<? super Block> blockFilter) {
Preconditions.checkArgument(blockFilter != null, "blockFilter must not be null");
this.blockFilter = blockFilter;
return this;
}

@Override
public PositionedRayTraceConfigurationBuilder targets(final RayTraceTarget first, final RayTraceTarget... others) {
Preconditions.checkArgument(first != null, "first must not be null");
Preconditions.checkArgument(others != null, "others must not be null");
this.targets = EnumSet.of(first, others);
return this;
}
}
23 changes: 23 additions & 0 deletions paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import com.google.common.collect.ImmutableMap;
import com.mojang.datafixers.util.Pair;
import io.papermc.paper.FeatureHooks;
import io.papermc.paper.raytracing.RayTraceTarget;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilder;
import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilderImpl;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.io.File;
import java.util.ArrayList;
Expand Down Expand Up @@ -1248,6 +1251,26 @@ public RayTraceResult rayTrace(io.papermc.paper.math.Position start, Vector dire
return blockHit;
}

@Override
public RayTraceResult rayTrace(Consumer<PositionedRayTraceConfigurationBuilder> builderConsumer) {
PositionedRayTraceConfigurationBuilderImpl builder = new PositionedRayTraceConfigurationBuilderImpl();

builderConsumer.accept(builder);
Preconditions.checkArgument(builder.start != null, "Start location cannot be null");
Preconditions.checkArgument(builder.direction != null, "Direction vector cannot be null");
Preconditions.checkArgument(builder.maxDistance.isPresent(), "Max distance must be set");
Preconditions.checkArgument(!builder.targets.isEmpty(), "At least one target");

final double maxDistance = builder.maxDistance.getAsDouble();
if (builder.targets.contains(RayTraceTarget.ENTITY)) {
if (builder.targets.contains(RayTraceTarget.BLOCK)) {
return this.rayTrace(builder.start, builder.direction, maxDistance, builder.fluidCollisionMode, builder.ignorePassableBlocks, builder.raySize, builder.entityFilter, builder.blockFilter);
}
return this.rayTraceEntities(builder.start, builder.direction, maxDistance, builder.raySize, builder.entityFilter);
}
return this.rayTraceBlocks(builder.start, builder.direction, maxDistance, builder.fluidCollisionMode, builder.ignorePassableBlocks, builder.blockFilter);
}

@Override
public List<Player> getPlayers() {
List<Player> list = new ArrayList<Player>(this.world.players().size());
Expand Down

0 comments on commit 939bb78

Please sign in to comment.