Minestom/src/main/java/net/minestom/server/utils/entity/EntityFinder.java

348 lines
13 KiB
Java
Raw Normal View History

2020-07-11 14:16:36 +02:00
package net.minestom.server.utils.entity;
import it.unimi.dsi.fastutil.objects.Object2BooleanMaps;
2021-01-08 23:47:31 +01:00
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
2021-01-10 02:30:57 +01:00
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandSender;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
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;
2020-07-11 14:16:36 +02:00
import net.minestom.server.utils.math.IntRange;
import net.minestom.server.utils.validate.Check;
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;
// TODO
2020-07-11 14:16:36 +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
2021-07-24 04:22:50 +02:00
private Point startPosition;
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<>();
private String constantName;
private UUID constantUuid;
private final ToggleableMap<String> names = new ToggleableMap<>();
private final ToggleableMap<UUID> uuids = 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;
}
2021-07-25 06:30:49 +02:00
public EntityFinder setStartPosition(@NotNull Point startPosition) {
2021-01-08 23:47:31 +01:00
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;
}
public EntityFinder setConstantName(@NotNull String constantName) {
this.constantName = constantName;
return this;
}
public EntityFinder setConstantUuid(@NotNull UUID constantUuid) {
this.constantUuid = constantUuid;
return this;
}
public EntityFinder setName(@NotNull String name, @NotNull ToggleableType toggleableType) {
this.names.put(name, toggleableType.getValue());
return this;
}
public EntityFinder setUuid(@NotNull UUID uuid, @NotNull ToggleableType toggleableType) {
this.uuids.put(uuid, toggleableType.getValue());
2021-01-09 03:31:24 +01:00
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
}
/**
* Find a list of entities (could be empty) based on the conditions
*
2021-01-10 02:30:57 +01:00
* @param instance the instance to search from,
* null if the query can be executed using global data (all online players)
* @param self the source of the query, null if not any
2021-01-08 23:47:31 +01:00
* @return all entities validating the conditions, can be empty
*/
2021-07-24 04:22:50 +02:00
public @NotNull List<@NotNull Entity> find(@Nullable Instance instance, @Nullable Entity self) {
if (targetSelector == TargetSelector.MINESTOM_USERNAME) {
Check.notNull(constantName, "The player name should not be null when searching for it");
final Player player = MinecraftServer.getConnectionManager().getPlayer(constantName);
return player != null ? List.of(player) : List.of();
} else if (targetSelector == TargetSelector.MINESTOM_UUID) {
Check.notNull(constantUuid, "The UUID should not be null when searching for it");
final Entity entity = Entity.getEntity(constantUuid);
return entity != null ? List.of(entity) : List.of();
}
2021-07-24 04:22:50 +02:00
final Point pos = startPosition != null ? startPosition : (self != null ? self.getPosition() : Vec.ZERO);
2021-01-08 23:47:31 +01:00
2021-07-24 04:22:50 +02:00
List<Entity> result = findTarget(instance, targetSelector, pos, self);
2021-01-10 02:30:57 +01:00
// Fast exit if there is nothing to process
2021-01-08 23:47:31 +01:00
if (result.isEmpty())
return result;
// Distance argument
if (distance != null) {
final int minDistance = distance.getMinimum();
final int maxDistance = distance.getMaximum();
2021-07-24 04:31:35 +02:00
result = result.stream()
.filter(entity -> MathUtils.isBetween(entity.getDistance(pos), minDistance, maxDistance))
.toList();
2021-01-08 23:47:31 +01:00
}
// 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 -> {
2021-07-06 20:44:24 +02:00
final var entityPosition = entity.getPosition();
2021-01-09 03:31:24 +01:00
if (dx != null && !MathUtils.isBetweenUnordered(
2021-07-06 20:44:24 +02:00
entityPosition.x(),
2021-07-24 04:22:50 +02:00
pos.x(), 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-07-06 20:44:24 +02:00
entityPosition.y(),
2021-07-24 04:22:50 +02:00
pos.y(), 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-07-06 20:44:24 +02:00
entityPosition.z(),
2021-07-24 04:22:50 +02:00
pos.z(), dz))
2021-01-08 23:47:31 +01:00
return false;
return true;
}).toList();
2021-01-08 23:47:31 +01:00
}
// Entity type
2021-01-09 00:59:03 +01:00
if (!entityTypes.isEmpty()) {
2021-07-24 04:31:35 +02:00
result = result.stream()
.filter(entity -> filterToggleableMap(entity.getEntityType(), entityTypes))
.toList();
2021-01-08 23:47:31 +01:00
}
// GameMode
2021-01-09 00:59:03 +01:00
if (!gameModes.isEmpty()) {
2021-07-24 04:31:35 +02:00
result = result.stream()
.filter(Player.class::isInstance)
.filter(entity -> filterToggleableMap(((Player) entity).getGameMode(), gameModes))
.toList();
2021-01-08 23:47:31 +01:00
}
// Level
if (level != null) {
final int minLevel = level.getMinimum();
final int maxLevel = level.getMaximum();
2021-07-24 04:31:35 +02:00
result = result.stream()
.filter(Player.class::isInstance)
.filter(entity -> MathUtils.isBetween(((Player) entity).getLevel(), minLevel, maxLevel))
.toList();
2021-01-08 23:47:31 +01:00
}
2021-01-09 03:31:24 +01:00
// Name
if (!names.isEmpty()) {
2021-07-24 04:31:35 +02:00
result = result.stream()
.filter(Player.class::isInstance)
.filter(entity -> filterToggleableMap(((Player) entity).getUsername(), names))
.toList();
2021-01-09 03:31:24 +01:00
}
// UUID
if (!uuids.isEmpty()) {
2021-07-24 04:31:35 +02:00
result = result.stream()
.filter(entity -> filterToggleableMap(entity.getUuid(), uuids))
.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, RANDOM ->
// RANDOM is handled below
1;
case FURTHEST -> pos.distance(ent1.getPosition()) >
pos.distance(ent2.getPosition()) ?
1 : 0;
case NEAREST -> pos.distance(ent1.getPosition()) <
pos.distance(ent2.getPosition()) ?
1 : 0;
2021-01-08 23:47:31 +01:00
})
2021-01-09 03:31:24 +01:00
.limit(limit != null ? limit : Integer.MAX_VALUE)
.toList();
2021-01-08 23:47:31 +01:00
if (entitySort == EntitySort.RANDOM) {
Collections.shuffle(result);
}
}
return result;
}
2021-07-24 04:22:50 +02:00
public @NotNull List<@NotNull Entity> find(@NotNull CommandSender sender) {
return sender instanceof Player player ?
find(player.getInstance(), player) : find(null, null);
}
/**
* Shortcut of {@link #find(Instance, Entity)} to retrieve the first
* player element in the list.
*
* @return the first player returned by {@link #find(Instance, Entity)}
* @see #find(Instance, Entity)
*/
2021-07-24 04:22:50 +02:00
public @Nullable Player findFirstPlayer(@Nullable Instance instance, @Nullable Entity self) {
final List<Entity> entities = find(instance, self);
for (Entity entity : entities) {
if (entity instanceof Player player) {
return player;
}
}
return null;
}
2021-07-24 04:22:50 +02:00
public @Nullable Player findFirstPlayer(@NotNull CommandSender sender) {
return sender instanceof Player player ?
findFirstPlayer(player.getInstance(), player) :
findFirstPlayer(null, null);
}
2021-07-24 04:22:50 +02:00
public @Nullable Entity findFirstEntity(@Nullable Instance instance, @Nullable Entity self) {
final List<Entity> entities = find(instance, self);
return entities.isEmpty() ? null : entities.get(0);
}
2021-07-24 04:22:50 +02:00
public @Nullable Entity findFirstEntity(@NotNull CommandSender sender) {
return sender instanceof Player player ?
findFirstEntity(player.getInstance(), player) : findFirstEntity(null, null);
}
2021-01-08 23:47:31 +01:00
public enum TargetSelector {
NEAREST_PLAYER, RANDOM_PLAYER, ALL_PLAYERS, ALL_ENTITIES, SELF, MINESTOM_USERNAME, MINESTOM_UUID
}
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> {
}
2021-07-24 04:22:50 +02:00
private static @NotNull List<@NotNull Entity> findTarget(@Nullable Instance instance,
@NotNull TargetSelector targetSelector,
@NotNull Point startPosition, @Nullable Entity self) {
2021-07-24 04:39:57 +02:00
final var players = instance != null ?
instance.getPlayers() : MinecraftServer.getConnectionManager().getOnlinePlayers();
2021-01-08 23:47:31 +01:00
if (targetSelector == TargetSelector.NEAREST_PLAYER) {
2021-07-24 04:39:57 +02:00
return players.stream()
2021-07-24 04:22:50 +02:00
.min(Comparator.comparingDouble(p -> p.getPosition().distance(startPosition)))
.<List<Entity>>map(Collections::singletonList).orElse(List.of());
2021-01-08 23:47:31 +01:00
} else if (targetSelector == TargetSelector.RANDOM_PLAYER) {
2021-07-24 04:22:50 +02:00
final int index = ThreadLocalRandom.current().nextInt(players.size());
final Player player = players.stream().skip(index).findFirst().orElseThrow();
return List.of(player);
2021-01-08 23:47:31 +01:00
} else if (targetSelector == TargetSelector.ALL_PLAYERS) {
2021-07-24 04:39:57 +02:00
return List.copyOf(players);
2021-01-08 23:47:31 +01:00
} else if (targetSelector == TargetSelector.ALL_ENTITIES) {
if (instance != null) {
2021-07-24 04:22:50 +02:00
return List.copyOf(instance.getEntities());
}
// Get entities from every instance
var instances = MinecraftServer.getInstanceManager().getInstances();
List<Entity> entities = new ArrayList<>();
for (Instance inst : instances) {
entities.addAll(inst.getEntities());
}
2021-07-24 04:22:50 +02:00
return entities;
2021-01-08 23:47:31 +01:00
} else if (targetSelector == TargetSelector.SELF) {
return self != null ? List.of(self) : List.of();
2021-01-08 23:47:31 +01:00
}
throw new IllegalStateException("Weird thing happened: " + targetSelector);
2021-01-08 23:47:31 +01:00
}
2021-01-09 03:31:24 +01:00
private static <T> boolean filterToggleableMap(@NotNull T value, @NotNull ToggleableMap<T> map) {
for (var entry : Object2BooleanMaps.fastIterable(map)) {
2021-07-24 04:22:50 +02:00
if (entry.getBooleanValue() != Objects.equals(value, entry.getKey())) {
return false;
}
}
return true;
2021-01-09 03:31:24 +01:00
}
2020-07-11 14:16:36 +02:00
}