org.lanternpowered.server.text.selector.SelectorResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.lanternpowered.server.text.selector.SelectorResolver.java

Source

/*
 * This file is part of LanternServer, licensed under the MIT License (MIT).
 *
 * Copyright (c) LanternPowered <https://www.lanternpowered.org>
 * Copyright (c) SpongePowered <https://www.spongepowered.org>
 * Copyright (c) contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the Software), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.lanternpowered.server.text.selector;

import static com.google.common.base.Preconditions.checkNotNull;

import com.flowpowered.math.vector.Vector3d;
import com.google.common.collect.ImmutableSet;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.data.manipulator.mutable.DisplayNameData;
import org.spongepowered.api.data.manipulator.mutable.entity.ExperienceHolderData;
import org.spongepowered.api.data.manipulator.mutable.entity.GameModeData;
import org.spongepowered.api.entity.Entity;
import org.spongepowered.api.entity.EntityType;
import org.spongepowered.api.entity.EntityTypes;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import org.spongepowered.api.scoreboard.Team;
import org.spongepowered.api.scoreboard.TeamMember;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.selector.Argument;
import org.spongepowered.api.text.selector.Argument.Invertible;
import org.spongepowered.api.text.selector.ArgumentHolder;
import org.spongepowered.api.text.selector.ArgumentType;
import org.spongepowered.api.text.selector.ArgumentTypes;
import org.spongepowered.api.text.selector.Selector;
import org.spongepowered.api.text.selector.SelectorType;
import org.spongepowered.api.text.selector.SelectorTypes;
import org.spongepowered.api.util.Functional;
import org.spongepowered.api.world.Locatable;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import org.spongepowered.api.world.extent.Extent;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

/**
 * A resolver that acts like Vanilla Minecraft in many regards.
 */
public class SelectorResolver {

    private static final Function<CommandSource, String> GET_NAME = CommandSource::getName;
    private static final Vector3d ORIGIN = new Vector3d(0, 0, 0);
    private static final Set<ArgumentType<?>> LOCATION_BASED_ARGUMENTS;
    private static final Function<Number, Double> TO_DOUBLE = Number::doubleValue;
    private static final Collection<SelectorType> INFINITE_TYPES = ImmutableSet.of(SelectorTypes.ALL_ENTITIES,
            SelectorTypes.ALL_PLAYERS);

    static {
        ImmutableSet.Builder<ArgumentType<?>> builder = ImmutableSet.builder();
        builder.addAll(ArgumentTypes.POSITION.getTypes());
        builder.addAll(ArgumentTypes.DIMENSION.getTypes());
        builder.addAll(ArgumentTypes.RADIUS.getTypes());
        // Left commented because Vanilla doesn't include it (see field_179666_d)
        // builder.addAll(ArgumentTypes.ROTATION.getTypes());
        LOCATION_BASED_ARGUMENTS = builder.build();
    }

    private static Extent extentFromSource(CommandSource origin) {
        if (origin instanceof Locatable) {
            return ((Locatable) origin).getWorld();
        }
        return null;
    }

    private static Vector3d positionFromSource(CommandSource origin) {
        if (origin instanceof Locatable) {
            return ((Locatable) origin).getLocation().getPosition();
        }
        return null;
    }

    private static <I, R> Predicate<I> requireTypePredicate(Class<I> inputType, final Class<R> requiredType) {
        return requiredType::isInstance;
    }

    private static <E> Collection<E> asSet(Optional<E> opt) {
        if (opt.isPresent()) {
            return Collections.singleton(opt.get());
        }
        return Collections.emptySet();
    }

    private final Collection<Extent> extents;
    private final Vector3d position;
    private final Optional<CommandSource> original;
    private final Selector selector;
    private final Predicate<Entity> selectorFilter;
    private final boolean alwaysUsePosition;

    public SelectorResolver(Collection<? extends Extent> extents, Selector selector, boolean force) {
        this(extents, null, null, selector, force);
    }

