From ef1afb9cd9e7709b25918e04a61479dfccc59e13 Mon Sep 17 00:00:00 2001 From: themode Date: Fri, 8 Jan 2021 23:47:31 +0100 Subject: [PATCH] WIP EntityFinder --- .../server/command/CommandManager.java | 20 +- .../net/minestom/server/utils/MathUtils.java | 8 + .../server/utils/entity/EntityFinder.java | 231 ++++++++++++++++-- 3 files changed, 226 insertions(+), 33 deletions(-) diff --git a/src/main/java/net/minestom/server/command/CommandManager.java b/src/main/java/net/minestom/server/command/CommandManager.java index 6a46051ac..0f0d4f6dd 100644 --- a/src/main/java/net/minestom/server/command/CommandManager.java +++ b/src/main/java/net/minestom/server/command/CommandManager.java @@ -388,9 +388,9 @@ public final class CommandManager { nodes.add(literalNode); // Contains the arguments of the already-parsed syntaxes - List syntaxesArguments = new ArrayList<>(); + List[]> syntaxesArguments = new ArrayList<>(); // Contains the nodes of an argument - Map> storedArgumentsNodes = new HashMap<>(); + Map, List> 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); diff --git a/src/main/java/net/minestom/server/utils/MathUtils.java b/src/main/java/net/minestom/server/utils/MathUtils.java index 41f39c15f..8fd237802 100644 --- a/src/main/java/net/minestom/server/utils/MathUtils.java +++ b/src/main/java/net/minestom/server/utils/MathUtils.java @@ -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; diff --git a/src/main/java/net/minestom/server/utils/entity/EntityFinder.java b/src/main/java/net/minestom/server/utils/entity/EntityFinder.java index 808c21bb2..f24f749d6 100644 --- a/src/main/java/net/minestom/server/utils/entity/EntityFinder.java +++ b/src/main/java/net/minestom/server/utils/entity/EntityFinder.java @@ -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 entityTypes; - public boolean isOnlySingleEntity() { - return onlySingleEntity; + // Players specific + private ToggleableMap 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 find() { - return new ArrayList<>(); + @NotNull + public List find(@NotNull Instance instance, @Nullable Entity self) { + List 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 extends Object2BooleanOpenHashMap { + + @Nullable + private T requirement; + + } + + @NotNull + private static List 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 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 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"); + } }