WIP EntityFinder

This commit is contained in:
themode 2021-01-08 23:47:31 +01:00
parent 8fef648221
commit ef1afb9cd9
3 changed files with 226 additions and 33 deletions

View File

@ -388,9 +388,9 @@ public final class CommandManager {
nodes.add(literalNode);
// Contains the arguments of the already-parsed syntaxes
List<Argument[]> syntaxesArguments = new ArrayList<>();
List<Argument<?>[]> syntaxesArguments = new ArrayList<>();
// Contains the nodes of an argument
Map<Argument, List<DeclareCommandsPacket.Node>> storedArgumentsNodes = new HashMap<>();
Map<Argument<?>, List<DeclareCommandsPacket.Node>> storedArgumentsNodes = new HashMap<>();
for (CommandSyntax syntax : syntaxes) {
final CommandCondition commandCondition = syntax.getCommandCondition();
@ -406,16 +406,17 @@ public final class CommandManager {
// Represent the children of the last node
IntList argChildren = null;
final Argument[] arguments = syntax.getArguments();
final Argument<?>[] arguments = syntax.getArguments();
for (int i = 0; i < arguments.length; i++) {
final Argument argument = arguments[i];
final Argument<?> argument = arguments[i];
final boolean isFirst = i == 0;
final boolean isLast = i == arguments.length - 1;
// Find shared part
boolean foundSharedPart = false;
for (Argument[] parsedArguments : syntaxesArguments) {
for (Argument<?>[] parsedArguments : syntaxesArguments) {
if (ArrayUtils.sameStart(arguments, parsedArguments, i + 1)) {
final Argument sharedArgument = parsedArguments[i];
final Argument<?> sharedArgument = parsedArguments[i];
argChildren = new IntArrayList();
lastNodes = storedArgumentsNodes.get(sharedArgument);
@ -442,9 +443,10 @@ public final class CommandManager {
if (lastNodes != null) {
final int[] children = ArrayUtils.toArray(argChildren);
lastNodes.forEach(n -> n.children = n.children == null ?
children :
ArrayUtils.concatenateIntArrays(n.children, children));
lastNodes.forEach(n ->
n.children = n.children == null ?
children :
ArrayUtils.concatenateIntArrays(n.children, children));
}
nodes.add(node);

View File

@ -59,6 +59,14 @@ public final class MathUtils {
return number >= min && number <= max;
}
public static boolean isBetweenUnordered(float number, float compare1, float compare2) {
if (compare1 > compare2) {
return isBetween(number, compare2, compare1);
} else {
return isBetween(number, compare1, compare2);
}
}
public static int clamp(int value, int min, int max) {
if (value < min) {
return min;

View File

@ -1,11 +1,20 @@
package net.minestom.server.utils.entity;
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
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;
import net.minestom.server.utils.math.IntRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
// TODO
@ -15,49 +24,223 @@ import java.util.List;
*/
public class EntityFinder {
// Commands option
private boolean onlySingleEntity;
private boolean onlyPlayers;
private TargetSelector targetSelector;
// Simple float
private float x, y, z;
private float dx, dy, dz;
private EntitySort entitySort = EntitySort.ARBITRARY;
// Range
// Position
private Position startPosition = new Position();
private OptionalDouble dx, dy, dz;
private IntRange distance;
private IntRange level;
// By traits
private int limit;
private EntitySort entitySort;
private EntityType entityType;
private OptionalInt limit;
private ToggleableMap<EntityType> entityTypes;
public boolean isOnlySingleEntity() {
return onlySingleEntity;
// Players specific
private ToggleableMap<GameMode> gameModes;
private IntRange level;
public EntityFinder setTargetSelector(@NotNull TargetSelector targetSelector) {
this.targetSelector = targetSelector;
return this;
}
public void setOnlySingleEntity(boolean onlySingleEntity) {
this.onlySingleEntity = onlySingleEntity;
public EntityFinder setEntitySort(@NotNull EntitySort entitySort) {
this.entitySort = entitySort;
return this;
}
public boolean isOnlyPlayers() {
return onlyPlayers;
public EntityFinder setStartPosition(@NotNull Position startPosition) {
this.startPosition = startPosition;
return this;
}
public void setOnlyPlayers(boolean onlyPlayers) {
this.onlyPlayers = onlyPlayers;
public EntityFinder setDistance(@NotNull IntRange distance) {
this.distance = distance;
return this;
}
public EntityFinder setLimit(int limit) {
this.limit = OptionalInt.of(limit);
return this;
}
public EntityFinder setLevel(@NotNull IntRange level) {
this.level = level;
return this;
}
public EntityFinder setDifference(double dx, double dy, double dz) {
this.dx = OptionalDouble.of(dx);
this.dy = OptionalDouble.of(dy);
this.dz = OptionalDouble.of(dz);
return this;
}
/**
* Find a list of entities (could be empty) based on the conditions
*
* @return all entities validating the conditions
* @return all entities validating the conditions, can be empty
*/
public List<Entity> find() {
return new ArrayList<>();
@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
if (dx.isPresent() || dy.isPresent() || dz.isPresent()) {
result = result.stream().filter(entity -> {
final Position entityPosition = entity.getPosition();
if (dx.isPresent() && !MathUtils.isBetweenUnordered(
entityPosition.getX(),
startPosition.getX(), (float) dx.getAsDouble()))
return false;
if (dy.isPresent() && !MathUtils.isBetweenUnordered(
entityPosition.getY(),
startPosition.getY(), (float) dy.getAsDouble()))
return false;
if (dz.isPresent() && !MathUtils.isBetweenUnordered(
entityPosition.getZ(),
startPosition.getZ(), (float) dz.getAsDouble()))
return false;
return true;
}).collect(Collectors.toList());
}
// Entity type
if (entityTypes != null && !entityTypes.isEmpty()) {
final EntityType requirement = entityTypes.requirement;
result = result.stream().filter(entity -> {
final EntityType entityType = entity.getEntityType();
// true if the entity type has not been mentioned or if is accepted
return (!entityTypes.containsKey(entityType) && requirement == null) ||
Objects.equals(requirement, entityType) ||
entityTypes.getBoolean(entityType);
}).collect(Collectors.toList());
}
// GameMode
if (gameModes != null && !gameModes.isEmpty()) {
final GameMode requirement = gameModes.requirement;
result = result.stream().filter(entity -> {
if (!(entity instanceof Player))
return false;
final GameMode gameMode = ((Player) entity).getGameMode();
// true if the entity type has not been mentioned or if is accepted
return (!gameModes.containsKey(gameMode) && requirement == null) ||
Objects.equals(requirement, gameMode) ||
gameModes.getBoolean(gameMode);
}).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());
}
// Sort & limit
if (entitySort != EntitySort.ARBITRARY || limit.isPresent()) {
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;
})
.limit(limit.isPresent() ? limit.getAsInt() : Integer.MAX_VALUE)
.collect(Collectors.toList());
if (entitySort == EntitySort.RANDOM) {
Collections.shuffle(result);
}
}
return result;
}
public enum TargetSelector {
NEAREST_PLAYER, RANDOM_PLAYER, ALL_PLAYERS, ALL_ENTITIES, SELF
}
public enum EntitySort {
ARBITRARY, FURTHEST, NEAREST, RANDOM
}
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");
}
}