    public SelectorResolver(Location<World> location, Selector selector, boolean force) {
        this(ImmutableSet.of(location.getExtent()), location.getPosition(), null, selector, force);
    }

    public SelectorResolver(CommandSource origin, Selector selector, boolean force) {
        this(asSet(Optional.ofNullable(extentFromSource(origin))), positionFromSource(origin), origin, selector,
                force);
    }

    private SelectorResolver(Collection<? extends Extent> extents, @Nullable Vector3d position,
            @Nullable CommandSource original, Selector selector, boolean force) {
        this.extents = ImmutableSet.copyOf(extents);
        this.position = position == null ? ORIGIN : position;
        this.original = Optional.ofNullable(original);
        this.selector = checkNotNull(selector);
        this.selectorFilter = makeFilter();
        this.alwaysUsePosition = force;
    }

    private Predicate<Entity> makeFilter() {
        // for easier reading
        final Selector sel = this.selector;
        final Vector3d position = getPositionOrDefault(this.position, ArgumentTypes.POSITION);
        final List<Predicate<Entity>> filters = new ArrayList<>();
        addTypeFilters(filters);
        addDimensionFilters(position, filters);
        addRadiusFilters(position, filters);
        addLevelFilters(filters);
        addGamemodeFilters(filters);
        addNameFilters(filters);
        addRotationFilters(filters);
        addTeamFilters(filters);
        addScoreFilters(filters);
        SelectorType selectorType = sel.getType();
        final Optional<Argument.Invertible<EntityType>> type = sel.getArgument(ArgumentTypes.ENTITY_TYPE);
        // isn't an ALL_ENTITIES selector or it is a RANDOM selector for only players
        final boolean isPlayerOnlySelector = selectorType == SelectorTypes.ALL_PLAYERS
                || selectorType == SelectorTypes.NEAREST_PLAYER
                || (selectorType == SelectorTypes.RANDOM && type.isPresent() && !type.get().isInverted()
                        && type.get().getValue() != EntityTypes.PLAYER);
        if (isPlayerOnlySelector) {
            // insert at the start so it applies first
            filters.add(0, requireTypePredicate(Entity.class, Player.class));
        }
        return Functional.predicateAnd(filters);
    }

    private void addDimensionFilters(final Vector3d position, List<Predicate<Entity>> filters) {
        Selector sel = this.selector;
        Vector3d boxDimensions = getPositionOrDefault(ORIGIN, ArgumentTypes.DIMENSION);
        Vector3d det2 = position.add(boxDimensions);
        final Vector3d boxMin = position.min(det2);
        final Vector3d boxMax = position.max(det2);
        if (sel.has(ArgumentTypes.DIMENSION.x())) {
            filters.add(input -> {
                Vector3d pos = input.getLocation().getPosition();
                return pos.getX() >= boxMin.getX() && pos.getX() <= boxMax.getX();
            });
        }
        if (sel.has(ArgumentTypes.DIMENSION.y())) {
            filters.add(input -> {
                Vector3d pos = input.getLocation().getPosition();
                return pos.getY() >= boxMin.getY() && pos.getY() <= boxMax.getY();
            });
        }
        if (sel.has(ArgumentTypes.DIMENSION.z())) {
            filters.add(input -> {
                Vector3d pos = input.getLocation().getPosition();
                return pos.getZ() >= boxMin.getZ() && pos.getZ() <= boxMax.getZ();
            });
        }
    }

    private void addGamemodeFilters(List<Predicate<Entity>> filters) {
        Selector sel = this.selector;
        Optional<GameMode> gamemode = sel.get(ArgumentTypes.GAME_MODE);
        // If the game mode is NOT_SET, that means accept any
        if (gamemode.isPresent() && gamemode.get() != GameModes.NOT_SET) {
            final GameMode actualMode = gamemode.get();
            filters.add(input -> {
                Optional<GameModeData> mode = input.get(GameModeData.class);
                return mode.isPresent() && mode.get() == actualMode;
            });
        }
    }

