mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-01 14:07:43 +01:00
Use predicate for entity targeting (#1099)
* Use predicate for entity targeting * Change range variable in deprecated constructor from double to float * Use distance squared
This commit is contained in:
parent
3821d204cf
commit
bab2c22a51
@ -4,111 +4,69 @@ import net.minestom.server.entity.Entity;
|
|||||||
import net.minestom.server.entity.EntityCreature;
|
import net.minestom.server.entity.EntityCreature;
|
||||||
import net.minestom.server.entity.LivingEntity;
|
import net.minestom.server.entity.LivingEntity;
|
||||||
import net.minestom.server.entity.ai.TargetSelector;
|
import net.minestom.server.entity.ai.TargetSelector;
|
||||||
import net.minestom.server.instance.Chunk;
|
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.function.Predicate;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Target the closest targetable entity (based on the class array)
|
* Target the closest targetable entity (based on the target predicate)
|
||||||
*/
|
*/
|
||||||
public class ClosestEntityTarget extends TargetSelector {
|
public class ClosestEntityTarget extends TargetSelector {
|
||||||
|
|
||||||
private final float range;
|
private final double range;
|
||||||
private final Class<? extends LivingEntity>[] entitiesTarget;
|
private final Predicate<Entity> targetPredicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param entityCreature the entity (self)
|
||||||
|
* @param range the maximum range the entity can target others within
|
||||||
|
* @param entitiesTarget the entities to target by class
|
||||||
|
* @deprecated Use {@link #ClosestEntityTarget(EntityCreature, double, Predicate)}
|
||||||
|
*/
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
|
@Deprecated
|
||||||
public ClosestEntityTarget(@NotNull EntityCreature entityCreature, float range,
|
public ClosestEntityTarget(@NotNull EntityCreature entityCreature, float range,
|
||||||
@NotNull Class<? extends LivingEntity>... entitiesTarget) {
|
@NotNull Class<? extends LivingEntity>... entitiesTarget) {
|
||||||
|
this(entityCreature, range, ent -> {
|
||||||
|
Class<? extends Entity> clazz = ent.getClass();
|
||||||
|
for (Class<? extends LivingEntity> targetClass : entitiesTarget) {
|
||||||
|
if (targetClass.isAssignableFrom(clazz)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param entityCreature the entity (self)
|
||||||
|
* @param range the maximum range the entity can target others within
|
||||||
|
* @param targetPredicate the predicate used to check if the other entity can be targeted
|
||||||
|
*/
|
||||||
|
public ClosestEntityTarget(@NotNull EntityCreature entityCreature, double range,
|
||||||
|
@NotNull Predicate<Entity> targetPredicate) {
|
||||||
super(entityCreature);
|
super(entityCreature);
|
||||||
this.range = range;
|
this.range = range;
|
||||||
this.entitiesTarget = entitiesTarget;
|
this.targetPredicate = targetPredicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Entity findTarget() {
|
public Entity findTarget() {
|
||||||
final Instance instance = getEntityCreature().getInstance();
|
|
||||||
final Chunk currentChunk = instance.getChunkAt(entityCreature.getPosition());
|
Instance instance = entityCreature.getInstance();
|
||||||
if (currentChunk == null) {
|
|
||||||
|
if (instance == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Chunk> chunks = getNeighbours(instance, currentChunk.getChunkX(), currentChunk.getChunkZ());
|
return instance.getNearbyEntities(entityCreature.getPosition(), range).stream()
|
||||||
|
// Don't target our self and make sure entity is valid
|
||||||
|
.filter(ent -> !entityCreature.equals(ent) && !ent.isRemoved())
|
||||||
|
.filter(targetPredicate)
|
||||||
|
.min(Comparator.comparingDouble(e -> e.getDistanceSquared(entityCreature)))
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
Entity entity = null;
|
|
||||||
double distance = Double.MAX_VALUE;
|
|
||||||
|
|
||||||
for (Chunk chunk : chunks) {
|
|
||||||
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
|
||||||
|
|
||||||
for (Entity ent : entities) {
|
|
||||||
|
|
||||||
// Only target living entities
|
|
||||||
if (!(ent instanceof LivingEntity)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't target itself
|
|
||||||
if (ent.equals(entityCreature)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ent.isRemoved()) {
|
|
||||||
// Entity not valid
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the entity type can be targeted
|
|
||||||
final Class<? extends Entity> clazz = ent.getClass();
|
|
||||||
boolean correct = false;
|
|
||||||
for (Class<? extends LivingEntity> targetClass : entitiesTarget) {
|
|
||||||
if (targetClass.isAssignableFrom(clazz)) {
|
|
||||||
correct = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!correct) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check distance
|
|
||||||
final double d = entityCreature.getDistance(ent);
|
|
||||||
if ((entity == null || d < distance) && d < range) {
|
|
||||||
entity = ent;
|
|
||||||
distance = d;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Chunk> getNeighbours(Instance instance, int chunkX, int chunkZ) {
|
|
||||||
List<Chunk> chunks = new ArrayList<>();
|
|
||||||
// Constants used to loop through the neighbors
|
|
||||||
final int[] posX = {1, 0, -1};
|
|
||||||
final int[] posZ = {1, 0, -1};
|
|
||||||
|
|
||||||
for (int x : posX) {
|
|
||||||
for (int z : posZ) {
|
|
||||||
|
|
||||||
final int targetX = chunkX + x;
|
|
||||||
final int targetZ = chunkZ + z;
|
|
||||||
final Chunk chunk = instance.getChunk(targetX, targetZ);
|
|
||||||
if (ChunkUtils.isLoaded(chunk)) {
|
|
||||||
// Chunk is loaded, add it
|
|
||||||
chunks.add(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return chunks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
package net.minestom.server.entity.ai;
|
||||||
|
|
||||||
|
import net.minestom.server.api.Env;
|
||||||
|
import net.minestom.server.api.EnvTest;
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
|
import net.minestom.server.entity.EntityCreature;
|
||||||
|
import net.minestom.server.entity.EntityType;
|
||||||
|
import net.minestom.server.entity.ai.target.ClosestEntityTarget;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
@EnvTest
|
||||||
|
public class ClosestEntityTargetTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validFindTarget(Env env) {
|
||||||
|
var instance = env.createFlatInstance();
|
||||||
|
|
||||||
|
var self = new EntityCreature(EntityType.ZOMBIE);
|
||||||
|
self.setInstance(instance, new Pos(0, 42, 0)).join();
|
||||||
|
|
||||||
|
var spider = new EntityCreature(EntityType.SPIDER);
|
||||||
|
spider.setInstance(instance, new Pos(-3, 42, -3)).join();
|
||||||
|
|
||||||
|
var secondSpider = new EntityCreature(EntityType.SPIDER);
|
||||||
|
secondSpider.setInstance(instance, new Pos(-4, 42, -4)).join();
|
||||||
|
|
||||||
|
var skeleton = new EntityCreature(EntityType.SKELETON);
|
||||||
|
skeleton.setInstance(instance, new Pos(5, 42, 5)).join();
|
||||||
|
|
||||||
|
var zombie = new EntityCreature(EntityType.ZOMBIE);
|
||||||
|
zombie.setInstance(instance, new Pos(10, 42, -10)).join();
|
||||||
|
|
||||||
|
assertEquals(5, instance.getEntities().size(), "Not all entities are in the instance");
|
||||||
|
|
||||||
|
assertNull(
|
||||||
|
new ClosestEntityTarget(self, 1, e -> true).findTarget(),
|
||||||
|
"Entity targets it self"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(spider,
|
||||||
|
new ClosestEntityTarget(self, 20, e -> e.getEntityType() == EntityType.SPIDER).findTarget(),
|
||||||
|
"The closest spider was not selected"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertNull(
|
||||||
|
new ClosestEntityTarget(self, 2, e -> e.getEntityType() == EntityType.SPIDER).findTarget(),
|
||||||
|
"Range distance is not being considered"
|
||||||
|
);
|
||||||
|
|
||||||
|
zombie.remove();
|
||||||
|
|
||||||
|
assertNull(
|
||||||
|
new ClosestEntityTarget(self, 20, e -> e.getEntityType() == EntityType.ZOMBIE).findTarget(),
|
||||||
|
"Removed entities are included in target selection"
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user