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

325 lines
12 KiB
Java
Raw Normal View History

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;
2021-01-10 02:30:57 +01:00
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.CommandSyntax;
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;
// 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
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
}
/**
* 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-01-08 23:47:31 +01:00
@NotNull
2021-01-10 02:30:57 +01:00
public List<Entity> find(@Nullable Instance instance, @Nullable Entity self) {
2021-01-08 23:47:31 +01:00
List<Entity> result = findTarget(instance, targetSelector, startPosition, 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();
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;
}
@NotNull
public List<Entity> find(@NotNull CommandSender sender) {
if (sender.isPlayer()) {
Player player = sender.asPlayer();
return find(player.getInstance(), player);
} else {
return 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)
*/
@Nullable
public Player findFirstPlayer(@Nullable Instance instance, @Nullable Entity self) {
List<Entity> entities = find(instance, self);
for (Entity entity : entities) {
if (entity instanceof Player) {
return (Player) entity;
}
}
return null;
}
2021-01-08 23:47:31 +01:00
public enum TargetSelector {
NEAREST_PLAYER, RANDOM_PLAYER, ALL_PLAYERS, ALL_ENTITIES, SELF
}
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
2021-01-10 02:30:57 +01:00
private static List<Entity> findTarget(@Nullable Instance instance, @NotNull TargetSelector targetSelector,
2021-01-08 23:47:31 +01:00
@NotNull Position startPosition, @Nullable Entity self) {
if (targetSelector == TargetSelector.NEAREST_PLAYER) {
Entity entity = null;
double closestDistance = Double.MAX_VALUE;
2021-01-08 23:47:31 +01:00
2021-01-10 02:30:57 +01:00
Collection<Player> instancePlayers = instance != null ?
instance.getPlayers() : MinecraftServer.getConnectionManager().getOnlinePlayers();
2021-01-08 23:47:31 +01:00
for (Player player : instancePlayers) {
final double distance = player.getPosition().getDistance(startPosition);
2021-01-08 23:47:31 +01:00
if (distance < closestDistance) {
entity = player;
closestDistance = distance;
}
}
2021-01-15 19:39:12 +01:00
return Collections.singletonList(entity);
2021-01-08 23:47:31 +01:00
} else if (targetSelector == TargetSelector.RANDOM_PLAYER) {
2021-01-10 02:30:57 +01:00
Collection<Player> instancePlayers = instance != null ?
instance.getPlayers() : MinecraftServer.getConnectionManager().getOnlinePlayers();
2021-01-08 23:47:31 +01:00
final int index = ThreadLocalRandom.current().nextInt(instancePlayers.size());
final Player player = instancePlayers.stream().skip(index).findFirst().orElseThrow();
2021-01-15 19:39:12 +01:00
return Collections.singletonList(player);
2021-01-08 23:47:31 +01:00
} else if (targetSelector == TargetSelector.ALL_PLAYERS) {
2021-01-10 02:30:57 +01:00
return new ArrayList<>(instance != null ?
instance.getPlayers() : MinecraftServer.getConnectionManager().getOnlinePlayers());
2021-01-08 23:47:31 +01:00
} else if (targetSelector == TargetSelector.ALL_ENTITIES) {
return new ArrayList<>(instance.getEntities());
} else if (targetSelector == TargetSelector.SELF) {
2021-01-15 19:39:12 +01:00
return Collections.singletonList(self);
2021-01-08 23:47:31 +01:00
}
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
}