    private void addLevelFilters(List<Predicate<Entity>> filters) {
        Selector sel = this.selector;
        Optional<Integer> levelMin = sel.get(ArgumentTypes.LEVEL.minimum());
        Optional<Integer> levelMax = sel.get(ArgumentTypes.LEVEL.maximum());
        if (levelMin.isPresent()) {
            final int actualMin = levelMin.get();
            filters.add(input -> {
                Optional<ExperienceHolderData> xp = input.get(ExperienceHolderData.class);
                return xp.isPresent() && xp.get().level().get() >= actualMin;
            });
        }
        if (levelMax.isPresent()) {
            final int actualMax = levelMax.get();
            filters.add(input -> {
                Optional<ExperienceHolderData> xp = input.get(ExperienceHolderData.class);
                return xp.isPresent() && xp.get().level().get() <= actualMax;
            });
        }
    }

    private void addNameFilters(List<Predicate<Entity>> filters) {
        Selector sel = this.selector;
        Optional<Argument.Invertible<String>> nameOpt = sel.getArgument(ArgumentTypes.NAME);
        if (nameOpt.isPresent()) {
            final String name = nameOpt.get().getValue();
            final boolean inverted = nameOpt.get().isInverted();
            filters.add(input -> {
                Optional<DisplayNameData> dispName = input.get(DisplayNameData.class);
                return inverted
                        ^ (dispName.isPresent() && name.equals(dispName.get().displayName().get().toPlain()));
            });
        }
    }

    private void addRadiusFilters(final Vector3d position, List<Predicate<Entity>> filters) {
        final Selector sel = this.selector;
        Optional<Integer> radiusMin = sel.get(ArgumentTypes.RADIUS.minimum());
        Optional<Integer> radiusMax = sel.get(ArgumentTypes.RADIUS.maximum());
        if (radiusMin.isPresent()) {
            int radMin = radiusMin.get();
            int radMinSquared = radMin * radMin;
            filters.add(input -> input.getLocation().getPosition().distanceSquared(position) >= radMinSquared);
        }
        if (radiusMax.isPresent()) {
            int radMax = radiusMax.get();
            int radMaxSquared = radMax * radMax;
            filters.add(input -> input.getLocation().getPosition().distanceSquared(position) <= radMaxSquared);
        }
    }

    private void addRotationFilters(List<Predicate<Entity>> filters) {
        Selector sel = this.selector;
        // If the Z's are uncommented, don't forget to implement them
        // Optional<Double> rotMinZ = sel.get(ArgumentTypes.ROTATION.minimum().z());
        // Optional<Double> rotMaxZ = sel.get(ArgumentTypes.ROTATION.maximum().z());
        Optional<Double> rotMinX = sel.get(ArgumentTypes.ROTATION.minimum().x());
        if (rotMinX.isPresent()) {
            double rmx = rotMinX.get();
            filters.add(input -> input.getRotation().getX() >= rmx);
        }
        Optional<Double> rotMinY = sel.get(ArgumentTypes.ROTATION.minimum().y());
        if (rotMinY.isPresent()) {
            double rmy = rotMinY.get();
            filters.add(input -> input.getRotation().getY() >= rmy);
        }
        Optional<Double> rotMaxX = sel.get(ArgumentTypes.ROTATION.maximum().x());
        if (rotMaxX.isPresent()) {
            double rx = rotMaxX.get();
            filters.add(input -> input.getRotation().getX() <= rx);
        }
        Optional<Double> rotMaxY = sel.get(ArgumentTypes.ROTATION.maximum().y());
        if (rotMaxY.isPresent()) {
            double ry = rotMaxY.get();
            filters.add(input -> input.getRotation().getY() <= ry);
        }
    }

