Created
January 17, 2019 16:41
-
-
Save Citymonstret/97d55a7658fdb07e19bf8e0fdb7080ff to your computer and use it in GitHub Desktop.
Minecraft command selector utilities
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.incendo.plotessentials.commands; | |
| import lombok.Getter; | |
| import lombok.RequiredArgsConstructor; | |
| import org.bukkit.entity.Entity; | |
| /** | |
| * Command selector target | |
| */ | |
| @RequiredArgsConstructor public class CommandTarget { | |
| @Getter private final CommandTargetType commandTargetType; | |
| @Getter private final Entity bukkitEntity; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.incendo.plotessentials.commands; | |
| import lombok.*; | |
| import javax.annotation.Nonnull; | |
| /** | |
| * Key value pair used to narrow down selectors | |
| */ | |
| @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @Getter @EqualsAndHashCode @ToString | |
| public class CommandTargetArgument { | |
| private final CommandTargetArgumentType key; | |
| private final String value; | |
| public static CommandTargetArgument valueOf(@Nonnull final String key, @Nonnull final String value) { | |
| final CommandTargetArgumentType type = CommandTargetArgumentType.fromString(key); | |
| if (type == null) { | |
| throw new IllegalArgumentException(String.format("Unknown argument type: %s", key)); | |
| } | |
| return new CommandTargetArgument(type, value); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.incendo.plotessentials.commands; | |
| import lombok.Getter; | |
| import lombok.RequiredArgsConstructor; | |
| import javax.annotation.Nonnull; | |
| import javax.annotation.Nullable; | |
| @RequiredArgsConstructor public enum CommandTargetArgumentType { | |
| TYPE("type"), | |
| COUNT("c"), | |
| GAMEMODE("m"), | |
| COORD_X("x"), | |
| COORD_Y("y"), | |
| COORD_Z("z"), | |
| DISTANCE_X("dx"), | |
| DISTANCE_Y("dy"), | |
| DISTANCE_Z("dz"); | |
| @Getter private final String key; | |
| @Override public String toString() { | |
| return this.key; | |
| } | |
| @Nullable public static CommandTargetArgumentType fromString(@Nonnull final String string) { | |
| for (final CommandTargetArgumentType targetType : values()) { | |
| if (targetType.getKey().equalsIgnoreCase(string)) { | |
| return targetType; | |
| } | |
| } | |
| return null; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.incendo.plotessentials.commands; | |
| import lombok.Getter; | |
| import lombok.RequiredArgsConstructor; | |
| import org.bukkit.Bukkit; | |
| import org.bukkit.GameMode; | |
| import org.bukkit.Location; | |
| import org.bukkit.World; | |
| import org.bukkit.entity.Entity; | |
| import org.bukkit.entity.EntityType; | |
| import org.bukkit.entity.Player; | |
| import org.bukkit.util.Vector; | |
| import javax.annotation.Nonnull; | |
| import java.util.*; | |
| import java.util.regex.Matcher; | |
| import java.util.regex.Pattern; | |
| import java.util.stream.Collectors; | |
| /** | |
| * Handler for command targets | |
| */ | |
| @RequiredArgsConstructor public class CommandTargetHandler { | |
| private static final Pattern SELECTOR_PATTERN = Pattern.compile("@(?<selector>[A-Za-z])(?<data>\\[(?<entries>[:A-Za-z0-9_\\-=,]+)])?"); | |
| private static final Pattern DATA_PATTERN = Pattern.compile("(?<entry>(?<key>[A-Za-z0-9_\\-]+)=(?<value>[~:A-Za-z0-9_\\-]+))"); | |
| /** | |
| * Maximum amount of selected targets | |
| */ | |
| @Getter private final int maxTargets; | |
| @Getter private final int maxDistance; | |
| private final Random random = new Random(); | |
| public Collection<CommandTarget> getApplicableTargets(@Nonnull final Entity executor, @Nonnull final String argument) { | |
| final Matcher matcher = SELECTOR_PATTERN.matcher(argument); | |
| if (!matcher.matches()) { | |
| throw new IllegalArgumentException(String.format("\"%s\" is not a valid selector", argument)); | |
| } | |
| final String selector = matcher.group("selector"); | |
| if (selector == null || selector.isEmpty()) { | |
| throw new IllegalArgumentException(String.format("Could not extract selector from \"%s\"", argument)); | |
| } | |
| final CommandTargetType type = CommandTargetType.fromString(selector); | |
| if (type == null) { | |
| throw new IllegalArgumentException(String.format("Unknown selector: %s", selector)); | |
| } | |
| final Map<CommandTargetArgumentType, CommandTargetArgument> arguments; | |
| if (matcher.group("data") != null) { | |
| arguments = new HashMap<>(); | |
| final String entries = matcher.group("entries"); | |
| final Matcher dataMatcher = DATA_PATTERN.matcher(entries); | |
| while (dataMatcher.find()) { | |
| final String key = dataMatcher.group("key"); | |
| final String value = dataMatcher.group("value"); | |
| final CommandTargetArgument targetArgument = CommandTargetArgument.valueOf(key, value); | |
| arguments.put(targetArgument.getKey(), targetArgument); | |
| } | |
| } else { | |
| arguments = Collections.emptyMap(); | |
| } | |
| final Location currentLocation = executor.getLocation(); | |
| Integer coordX = null, coordY = null, coordZ = null; | |
| Double dx = null, dy = null, dz = null; | |
| if (arguments.containsKey(CommandTargetArgumentType.COORD_X)) { | |
| String value = arguments.get(CommandTargetArgumentType.COORD_X).getValue(); | |
| int relativeValue = 0; | |
| if (value.startsWith("~")) { | |
| relativeValue = currentLocation.getBlockX(); | |
| } | |
| coordX = relativeValue + Integer.parseInt(value); | |
| } | |
| if (arguments.containsKey(CommandTargetArgumentType.COORD_Y)) { | |
| String value = arguments.get(CommandTargetArgumentType.COORD_X).getValue(); | |
| int relativeValue = 0; | |
| if (value.startsWith("~")) { | |
| relativeValue = currentLocation.getBlockY(); | |
| } | |
| coordY = relativeValue + Integer.parseInt(value); | |
| } | |
| if (arguments.containsKey(CommandTargetArgumentType.COORD_Z)) { | |
| String value = arguments.get(CommandTargetArgumentType.COORD_X).getValue(); | |
| int relativeValue = 0; | |
| if (value.startsWith("~")) { | |
| relativeValue = currentLocation.getBlockZ(); | |
| } | |
| coordZ = relativeValue + Integer.parseInt(value); | |
| } | |
| if (arguments.containsKey(CommandTargetArgumentType.DISTANCE_X)) { | |
| String value = arguments.get(CommandTargetArgumentType.COORD_X).getValue(); | |
| double relativeValue = 0; | |
| if (value.startsWith("~")) { | |
| relativeValue = currentLocation.getX(); | |
| } | |
| dx = relativeValue + Double.parseDouble(value); | |
| } | |
| if (arguments.containsKey(CommandTargetArgumentType.DISTANCE_Y)) { | |
| String value = arguments.get(CommandTargetArgumentType.COORD_X).getValue(); | |
| double relativeValue = 0; | |
| if (value.startsWith("~")) { | |
| relativeValue = currentLocation.getX(); | |
| } | |
| dy = relativeValue + Double.parseDouble(value); | |
| } | |
| if (arguments.containsKey(CommandTargetArgumentType.DISTANCE_Z)) { | |
| String value = arguments.get(CommandTargetArgumentType.COORD_X).getValue(); | |
| double relativeValue = 0; | |
| if (value.startsWith("~")) { | |
| relativeValue = currentLocation.getX(); | |
| } | |
| dz = relativeValue + Double.parseDouble(value); | |
| } | |
| final Location minLocation = new Location(executor.getWorld(), | |
| dx == null ? Integer.MIN_VALUE : currentLocation.getX() - dx, | |
| dy == null ? Integer.MIN_VALUE : currentLocation.getX() - dy, | |
| dz == null ? Integer.MIN_VALUE : currentLocation.getX() - dz); | |
| final Location maxLocation = new Location(executor.getWorld(), | |
| dx == null ? Integer.MIN_VALUE : currentLocation.getX() + dx, | |
| dy == null ? Integer.MIN_VALUE : currentLocation.getX() + dy, | |
| dz == null ? Integer.MIN_VALUE : currentLocation.getX() + dz); | |
| final Vector vectorMin = minLocation.toVector(), vectorMax = maxLocation.toVector(); | |
| if (currentLocation.distance(minLocation) > maxDistance || currentLocation.distance(maxLocation) > maxDistance) { | |
| throw new IllegalArgumentException(String.format("Maximum distance is %d", maxDistance)); | |
| } | |
| final GameMode requiredMode; | |
| if (arguments.containsKey(CommandTargetArgumentType.GAMEMODE)) { | |
| requiredMode = GameMode.valueOf(arguments.get(CommandTargetArgumentType.GAMEMODE).getValue().toUpperCase(Locale.ENGLISH)); | |
| } else { | |
| requiredMode = null; | |
| } | |
| switch (type) { | |
| case SELF: return Collections.singleton(new CommandTarget(type, executor)); | |
| case RANDOM_PLAYER: { | |
| final Player[] onlinePlayers = Bukkit.getOnlinePlayers().toArray(new Player[0]); | |
| final Collection<Player> tested = new HashSet<>(); | |
| Player currentPlayer; | |
| while (tested.size() < onlinePlayers.length) { | |
| while ((tested.contains((currentPlayer = onlinePlayers[random.nextInt(onlinePlayers.length)])))); | |
| // test player | |
| if (currentPlayer == null) { | |
| continue; | |
| } | |
| tested.add(currentPlayer); | |
| final Location currentPlayerLocation = currentPlayer.getLocation(); | |
| if (!currentLocation.toVector().isInAABB(vectorMin, vectorMax)) { | |
| continue; | |
| } | |
| if (coordX != null && currentPlayerLocation.getBlockX() != coordX) { | |
| continue; | |
| } | |
| if (coordY != null && currentPlayerLocation.getBlockY() != coordY) { | |
| continue; | |
| } | |
| if (coordZ != null && currentPlayerLocation.getBlockZ() != coordZ) { | |
| continue; | |
| } | |
| if (requiredMode != null && currentPlayer.getGameMode() != requiredMode) { | |
| continue; | |
| } | |
| return Collections.singleton(new CommandTarget(type, currentPlayer)); | |
| } | |
| // None matched | |
| return Collections.emptyList(); | |
| } | |
| case PLAYER: | |
| case PLAYERS: { | |
| final List<CommandTarget> targets = new ArrayList<>(); | |
| for (final Player currentPlayer : Bukkit.getOnlinePlayers()) { | |
| final Location currentPlayerLocation = currentPlayer.getLocation(); | |
| if (!currentLocation.toVector().isInAABB(vectorMin, vectorMax)) { | |
| continue; | |
| } | |
| if (coordX != null && currentPlayerLocation.getBlockX() != coordX) { | |
| continue; | |
| } | |
| if (coordY != null && currentPlayerLocation.getBlockY() != coordY) { | |
| continue; | |
| } | |
| if (coordZ != null && currentPlayerLocation.getBlockZ() != coordZ) { | |
| continue; | |
| } | |
| if (requiredMode != null && currentPlayer.getGameMode() != requiredMode) { | |
| continue; | |
| } | |
| targets.add(new CommandTarget(type, currentPlayer)); | |
| } | |
| // Sort by distance to player | |
| targets.sort((t1, t2) -> { | |
| final double d1 = currentLocation.distanceSquared(t1.getBukkitEntity().getLocation()); | |
| final double d2 = currentLocation.distanceSquared(t2.getBukkitEntity().getLocation()); | |
| return Double.compare(d1, d2); | |
| }); | |
| if (type == CommandTargetType.PLAYERS) { | |
| final int maxCount; | |
| if (arguments.containsKey(CommandTargetArgumentType.COUNT)) { | |
| maxCount = Math.min(this.maxTargets, Integer.parseInt(arguments.get(CommandTargetArgumentType.COUNT).getValue())); | |
| } else { | |
| maxCount = this.maxTargets; | |
| } | |
| if (targets.size() > maxCount) { | |
| return targets.subList(0, maxCount); | |
| } | |
| } else { | |
| return targets.subList(0, 1); | |
| } | |
| return targets; | |
| } | |
| case ENTITIES: { | |
| final World world = currentLocation.getWorld(); | |
| final EntityType requiredType; | |
| if (arguments.containsKey(CommandTargetArgumentType.TYPE)) { | |
| String testValue = arguments.get(CommandTargetArgumentType.TYPE).getValue().toLowerCase(Locale.ENGLISH); | |
| if (!testValue.startsWith("minecraft:")) { | |
| testValue = String.format("minecraft:%s", testValue); | |
| } | |
| requiredType = EntityType.fromName(testValue); | |
| } else { | |
| requiredType = null; | |
| } | |
| final Integer[] coords = new Integer[] { coordX, coordY, coordZ }; | |
| final Collection<Entity> entities; | |
| if (dx != null || dy != null || dz != null) { | |
| entities = world.getNearbyEntities(currentLocation, dx == null ? 1 : dx, dy == null ? 1 : dy, | |
| dz == null ? 1 : dz, entity -> { | |
| if (requiredType != null && requiredType != entity.getType()) { | |
| return false; | |
| } | |
| final Location currentEntityLocation = entity.getLocation(); | |
| if (coords[0] != null && currentEntityLocation.getBlockX() != coords[0]) { | |
| return false; | |
| } | |
| if (coords[1] != null && currentEntityLocation.getBlockY() != coords[1]) { | |
| return false; | |
| } | |
| if (coords[2] != null && currentEntityLocation.getBlockZ() != coords[2]) { | |
| return false; | |
| } | |
| return true; | |
| }); | |
| } else { | |
| entities = world.getEntities().stream().filter(entity -> { | |
| if (requiredType != null && requiredType != entity.getType()) { | |
| return false; | |
| } | |
| final Location currentEntityLocation = entity.getLocation(); | |
| if (coords[0] != null && currentEntityLocation.getBlockX() != coords[0]) { | |
| return false; | |
| } | |
| if (coords[1] != null && currentEntityLocation.getBlockY() != coords[1]) { | |
| return false; | |
| } | |
| if (coords[2] != null && currentEntityLocation.getBlockZ() != coords[2]) { | |
| return false; | |
| } | |
| return true; | |
| }).collect(Collectors.toList()); | |
| } | |
| final List<CommandTarget> targets = new ArrayList<>(entities.size()); | |
| targets.addAll(entities.stream().map(entity -> new CommandTarget(type, entity)).collect( | |
| Collectors.toList())); | |
| // Sort by distance to player | |
| targets.sort((t1, t2) -> { | |
| final double d1 = currentLocation.distanceSquared(t1.getBukkitEntity().getLocation()); | |
| final double d2 = currentLocation.distanceSquared(t2.getBukkitEntity().getLocation()); | |
| return Double.compare(d1, d2); | |
| }); | |
| final int maxCount; | |
| if (arguments.containsKey(CommandTargetArgumentType.COUNT)) { | |
| maxCount = Math.min(this.maxTargets, Integer.parseInt(arguments.get(CommandTargetArgumentType.COUNT).getValue())); | |
| } else { | |
| maxCount = this.maxTargets; | |
| } | |
| if (targets.size() > maxCount) { | |
| return targets.subList(0, maxCount); | |
| } | |
| return targets; | |
| } | |
| default: return Collections.emptyList(); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.incendo.plotessentials.commands; | |
| import lombok.Getter; | |
| import lombok.RequiredArgsConstructor; | |
| import javax.annotation.Nonnull; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Target selector types | |
| */ | |
| @RequiredArgsConstructor public enum CommandTargetType { | |
| PLAYER("p"), | |
| RANDOM_PLAYER("r"), | |
| PLAYERS("a"), | |
| ENTITIES("e"), | |
| SELF("s"); | |
| @Getter private final String selectorString; | |
| @Override public String toString() { | |
| return String.format("@%s", this.selectorString); | |
| } | |
| @Nullable public static CommandTargetType fromString(@Nonnull final String string) { | |
| for (final CommandTargetType targetType : values()) { | |
| if (targetType.getSelectorString().equalsIgnoreCase(string)) { | |
| return targetType; | |
| } | |
| } | |
| return null; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment