/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.utils.entity;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.managed.entity.ITrackedEntity;
import com.jcwhatever.nucleus.providers.npc.Npcs;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.coords.LocationUtils;
import com.jcwhatever.nucleus.utils.validate.IValidator;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Projectile;
import org.bukkit.inventory.ItemStack;
import org.bukkit.projectiles.ProjectileSource;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* {@link org.bukkit.entity.Entity} helper utilities
*/
public final class EntityUtils {
private EntityUtils() {}
/**
* A validator that always specifies an entity as valid.
*/
public static final IValidator<Entity> ALL_VALID_ENTITY = new IValidator<Entity>() {
@Override
public boolean isValid(Entity element) {
return true;
}
};
/**
* A validator that always specifies a living entity as valid.
*/
public static final IValidator<LivingEntity> ALL_VALID_LIVING = new IValidator<LivingEntity>() {
@Override
public boolean isValid(LivingEntity element) {
return true;
}
};
private static final Location SOURCE_ENTITY_LOCATION = new Location(null, 0, 0, 0);
private static final Location TARGET_ENTITY_LOCATION = new Location(null, 0, 0, 0);
private static final Location NEARBY_ENTITY_LOCATION = new Location(null, 0, 0, 0);
private static final ClosestLivingValidator CLOSEST_LIVING_VALIDATOR =
new ClosestLivingValidator();
private static final SourcedClosestEntityValidator SOURCED_CLOSEST_ENTITY_VALIDATOR =
new SourcedClosestEntityValidator();
private static final SourcedClosestLivingValidator SOURCED_CLOSEST_LIVING_VALIDATOR =
new SourcedClosestLivingValidator();
/**
* Find an {@link org.bukkit.entity.Entity} in a {@link org.bukkit.World}
* by its integer ID.
*
* @param world The world to look in.
* @param id The entity ID.
*
* @return Null if not found.
*/
@Nullable
public static Entity getEntityById(World world, int id) {
PreCon.notNull(world);
List<Entity> entities = world.getEntities();
for (Entity entity : entities) {
if (entity.getEntityId() == id)
return entity;
}
return null;
}
/**
* Find an {@link org.bukkit.entity.Entity} in a {@link org.bukkit.Chunk}
* by its integer ID.
*
* @param chunk The chunk to look in.
* @param id The entity ID.
*
* @return Null if not found.
*/
@Nullable
public static Entity getEntityById(Chunk chunk, int id) {
PreCon.notNull(chunk);
Entity[] entities = chunk.getEntities();
for (Entity entity : entities) {
if (entity.getEntityId() == id)
return entity;
}
return null;
}
/**
* Find an {@link org.bukkit.entity.Entity} in a {@link org.bukkit.World}
* by its unique ID.
*
* @param world The world to look in.
* @param uniqueId The entity unique ID.
*
* @return Null if not found.
*/
@Nullable
public static Entity getEntityByUUID(World world, UUID uniqueId) {
PreCon.notNull(world);
PreCon.notNull(uniqueId);
List<Entity> entities = world.getEntities();
for (Entity entity : entities) {
if (entity.getUniqueId().equals(uniqueId))
return entity;
}
return null;
}
/**
* Find an {@link org.bukkit.entity.Entity} in a {@link org.bukkit.Chunk}
* by its unique ID.
*
* @param chunk The chunk to look in.
* @param uniqueId The entity unique ID.
*
* @return Null if not found.
*/
@Nullable
public static Entity getEntityByUUID(Chunk chunk, UUID uniqueId) {
PreCon.notNull(chunk);
PreCon.notNull(uniqueId);
Entity[] entities = chunk.getEntities();
for (Entity entity : entities) {
if (entity.getUniqueId().equals(uniqueId))
return entity;
}
return null;
}
/**
* Guarantees the removal of an {@link org.bukkit.entity.Entity} even
* if the entity instance is outdated or the chunk it's in
* is not loaded.
*
* @param entity The entity to remove.
*
* @return True if the entity was found and removed.
*/
public static boolean removeEntity(Entity entity) {
PreCon.notNull(entity);
return removeEntity(entity.getLocation().getChunk(), entity.getUniqueId());
}
/**
* Remove an {@link org.bukkit.entity.Entity} from a
* {@link org.bukkit.Chunk}, even if the chunk is not loaded.
*
* @param chunk The chunk the entity is in.
* @param entityId The entities unique ID.
*
* @return True if the entity was found and removed.
*/
public static boolean removeEntity(Chunk chunk, UUID entityId) {
PreCon.notNull(chunk);
PreCon.notNull(entityId);
Entity entity = getEntityByUUID(chunk, entityId);
if (entity == null)
return false;
entity.remove();
return true;
}
/**
* Get the damaging {@link org.bukkit.entity.Entity}. Returns the entityDamager
* unless it's a {@link org.bukkit.entity.Projectile}, in which case the projectile
* source is returned. If the projectile does not have a source then the projectile
* is returned.
*
* @param entityDamager The entity that has caused direct damage.
*/
public static Entity getDamager(Entity entityDamager) {
if (entityDamager instanceof Projectile) {
ProjectileSource source = ((Projectile) entityDamager).getShooter();
if (source instanceof Entity) {
return (Entity)source;
}
}
return entityDamager;
}
/**
* Get the closest {@link org.bukkit.entity.Entity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>The radius is spherical.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The entity source to search from.
* @param radius The radius entities must be within to be returned.
*/
@Nullable
public static Entity getClosestEntity(Entity sourceEntity, double radius) {
return getClosestEntity(sourceEntity, radius, radius, radius, null);
}
/**
* Get the closest {@link org.bukkit.entity.Entity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>The radius is spherical.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The entity source to search from.
* @param radius The radius entities must be within to be returned.
* @param validator The validator used to determine if an entity is a candidate.
*/
@Nullable
public static Entity getClosestEntity(Entity sourceEntity, double radius,
@Nullable IValidator<Entity> validator) {
return getClosestEntity(sourceEntity, radius, radius, radius, validator);
}
/**
* Get the closest {@link org.bukkit.entity.Entity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The entity source to search from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
*/
@Nullable
public static Entity getClosestEntity(Entity sourceEntity,
double radiusX, double radiusY, double radiusZ) {
return getClosestEntity(sourceEntity, radiusX, radiusY, radiusZ, null);
}
/**
* Get the closest {@link org.bukkit.entity.Entity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The entity source to search from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
* @param validator The validator used to determine if an entity is a candidate.
*/
@Nullable
public static Entity getClosestEntity(final Entity sourceEntity,
double radiusX, double radiusY, double radiusZ,
@Nullable final IValidator<Entity> validator) {
PreCon.notNull(sourceEntity);
PreCon.positiveNumber(radiusX);
PreCon.positiveNumber(radiusY);
PreCon.positiveNumber(radiusZ);
Location sourceLocation = getEntityLocation(sourceEntity, SOURCE_ENTITY_LOCATION);
List<Entity> entities = sourceEntity.getNearbyEntities(radiusX, radiusY, radiusZ);
return getClosestEntity(sourceLocation, entities,
SOURCED_CLOSEST_ENTITY_VALIDATOR.validator(validator).source(sourceEntity));
}
/**
* Get the closest {@link org.bukkit.entity.Entity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceLocation The location to check.
* @param radius The max radius to search in.
* @param validator The validator used to determine if an entity is a candidate.
*/
@Nullable
public static Entity getClosestEntity(Location sourceLocation, double radius,
@Nullable final IValidator<Entity> validator) {
PreCon.notNull(sourceLocation);
PreCon.positiveNumber(radius);
if (sourceLocation.getWorld() == null)
return null;
int xRadius = ((int)radius) >> 4;
int zRadius = xRadius;
int xSource = sourceLocation.getBlockX();
if (xSource < radius || xSource > 16 - radius)
xRadius++;
int zSource = sourceLocation.getBlockZ();
if (zSource < radius || zSource > 16 - radius)
zRadius++;
int chunkX = sourceLocation.getBlockX() >> 4;
int chunkZ = sourceLocation.getBlockZ() >> 4;
Entity closest = null;
double closestDistanceSq = 0;
for (int x = -xRadius; x <= xRadius; x++) {
for (int z = -zRadius; z <= zRadius; z++) {
Chunk chunk = sourceLocation.getWorld().getChunkAt(chunkX + x, chunkZ + z);
Entity[] entities = chunk.getEntities();
for (Entity entity : entities) {
if (validator == null && Npcs.isNpc(entity))
continue;
double distance = entity.getLocation(NEARBY_ENTITY_LOCATION)
.distanceSquared(sourceLocation);
if (distance > radius * radius)
continue;
if (closest == null ||
distance < closestDistanceSq) {
if (validator != null && !validator.isValid(entity))
continue;
closestDistanceSq = distance;
closest = entity;
}
}
}
}
return closest;
}
/**
* Get the closest {@link org.bukkit.entity.Entity} to a {@link org.bukkit.Location}.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceLocation The location to check.
* @param entities The list of entities to check.
* @param validator The validator used to determine if an entity is a candidate.
*/
@Nullable
public static Entity getClosestEntity(Location sourceLocation,
List<? extends Entity> entities,
@Nullable IValidator<Entity> validator) {
PreCon.notNull(sourceLocation);
PreCon.notNull(entities);
Entity closest = null;
double closestDist = Double.MAX_VALUE;
for (Entity entity : entities) {
if (!entity.isValid())
continue;
if (!entity.getWorld().equals(sourceLocation.getWorld()))
continue;
if (validator == null && Npcs.isNpc(entity))
continue;
Location targetLocation = getEntityLocation(entity, TARGET_ENTITY_LOCATION);
double dist = sourceLocation.distanceSquared(targetLocation);
if (dist < closestDist &&
(validator == null || validator.isValid(entity))) {
closest = entity;
closestDist = dist;
}
}
return closest;
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>The radius is spherical.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The entity source to search from.
* @param radius The radius entities must be within to be returned.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(Entity sourceEntity, double radius) {
return getClosestLivingEntity(sourceEntity, radius, radius, radius, null);
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>The radius is spherical.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The entity source to search from.
* @param radius The radius entities must be within to be returned.
* @param validator The validator used to determine if a living entity is a candidate.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(Entity sourceEntity, double radius,
@Nullable IValidator<LivingEntity> validator) {
return getClosestLivingEntity(sourceEntity, radius, radius, radius, validator);
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The entity source to search from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(Entity sourceEntity,
double radiusX, double radiusY, double radiusZ) {
return getClosestLivingEntity(sourceEntity, radiusX, radiusY, radiusZ, null);
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to the specified source
* {@link org.bukkit.entity.Entity}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The entity source to search from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
* @param validator The validator used to determine if a living entity is a candidate.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(final Entity sourceEntity,
double radiusX, double radiusY, double radiusZ,
@Nullable final IValidator<LivingEntity> validator) {
PreCon.notNull(sourceEntity);
Location sourceLocation = getEntityLocation(sourceEntity, SOURCE_ENTITY_LOCATION);
return getClosestLivingEntity(sourceLocation, radiusX, radiusY, radiusZ,
SOURCED_CLOSEST_LIVING_VALIDATOR.validator(validator).source(sourceEntity));
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to the specified source
* {@link org.bukkit.Location}.
*
* <p>The radius is spherical.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceLocation The location to search from.
* @param radius The radius entities must be within to be returned.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(Location sourceLocation, double radius) {
return getClosestLivingEntity(sourceLocation, radius, radius, radius, null);
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to the specified source
* {@link org.bukkit.Location}.
*
* <p>The radius is spherical.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceLocation The location to search from.
* @param radius The radius entities must be within to be returned.
* @param validator The validator used to determine if a living entity is a candidate.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(Location sourceLocation, double radius,
@Nullable IValidator<LivingEntity> validator) {
return getClosestLivingEntity(sourceLocation, radius, radius, radius, validator);
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to the specified source
* {@link org.bukkit.Location}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceLocation The location to search from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(Location sourceLocation,
double radiusX, double radiusY, double radiusZ) {
return getClosestLivingEntity(sourceLocation, radiusX, radiusY, radiusZ, null);
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to the specified source
* {@link org.bukkit.Location}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceLocation The location to search from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
* @param validator The validator used to determine if a living entity is a candidate.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(Location sourceLocation,
double radiusX, double radiusY, double radiusZ,
@Nullable final IValidator<LivingEntity> validator) {
PreCon.notNull(sourceLocation);
List<Entity> nearbyEntities = getNearbyEntities(sourceLocation, radiusX, radiusY, radiusZ,
CLOSEST_LIVING_VALIDATOR.validator(validator));
return (LivingEntity)getClosestEntity(sourceLocation, nearbyEntities, null);
}
/**
* Get the closest {@link org.bukkit.entity.LivingEntity} to a {@link org.bukkit.Location}.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceLocation The location to check.
* @param entities The list of entities to check.
* @param validator The validator used to determine if a living entity is a candidate.
*/
@Nullable
public static LivingEntity getClosestLivingEntity(Location sourceLocation, List<Entity> entities,
@Nullable IValidator<LivingEntity> validator) {
PreCon.notNull(sourceLocation);
PreCon.notNull(entities);
Entity closest = null;
double closestDist = Double.MAX_VALUE;
for (Entity entity : entities) {
if (!(entity instanceof LivingEntity))
continue;
if (!entity.getWorld().equals(sourceLocation.getWorld()))
continue;
if (validator == null && Npcs.isNpc(entity))
continue;
Location targetLocation = getEntityLocation(entity, TARGET_ENTITY_LOCATION);
double dist = sourceLocation.distanceSquared(targetLocation);
if (dist < closestDist &&
(validator == null || validator.isValid((LivingEntity)entity))) {
closest = entity;
closestDist = dist;
}
}
return (LivingEntity)closest;
}
/**
* Get entities within a specified radius of a {@link org.bukkit.Location}.
*
* <p>The radius is spherical.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param location The location to check from.
* @param radius The radius entities must be within to be returned.
*/
public static List<Entity> getNearbyEntities(Location location, double radius) {
return getNearbyEntities(location, radius, radius, radius, null);
}
/**
* Get entities within a specified radius of a {@link org.bukkit.Location}.
*
* <p>The radius is spherical.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param location The location to check from.
* @param radius The radius entities must be within to be returned.
* @param validator Optional validator used to validate each entity within the radius.
*/
public static List<Entity> getNearbyEntities(Location location, double radius,
@Nullable IValidator<Entity> validator) {
return getNearbyEntities(location, radius, radius, radius, validator);
}
/**
* Get entities within a specified radius of a {@link org.bukkit.Location}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param location The location to check from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
*/
public static List<Entity> getNearbyEntities(Location location,
double radiusX, double radiusY, double radiusZ) {
return getNearbyEntities(location, radiusX, radiusY, radiusZ, null);
}
/**
* Get entities within a specified radius of a {@link org.bukkit.Location}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param location The location to check from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
* @param validator Optional validator used to validate each entity within the radius.
*/
public static List<Entity> getNearbyEntities(Location location,
double radiusX, double radiusY, double radiusZ,
@Nullable IValidator<Entity> validator) {
PreCon.notNull(location);
PreCon.positiveNumber(radiusX);
PreCon.positiveNumber(radiusY);
PreCon.positiveNumber(radiusZ);
List<Entity> results = new ArrayList<>(15);
World world = location.getWorld();
if (world == null)
return results;
int xStart = getStartChunk(location.getX(), radiusX);
int xEnd = getEndChunk(location.getX(), radiusX);
int zStart = getStartChunk(location.getZ(), radiusZ);
int zEnd = getEndChunk(location.getZ(), radiusZ);
for (int x = xStart; x <= xEnd; x++) {
for (int z = zStart; z <= zEnd; z++) {
Chunk chunk = world.getChunkAt(x, z);
if (!chunk.isLoaded())
chunk.load();
Entity[] entities = chunk.getEntities();
for (Entity entity : entities) {
if (!entity.isValid())
continue;
if (!entity.getWorld().equals(world))
continue;
if (validator == null && Npcs.isNpc(entity))
continue;
Location entityLocation = getEntityLocation(entity, NEARBY_ENTITY_LOCATION);
if (!LocationUtils.isInRange(location, entityLocation, radiusX, radiusY, radiusZ))
continue;
if (validator != null && !validator.isValid(entity))
continue;
results.add(entity);
}
}
}
return results;
}
/**
* Get entities within a specified radius of a {@link org.bukkit.Location}.
*
* <p>The radius is spherical.</p>
*
* <p>The source entity is not included in the result.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The source entity to check from.
* @param radius The radius entities must be within to be returned.
* @param validator Optional validator used to validate each entity within the radius.
*/
public static List<Entity> getNearbyEntities(Entity sourceEntity, double radius,
@Nullable IValidator<Entity> validator) {
return getNearbyEntities(sourceEntity, radius, radius, radius, validator);
}
/**
* Get entities within a specified radius of a {@link org.bukkit.Location}.
*
* <p>If all radius values are equal, the radius is spherical. Otherwise the radius is cuboid.</p>
*
* <p>The source entity is not included in the result.</p>
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The source entity to check from.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
* @param validator Optional validator used to validate each entity within the radius.
*/
public static List<Entity> getNearbyEntities(Entity sourceEntity,
double radiusX, double radiusY, double radiusZ,
@Nullable IValidator<Entity> validator) {
PreCon.notNull(sourceEntity);
PreCon.positiveNumber(radiusX);
PreCon.positiveNumber(radiusY);
PreCon.positiveNumber(radiusZ);
List<Entity> results = new ArrayList<>(15);
World world = sourceEntity.getWorld();
if (world == null)
return results;
Location location = sourceEntity.getLocation(SOURCE_ENTITY_LOCATION);
int xStart = getStartChunk(location.getX(), radiusX);
int xEnd = getEndChunk(location.getX(), radiusX);
int zStart = getStartChunk(location.getZ(), radiusZ);
int zEnd = getEndChunk(location.getZ(), radiusZ);
for (int x = xStart; x <= xEnd; x++) {
for (int z = zStart; z <= zEnd; z++) {
Chunk chunk = world.getChunkAt(x, z);
if (!chunk.isLoaded())
chunk.load();
Entity[] entities = chunk.getEntities();
for (Entity entity : entities) {
if (!entity.isValid())
continue;
if (sourceEntity.getUniqueId().equals(entity.getUniqueId()))
continue;
if (!entity.getWorld().equals(world))
continue;
if (validator == null && Npcs.isNpc(entity))
continue;
Location entityLocation = getEntityLocation(entity, NEARBY_ENTITY_LOCATION);
if (!LocationUtils.isInRange(location, entityLocation, radiusX, radiusY, radiusZ))
continue;
if (validator != null && !validator.isValid(entity))
continue;
results.add(entity);
}
}
}
return results;
}
/**
* Determine if there is an entity of a specified type within the specified radius
* of a source entity.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The source entity to check from.
* @param type The {@link EntityType} to search for.
* @param radius The radius entities must be within to be returned.
*/
public static boolean hasNearbyEntityType(Entity sourceEntity, EntityType type, double radius) {
return hasNearbyEntityType(sourceEntity, type, radius, radius, radius, null);
}
/**
* Determine if there is an entity of a specified type within the specified radius
* of a source entity.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The source entity to check from.
* @param type The {@link EntityType} to search for.
* @param radius The radius entities must be within to be returned.
* @param validator Optional validator used to validate each entity of the specified type
* found within the radius.
*/
public static boolean hasNearbyEntityType(Entity sourceEntity, EntityType type, double radius,
@Nullable IValidator<Entity> validator) {
return hasNearbyEntityType(sourceEntity, type, radius, radius, radius, validator);
}
/**
* Determine if there is an entity of a specified type within the specified radius
* of a source entity.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param sourceEntity The source entity to check from.
* @param type The {@link EntityType} to search for.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
* @param validator Optional validator used to validate each entity of the specified type
* found within the radius.
*/
public static boolean hasNearbyEntityType(Entity sourceEntity, EntityType type,
double radiusX, double radiusY, double radiusZ,
@Nullable IValidator<Entity> validator) {
PreCon.notNull(sourceEntity);
PreCon.notNull(type);
PreCon.positiveNumber(radiusX);
PreCon.positiveNumber(radiusY);
PreCon.positiveNumber(radiusZ);
List<Entity> entities = sourceEntity.getNearbyEntities(radiusX, radiusY, radiusZ);
for (Entity entity : entities) {
if (!entity.isValid())
continue;
if (entity.getType() != type)
continue;
if (validator == null && Npcs.isNpc(entity))
continue;
if (validator != null && !validator.isValid(entity))
continue;
return true;
}
return false;
}
/**
* Determine if there is an entity of a specified type within the specified radius
* of a location.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param location The location to check from.
* @param type The {@link EntityType} to search for.
* @param radius The radius entities must be within to be returned.
*/
public static boolean hasNearbyEntityType(Location location, EntityType type, double radius) {
return hasNearbyEntityType(location, type, radius, radius, radius, null);
}
/**
* Determine if there is an entity of a specified type within the specified radius
* of a location.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param location The location to check from.
* @param type The {@link EntityType} to search for.
* @param radius The radius entities must be within to be returned.
* @param validator Optional validator used to validate each entity of the specified type
* found within the radius.
*/
public static boolean hasNearbyEntityType(Location location, EntityType type, double radius,
@Nullable IValidator<Entity> validator) {
return hasNearbyEntityType(location, type, radius, radius, radius, validator);
}
/**
* Determine if there is an entity of a specified type within the specified radius
* of a location.
*
* <p>Does not include Npc's if validator is not specified.</p>
*
* @param location The location to check from.
* @param type The {@link EntityType} to search for.
* @param radiusX The x-axis radius entities must be within to be returned.
* @param radiusY The y-axis radius entities must be within to be returned.
* @param radiusZ The z-axis radius entities must be within to be returned.
* @param validator Optional validator used to validate each entity of the specified type
* found within the radius.
*/
public static boolean hasNearbyEntityType(Location location, EntityType type,
double radiusX, double radiusY, double radiusZ,
@Nullable IValidator<Entity> validator) {
PreCon.notNull(location);
PreCon.notNull(type);
PreCon.positiveNumber(radiusX);
PreCon.positiveNumber(radiusY);
PreCon.positiveNumber(radiusZ);
World world = location.getWorld();
if (world == null)
return false;
int xStart = getStartChunk(location.getX(), radiusX);
int xEnd = getEndChunk(location.getX(), radiusX);
int zStart = getStartChunk(location.getZ(), radiusZ);
int zEnd = getEndChunk(location.getZ(), radiusZ);
for (int x = xStart; x <= xEnd; x++) {
for (int z = zStart; z <= zEnd; z++) {
Chunk chunk = world.getChunkAt(x, z);
if (!chunk.isLoaded())
chunk.load();
Entity[] entities = chunk.getEntities();
for (Entity entity : entities) {
if (!entity.isValid())
continue;
if (entity.getType() != type)
continue;
if (!entity.getWorld().equals(world))
continue;
if (validator == null && Npcs.isNpc(entity))
continue;
Location entityLocation = getEntityLocation(entity, NEARBY_ENTITY_LOCATION);
if (!LocationUtils.isInRange(location, entityLocation, radiusX, radiusY, radiusZ))
continue;
if (validator != null && !validator.isValid(entity))
continue;
return true;
}
}
}
return false;
}
/**
* Get a monster spawn egg from an entity type.
*
* @param type The entity type.
*
* @return The spawn egg as an item stack or null if the type does not
* have an equivalent spawn egg.
*/
@Nullable
public static ItemStack getEgg(EntityType type) {
PreCon.notNull(type);
switch (type) {
case CREEPER:
case SKELETON:
case SPIDER:
case GIANT:
case ZOMBIE:
case SLIME:
case GHAST:
case PIG_ZOMBIE:
case ENDERMAN:
case CAVE_SPIDER:
case SILVERFISH:
case BLAZE:
case MAGMA_CUBE:
case ENDER_DRAGON:
case WITHER:
case ENDERMITE:
case GUARDIAN:
case PIG:
case SHEEP:
case COW:
case CHICKEN:
case SQUID:
case WOLF:
case MUSHROOM_COW:
case SNOWMAN:
case OCELOT:
case IRON_GOLEM:
case HORSE:
case RABBIT:
return new ItemStack(Material.MONSTER_EGG, 1, (byte)type.getTypeId());
default:
return null;
}
}
/*
* Get the root entity in a passenger/vehicle entity relationship
*/
public static Entity getRootVehicle(Entity entity) {
while (entity.getVehicle() != null) {
entity = entity.getVehicle();
}
return entity;
}
/**
* Get the entities location. If the method is invoked from the primary thread, the result
* is returned in the provided output location, otherwise a new location object is returned.
*
* @param entity The entity to get a location from.
* @param output The output location.
*
* @return The output location if invoked on primary thread, otherwise a new location object.
*/
public static Location getEntityLocation(Entity entity, Location output) {
return Bukkit.isPrimaryThread()
? entity.getLocation(output)
: entity.getLocation();
}
/**
* Get the entities location. If the method is invoked from the specified thread, the result
* is returned in the provided output location, otherwise a new location object is returned.
*
* @param entity The entity to get a location from.
* @param output The output location.
* @param thread The thread to check.
*
* @return The output location if invoked on primary thread, otherwise a new location object.
*/
public static Location getEntityLocation(Entity entity, Location output, Thread thread) {
return Thread.currentThread().equals(thread)
? entity.getLocation(output)
: entity.getLocation();
}
/**
* Returns a {@link ITrackedEntity} object used to get the latest instance of an
* {@link org.bukkit.entity.Entity}.
*
* <p>This is used when an entity reference needs to be kept. Entity references are changed
* whenever the entity is in a chunk that loads/unloads. The entity id can change and references
* become outdated and no longer represent the intended entity.</p>
*
* @param entity The entity to track.
*/
public static ITrackedEntity trackEntity(Entity entity) {
PreCon.notNull(entity);
return Nucleus.getEntityTracker().trackEntity(entity);
}
private static int getStartChunk(double center, double radius) {
double start = center - radius - 2.0D;
double chunkStart = Math.floor(start / 16.0D);
return (int)chunkStart;
}
private static int getEndChunk(double center, double radius) {
double end = center + radius + 2.0D;
double chunkEnd = Math.floor(end / 16.0D);
return (int)chunkEnd;
}
private static class ClosestLivingValidator implements IValidator<Entity> {
IValidator<LivingEntity> validator;
@Override
public boolean isValid(Entity entity) {
return entity instanceof LivingEntity &&
((validator == null && !Npcs.isNpc(entity)) ||
validator == null || validator.isValid((LivingEntity)entity));
}
public IValidator<Entity> validator(@Nullable IValidator<LivingEntity> validator) {
this.validator = validator;
return this;
}
}
private static class SourcedClosestLivingValidator implements IValidator<LivingEntity> {
IValidator<LivingEntity> validator;
Entity source;
@Override
public boolean isValid(LivingEntity entity) {
return !entity.equals(source) && (validator == null && !Npcs.isNpc(entity)) ||
validator == null || validator.isValid(entity);
}
public SourcedClosestLivingValidator validator(@Nullable IValidator<LivingEntity> validator) {
this.validator = validator;
return this;
}
public SourcedClosestLivingValidator source(Entity source) {
this.source = source;
return this;
}
}
private static class SourcedClosestEntityValidator implements IValidator<Entity> {
IValidator<Entity> validator;
Entity source;
@Override
public boolean isValid(Entity entity) {
return !entity.equals(source) && (validator == null && !Npcs.isNpc(entity)) ||
validator == null || validator.isValid(entity);
}
public SourcedClosestEntityValidator validator(@Nullable IValidator<Entity> validator) {
this.validator = validator;
return this;
}
public SourcedClosestEntityValidator source(Entity source) {
this.source = source;
return this;
}
}
}