    private void addScoreFilters(List<Predicate<Entity>> filters) {
        Selector sel = this.selector;
        sel.getArguments();
    }

    private void addTeamFilters(List<Predicate<Entity>> filters) {
        Selector sel = this.selector;
        Optional<Invertible<String>> teamOpt = sel.getArgument(ArgumentTypes.TEAM);
        if (teamOpt.isPresent()) {
            Invertible<String> teamArg = teamOpt.get();
            final boolean inverted = teamArg.isInverted();
            final Collection<Team> teams = Sponge.getGame().getServer().getServerScoreboard().get().getTeams();
            filters.add(new Predicate<Entity>() {

                @Override
                public boolean test(Entity input) {
                    if (input instanceof TeamMember) {
                        return inverted
                                ^ collectMembers(teams).contains(((TeamMember) input).getTeamRepresentation());
                    }
                    return false;
                }

                private Collection<Text> collectMembers(Collection<Team> teams) {
                    ImmutableSet.Builder<Text> users = ImmutableSet.builder();
                    for (Team t : teams) {
                        users.addAll(t.getMembers());
                    }
                    return users.build();
                }

            });
        }
    }

    private void addTypeFilters(List<Predicate<Entity>> filters) {
        Selector sel = this.selector;
        Optional<Argument.Invertible<EntityType>> typeOpt = sel.getArgument(ArgumentTypes.ENTITY_TYPE);
        if (typeOpt.isPresent()) {
            Argument.Invertible<EntityType> typeArg = typeOpt.get();
            boolean inverted = typeArg.isInverted();
            EntityType type = typeArg.getValue();
            filters.add(input -> inverted ^ input.getType() == type);
        }
    }

    private Vector3d getPositionOrDefault(Vector3d pos, ArgumentHolder.Vector3<?, ? extends Number> vecTypes) {
        Optional<Double> x = this.selector.get(vecTypes.x()).map(TO_DOUBLE);
        Optional<Double> y = this.selector.get(vecTypes.y()).map(TO_DOUBLE);
        Optional<Double> z = this.selector.get(vecTypes.z()).map(TO_DOUBLE);
        return new Vector3d(x.orElse(pos.getX()), y.orElse(pos.getY()), z.orElse(pos.getZ()));
    }

    public String getName() {
        return this.original.map(GET_NAME).orElse("SelectorResolver");
    }

    public Set<Entity> resolve() {
        SelectorType selectorType = this.selector.getType();
        int defaultCount = 1;
        if (INFINITE_TYPES.contains(selectorType)) {
            defaultCount = 0;
        }
        int maxToSelect = this.selector.get(ArgumentTypes.COUNT).orElse(defaultCount);
        Set<? extends Extent> extents = getExtentSet();
        int count = 0;
        ImmutableSet.Builder<Entity> entities = ImmutableSet.builder();
        for (Extent extent : extents) {
            Collection<Entity> allEntities = extent.getEntities();
            if (selectorType == SelectorTypes.RANDOM) {
                List<Entity> entityList = new ArrayList<Entity>(allEntities);
                Collections.shuffle(entityList);
                allEntities = entityList;
            }

            for (Entity e : allEntities) {
                if (!this.selectorFilter.test(e)) {
                    continue;
                }
                entities.add(e);
                count++;
                if (maxToSelect != 0 && count > maxToSelect) {
                    break;
                }
            }
        }
        return entities.build();
    }

    private Set<? extends Extent> getExtentSet() {
        if (!this.alwaysUsePosition
                && Collections.disjoint(getArgumentTypes(this.selector.getArguments()), LOCATION_BASED_ARGUMENTS)) {
            return ImmutableSet.copyOf(Sponge.getServer().getWorlds());
        }
        return ImmutableSet.copyOf(this.extents);
    }

    private Collection<ArgumentType<?>> getArgumentTypes(Collection<Argument<?>> arguments) {
        return arguments.stream().map(Argument::getType).collect(Collectors.toSet());
    }

}