2020-07-11 14:16:36 +02:00
|
|
|
package net.minestom.server.utils.entity;
|
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
|
2020-07-29 22:55:25 +02:00
|
|
|
import net.minestom.server.entity.Entity;
|
2020-07-11 14:16:36 +02:00
|
|
|
import net.minestom.server.entity.EntityType;
|
2021-01-08 23:47:31 +01:00
|
|
|
import net.minestom.server.entity.GameMode;
|
|
|
|
import net.minestom.server.entity.Player;
|
|
|
|
import net.minestom.server.instance.Instance;
|
|
|
|
import net.minestom.server.utils.MathUtils;
|
|
|
|
import net.minestom.server.utils.Position;
|
2020-07-11 14:16:36 +02:00
|
|
|
import net.minestom.server.utils.math.IntRange;
|
2021-01-08 23:47:31 +01:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
2020-07-11 14:16:36 +02:00
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
import java.util.*;
|
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
import java.util.stream.Collectors;
|
2020-07-29 22:55:25 +02:00
|
|
|
|
2020-10-17 13:24:18 +02:00
|
|
|
// TODO
|
|
|
|
|
2020-07-11 14:16:36 +02:00
|
|
|
/**
|
2020-10-17 13:24:18 +02:00
|
|
|
* Represents a query which can be call to find one or multiple entities.
|
|
|
|
* It is based on the target selectors used in commands.
|
2020-07-11 14:16:36 +02:00
|
|
|
*/
|
|
|
|
public class EntityFinder {
|
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
private TargetSelector targetSelector;
|
2020-07-11 14:16:36 +02:00
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
private EntitySort entitySort = EntitySort.ARBITRARY;
|
2020-07-11 14:16:36 +02:00
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
// Position
|
|
|
|
private Position startPosition = new Position();
|
2021-01-09 03:31:24 +01:00
|
|
|
private Float dx, dy, dz;
|
2020-07-11 14:16:36 +02:00
|
|
|
private IntRange distance;
|
|
|
|
|
|
|
|
// By traits
|
2021-01-09 03:31:24 +01:00
|
|
|
private Integer limit;
|
2021-01-09 00:59:03 +01:00
|
|
|
private final ToggleableMap<EntityType> entityTypes = new ToggleableMap<>();
|
2021-01-09 03:31:24 +01:00
|
|
|
private final ToggleableMap<String> names = new ToggleableMap<>();
|
2021-01-08 23:47:31 +01:00
|
|
|
|
|
|
|
// Players specific
|
2021-01-09 00:59:03 +01:00
|
|
|
private final ToggleableMap<GameMode> gameModes = new ToggleableMap<>();
|
2021-01-08 23:47:31 +01:00
|
|
|
private IntRange level;
|
|
|
|
|
|
|
|
public EntityFinder setTargetSelector(@NotNull TargetSelector targetSelector) {
|
|
|
|
this.targetSelector = targetSelector;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public EntityFinder setEntitySort(@NotNull EntitySort entitySort) {
|
|
|
|
this.entitySort = entitySort;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public EntityFinder setStartPosition(@NotNull Position startPosition) {
|
|
|
|
this.startPosition = startPosition;
|
|
|
|
return this;
|
|
|
|
}
|
2020-07-11 14:16:36 +02:00
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
public EntityFinder setDistance(@NotNull IntRange distance) {
|
|
|
|
this.distance = distance;
|
|
|
|
return this;
|
2020-07-11 14:16:36 +02:00
|
|
|
}
|
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
public EntityFinder setLimit(int limit) {
|
2021-01-09 03:31:24 +01:00
|
|
|
this.limit = limit;
|
2021-01-08 23:47:31 +01:00
|
|
|
return this;
|
2020-07-11 14:16:36 +02:00
|
|
|
}
|
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
public EntityFinder setLevel(@NotNull IntRange level) {
|
|
|
|
this.level = level;
|
|
|
|
return this;
|
2020-07-11 14:16:36 +02:00
|
|
|
}
|
|
|
|
|
2021-01-09 00:59:03 +01:00
|
|
|
public EntityFinder setEntity(@NotNull EntityType entityType, @NotNull ToggleableType toggleableType) {
|
|
|
|
this.entityTypes.put(entityType, toggleableType.getValue());
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-01-09 03:31:24 +01:00
|
|
|
public EntityFinder setName(@NotNull String name, @NotNull ToggleableType toggleableType) {
|
|
|
|
this.names.put(name, toggleableType.getValue());
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-01-09 00:59:03 +01:00
|
|
|
public EntityFinder setGameMode(@NotNull GameMode gameMode, @NotNull ToggleableType toggleableType) {
|
|
|
|
this.gameModes.put(gameMode, toggleableType.getValue());
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-01-09 03:31:24 +01:00
|
|
|
public EntityFinder setDifference(float dx, float dy, float dz) {
|
|
|
|
this.dx = dx;
|
|
|
|
this.dy = dy;
|
|
|
|
this.dz = dz;
|
2021-01-08 23:47:31 +01:00
|
|
|
return this;
|
2020-07-11 14:16:36 +02:00
|
|
|
}
|
|
|
|
|
2020-07-29 22:55:25 +02:00
|
|
|
/**
|
|
|
|
* Find a list of entities (could be empty) based on the conditions
|
|
|
|
*
|
2021-01-08 23:47:31 +01:00
|
|
|
* @return all entities validating the conditions, can be empty
|
2020-07-29 22:55:25 +02:00
|
|
|
*/
|
2021-01-08 23:47:31 +01:00
|
|
|
@NotNull
|
|
|
|
public List<Entity> find(@NotNull Instance instance, @Nullable Entity self) {
|
|
|
|
List<Entity> result = findTarget(instance, targetSelector, startPosition, self);
|
|
|
|
|
|
|
|
// Fast exist if there is nothing to process
|
|
|
|
if (result.isEmpty())
|
|
|
|
return result;
|
|
|
|
|
|
|
|
// Distance argument
|
|
|
|
if (distance != null) {
|
|
|
|
final int minDistance = distance.getMinimum();
|
|
|
|
final int maxDistance = distance.getMaximum();
|
|
|
|
result = result.stream().filter(entity -> {
|
|
|
|
final int distance = (int) entity.getDistance(self);
|
|
|
|
return MathUtils.isBetween(distance, minDistance, maxDistance);
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Diff X/Y/Z
|
2021-01-09 03:31:24 +01:00
|
|
|
if (dx != null || dy != null || dz != null) {
|
2021-01-08 23:47:31 +01:00
|
|
|
result = result.stream().filter(entity -> {
|
|
|
|
final Position entityPosition = entity.getPosition();
|
2021-01-09 03:31:24 +01:00
|
|
|
if (dx != null && !MathUtils.isBetweenUnordered(
|
2021-01-08 23:47:31 +01:00
|
|
|
entityPosition.getX(),
|
2021-01-09 03:31:24 +01:00
|
|
|
startPosition.getX(), dx))
|
2021-01-08 23:47:31 +01:00
|
|
|
return false;
|
|
|
|
|
2021-01-09 03:31:24 +01:00
|
|
|
if (dy != null && !MathUtils.isBetweenUnordered(
|
2021-01-08 23:47:31 +01:00
|
|
|
entityPosition.getY(),
|
2021-01-09 03:31:24 +01:00
|
|
|
startPosition.getY(), dy))
|
2021-01-08 23:47:31 +01:00
|
|
|
return false;
|
|
|
|
|
2021-01-09 03:31:24 +01:00
|
|
|
if (dz != null && !MathUtils.isBetweenUnordered(
|
2021-01-08 23:47:31 +01:00
|
|
|
entityPosition.getZ(),
|
2021-01-09 03:31:24 +01:00
|
|
|
startPosition.getZ(), dz))
|
2021-01-08 23:47:31 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Entity type
|
2021-01-09 00:59:03 +01:00
|
|
|
if (!entityTypes.isEmpty()) {
|
2021-01-09 03:31:24 +01:00
|
|
|
result = result.stream().filter(entity ->
|
|
|
|
filterToggleableMap(entity, entity.getEntityType(), entityTypes))
|
|
|
|
.collect(Collectors.toList());
|
2021-01-08 23:47:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// GameMode
|
2021-01-09 00:59:03 +01:00
|
|
|
if (!gameModes.isEmpty()) {
|
2021-01-08 23:47:31 +01:00
|
|
|
final GameMode requirement = gameModes.requirement;
|
|
|
|
result = result.stream().filter(entity -> {
|
|
|
|
if (!(entity instanceof Player))
|
|
|
|
return false;
|
2021-01-09 03:31:24 +01:00
|
|
|
return filterToggleableMap(entity, ((Player) entity).getGameMode(), gameModes);
|
2021-01-08 23:47:31 +01:00
|
|
|
}).collect(Collectors.toList());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Level
|
|
|
|
if (level != null) {
|
|
|
|
final int minLevel = level.getMinimum();
|
|
|
|
final int maxLevel = level.getMaximum();
|
|
|
|
result = result.stream().filter(entity -> {
|
|
|
|
if (!(entity instanceof Player))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
final int level = ((Player) entity).getLevel();
|
|
|
|
return MathUtils.isBetween(level, minLevel, maxLevel);
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
}
|
|
|
|
|
2021-01-09 03:31:24 +01:00
|
|
|
// Name
|
|
|
|
if (!names.isEmpty()) {
|
|
|
|
// TODO entity name
|
|
|
|
result = result.stream().filter(entity -> {
|
|
|
|
if (!(entity instanceof Player))
|
|
|
|
return false;
|
|
|
|
return filterToggleableMap(entity, ((Player) entity).getUsername(), names);
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
}
|
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
|
|
|
|
// Sort & limit
|
2021-01-09 03:31:24 +01:00
|
|
|
if (entitySort != EntitySort.ARBITRARY || limit != null) {
|
2021-01-08 23:47:31 +01:00
|
|
|
result = result.stream()
|
|
|
|
.sorted((ent1, ent2) -> {
|
|
|
|
switch (entitySort) {
|
|
|
|
case ARBITRARY:
|
|
|
|
case RANDOM:
|
|
|
|
// RANDOM is handled below
|
|
|
|
return 1;
|
|
|
|
case FURTHEST:
|
|
|
|
return startPosition.getDistance(ent1.getPosition()) >
|
|
|
|
startPosition.getDistance(ent2.getPosition()) ?
|
|
|
|
1 : 0;
|
|
|
|
case NEAREST:
|
|
|
|
return startPosition.getDistance(ent1.getPosition()) <
|
|
|
|
startPosition.getDistance(ent2.getPosition()) ?
|
|
|
|
1 : 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
})
|
2021-01-09 03:31:24 +01:00
|
|
|
.limit(limit != null ? limit : Integer.MAX_VALUE)
|
2021-01-08 23:47:31 +01:00
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
|
|
if (entitySort == EntitySort.RANDOM) {
|
|
|
|
Collections.shuffle(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum TargetSelector {
|
|
|
|
NEAREST_PLAYER, RANDOM_PLAYER, ALL_PLAYERS, ALL_ENTITIES, SELF
|
2020-07-29 22:55:25 +02:00
|
|
|
}
|
|
|
|
|
2020-07-11 14:16:36 +02:00
|
|
|
public enum EntitySort {
|
|
|
|
ARBITRARY, FURTHEST, NEAREST, RANDOM
|
|
|
|
}
|
2021-01-08 23:47:31 +01:00
|
|
|
|
2021-01-09 00:59:03 +01:00
|
|
|
public enum ToggleableType {
|
|
|
|
INCLUDE(true), EXCLUDE(false);
|
|
|
|
|
|
|
|
private final boolean value;
|
|
|
|
|
|
|
|
ToggleableType(boolean value) {
|
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean getValue() {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-08 23:47:31 +01:00
|
|
|
private static class ToggleableMap<T> extends Object2BooleanOpenHashMap<T> {
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
private T requirement;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
private static List<Entity> findTarget(@NotNull Instance instance, @NotNull TargetSelector targetSelector,
|
|
|
|
@NotNull Position startPosition, @Nullable Entity self) {
|
|
|
|
|
|
|
|
if (targetSelector == TargetSelector.NEAREST_PLAYER) {
|
|
|
|
Entity entity = null;
|
|
|
|
float closestDistance = Float.MAX_VALUE;
|
|
|
|
|
|
|
|
Set<Player> instancePlayers = instance.getPlayers();
|
|
|
|
for (Player player : instancePlayers) {
|
|
|
|
final float distance = player.getPosition().getDistance(startPosition);
|
|
|
|
if (distance < closestDistance) {
|
|
|
|
entity = player;
|
|
|
|
closestDistance = distance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Arrays.asList(entity);
|
|
|
|
} else if (targetSelector == TargetSelector.RANDOM_PLAYER) {
|
|
|
|
Set<Player> instancePlayers = instance.getPlayers();
|
|
|
|
final int index = ThreadLocalRandom.current().nextInt(instancePlayers.size());
|
|
|
|
final Player player = instancePlayers.stream().skip(index).findFirst().orElseThrow();
|
|
|
|
return Arrays.asList(player);
|
|
|
|
} else if (targetSelector == TargetSelector.ALL_PLAYERS) {
|
|
|
|
return new ArrayList<>(instance.getPlayers());
|
|
|
|
} else if (targetSelector == TargetSelector.ALL_ENTITIES) {
|
|
|
|
return new ArrayList<>(instance.getEntities());
|
|
|
|
} else if (targetSelector == TargetSelector.SELF) {
|
|
|
|
return Arrays.asList(self);
|
|
|
|
}
|
|
|
|
throw new IllegalStateException("Weird thing happened");
|
|
|
|
}
|
2021-01-09 03:31:24 +01:00
|
|
|
|
|
|
|
private static <T> boolean filterToggleableMap(@NotNull Entity entity, @NotNull T value, @NotNull ToggleableMap<T> map) {
|
|
|
|
if (!(entity instanceof Player))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
final T requirement = map.requirement;
|
|
|
|
// true if the entity type has not been mentioned or if is accepted
|
|
|
|
return (!map.containsKey(value) && requirement == null) ||
|
|
|
|
Objects.equals(requirement, value) ||
|
|
|
|
map.getBoolean(value);
|
|
|
|
}
|
2020-07-11 14:16:36 +02:00
|
|
|
}
|