Skip to content

Instantly share code, notes, and snippets.

@Citymonstret
Created January 17, 2019 16:41
Show Gist options
  • Select an option

  • Save Citymonstret/97d55a7658fdb07e19bf8e0fdb7080ff to your computer and use it in GitHub Desktop.

Select an option

Save Citymonstret/97d55a7658fdb07e19bf8e0fdb7080ff to your computer and use it in GitHub Desktop.
Minecraft command selector utilities
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;
}
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);
}
}
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;
}
}
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();
}
}
}
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