98 lines
3.6 KiB
Java
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());
|
|
}
|
|
}
|