mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-29 12:37:42 +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.LivingEntity;
|
||||
import net.minestom.server.entity.ai.TargetSelector;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private final float range;
|
||||
private final Class<? extends LivingEntity>[] entitiesTarget;
|
||||
private final double range;
|
||||
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
|
||||
@Deprecated
|
||||
public ClosestEntityTarget(@NotNull EntityCreature entityCreature, float range,
|
||||
@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);
|
||||
this.range = range;
|
||||
this.entitiesTarget = entitiesTarget;
|
||||
this.targetPredicate = targetPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity findTarget() {
|
||||
final Instance instance = getEntityCreature().getInstance();
|
||||
final Chunk currentChunk = instance.getChunkAt(entityCreature.getPosition());
|
||||
if (currentChunk == null) {
|
||||
|
||||
Instance instance = entityCreature.getInstance();
|
||||
|
||||
if (instance == 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