Files
purpur-trigger-spawn/src/main/java/games/dmg/triggerspawn/SafeSpawnFinder.java
T
dmg bce513e922
CI / build (push) Successful in 59s
CI / build (pull_request) Successful in 1m2s
feat(spawn): support Purpur 26.3
2026-10-17 00:11:07 -04:00

98 lines
3.6 KiB
Java

package games.dmg.triggerspawn;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.random.RandomGenerator;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.block.Block;
final class SafeSpawnFinder {
private static final int MAX_ATTEMPTS = 32;
private static final Set<Material> UNSAFE_GROUND = EnumSet.of(
Material.LAVA,
Material.FIRE,
Material.SOUL_FIRE,
Material.MAGMA_BLOCK,
Material.CAMPFIRE,
Material.SOUL_CAMPFIRE,
Material.CACTUS,
Material.SWEET_BERRY_BUSH,
Material.POINTED_DRIPSTONE);
private static final Set<Material> UNSAFE_SPACE = EnumSet.of(
Material.LAVA,
Material.WATER,
Material.FIRE,
Material.SOUL_FIRE,
Material.POWDER_SNOW);
private final Predicate<Material> solidMaterial;
private final Predicate<Material> leafMaterial;
SafeSpawnFinder() {
this(Material::isSolid, material -> Tag.LEAVES.isTagged(material));
}
SafeSpawnFinder(Predicate<Material> solidMaterial, Predicate<Material> leafMaterial) {
this.solidMaterial = Objects.requireNonNull(solidMaterial);
this.leafMaterial = Objects.requireNonNull(leafMaterial);
}
Optional<Location> find(
World world,
SpawnLocation center,
int maxDistance,
RandomGenerator random) {
int attempts = maxDistance == 0 ? 1 : MAX_ATTEMPTS;
for (int attempt = 0; attempt < attempts; attempt++) {
SpawnLocation sampled = CircularSpawnSampler.sample(center, maxDistance, random);
int x = (int) Math.floor(sampled.x());
int z = (int) Math.floor(sampled.z());
Location borderCheck = new Location(world, x + 0.5, center.y(), z + 0.5);
if (!world.getWorldBorder().isInside(borderCheck)) {
continue;
}
int minimumFeetY = world.getMinHeight() + 1;
int maximumFeetY = world.getMaxHeight() - 2;
if (world.getEnvironment() == World.Environment.NETHER) {
maximumFeetY = Math.min(maximumFeetY, world.getLogicalHeight() - 1);
}
Optional<Integer> y = NearestSafeY.find(
(int) Math.floor(center.y()),
minimumFeetY,
maximumFeetY,
candidate -> isSafe(world, x, candidate, z))
.stream()
.boxed()
.findFirst();
if (y.isPresent()) {
return Optional.of(new Location(
world, x + 0.5, y.orElseThrow(), z + 0.5, center.yaw(), center.pitch()));
}
}
return Optional.empty();
}
private boolean isSafe(World world, int x, int feetY, int z) {
Block ground = world.getBlockAt(x, feetY - 1, z);
Block feet = world.getBlockAt(x, feetY, z);
Block head = world.getBlockAt(x, feetY + 1, z);
Material groundType = ground.getType();
return solidMaterial.test(groundType)
&& !leafMaterial.test(groundType)
&& !UNSAFE_GROUND.contains(groundType)
&& isClear(feet)
&& isClear(head);
}
private static boolean isClear(Block block) {
return block.isPassable()
&& !block.isLiquid()
&& !UNSAFE_SPACE.contains(block.getType());
}
}