minecraft-src/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
2025-07-04 01:41:11 +03:00

650 lines
19 KiB
Java

package net.minecraft.commands.arguments.selector;
import com.google.common.primitives.Doubles;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.advancements.critereon.WrappedMinMaxBounds;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.arguments.selector.options.EntitySelectorOptions;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class EntitySelectorParser {
public static final char SYNTAX_SELECTOR_START = '@';
private static final char SYNTAX_OPTIONS_START = '[';
private static final char SYNTAX_OPTIONS_END = ']';
public static final char SYNTAX_OPTIONS_KEY_VALUE_SEPARATOR = '=';
private static final char SYNTAX_OPTIONS_SEPARATOR = ',';
public static final char SYNTAX_NOT = '!';
public static final char SYNTAX_TAG = '#';
private static final char SELECTOR_NEAREST_PLAYER = 'p';
private static final char SELECTOR_ALL_PLAYERS = 'a';
private static final char SELECTOR_RANDOM_PLAYERS = 'r';
private static final char SELECTOR_CURRENT_ENTITY = 's';
private static final char SELECTOR_ALL_ENTITIES = 'e';
private static final char SELECTOR_NEAREST_ENTITY = 'n';
public static final SimpleCommandExceptionType ERROR_INVALID_NAME_OR_UUID = new SimpleCommandExceptionType(Component.translatable("argument.entity.invalid"));
public static final DynamicCommandExceptionType ERROR_UNKNOWN_SELECTOR_TYPE = new DynamicCommandExceptionType(
object -> Component.translatableEscape("argument.entity.selector.unknown", object)
);
public static final SimpleCommandExceptionType ERROR_SELECTORS_NOT_ALLOWED = new SimpleCommandExceptionType(
Component.translatable("argument.entity.selector.not_allowed")
);
public static final SimpleCommandExceptionType ERROR_MISSING_SELECTOR_TYPE = new SimpleCommandExceptionType(
Component.translatable("argument.entity.selector.missing")
);
public static final SimpleCommandExceptionType ERROR_EXPECTED_END_OF_OPTIONS = new SimpleCommandExceptionType(
Component.translatable("argument.entity.options.unterminated")
);
public static final DynamicCommandExceptionType ERROR_EXPECTED_OPTION_VALUE = new DynamicCommandExceptionType(
object -> Component.translatableEscape("argument.entity.options.valueless", object)
);
public static final BiConsumer<Vec3, List<? extends Entity>> ORDER_NEAREST = (vec3, list) -> list.sort(
(entity, entity2) -> Doubles.compare(entity.distanceToSqr(vec3), entity2.distanceToSqr(vec3))
);
public static final BiConsumer<Vec3, List<? extends Entity>> ORDER_FURTHEST = (vec3, list) -> list.sort(
(entity, entity2) -> Doubles.compare(entity2.distanceToSqr(vec3), entity.distanceToSqr(vec3))
);
public static final BiConsumer<Vec3, List<? extends Entity>> ORDER_RANDOM = (vec3, list) -> Collections.shuffle(list);
public static final BiFunction<SuggestionsBuilder, Consumer<SuggestionsBuilder>, CompletableFuture<Suggestions>> SUGGEST_NOTHING = (suggestionsBuilder, consumer) -> suggestionsBuilder.buildFuture();
private final StringReader reader;
private final boolean allowSelectors;
private int maxResults;
private boolean includesEntities;
private boolean worldLimited;
private MinMaxBounds.Doubles distance = MinMaxBounds.Doubles.ANY;
private MinMaxBounds.Ints level = MinMaxBounds.Ints.ANY;
@Nullable
private Double x;
@Nullable
private Double y;
@Nullable
private Double z;
@Nullable
private Double deltaX;
@Nullable
private Double deltaY;
@Nullable
private Double deltaZ;
private WrappedMinMaxBounds rotX = WrappedMinMaxBounds.ANY;
private WrappedMinMaxBounds rotY = WrappedMinMaxBounds.ANY;
private final List<Predicate<Entity>> predicates = new ArrayList();
private BiConsumer<Vec3, List<? extends Entity>> order = EntitySelector.ORDER_ARBITRARY;
private boolean currentEntity;
@Nullable
private String playerName;
private int startPosition;
@Nullable
private UUID entityUUID;
private BiFunction<SuggestionsBuilder, Consumer<SuggestionsBuilder>, CompletableFuture<Suggestions>> suggestions = SUGGEST_NOTHING;
private boolean hasNameEquals;
private boolean hasNameNotEquals;
private boolean isLimited;
private boolean isSorted;
private boolean hasGamemodeEquals;
private boolean hasGamemodeNotEquals;
private boolean hasTeamEquals;
private boolean hasTeamNotEquals;
@Nullable
private EntityType<?> type;
private boolean typeInverse;
private boolean hasScores;
private boolean hasAdvancements;
private boolean usesSelectors;
public EntitySelectorParser(StringReader reader, boolean allowSelectors) {
this.reader = reader;
this.allowSelectors = allowSelectors;
}
public static <S> boolean allowSelectors(S suggestionProvider) {
return suggestionProvider instanceof SharedSuggestionProvider sharedSuggestionProvider && sharedSuggestionProvider.hasPermission(2);
}
public EntitySelector getSelector() {
AABB aABB;
if (this.deltaX == null && this.deltaY == null && this.deltaZ == null) {
if (this.distance.max().isPresent()) {
double d = (Double)this.distance.max().get();
aABB = new AABB(-d, -d, -d, d + 1.0, d + 1.0, d + 1.0);
} else {
aABB = null;
}
} else {
aABB = this.createAabb(this.deltaX == null ? 0.0 : this.deltaX, this.deltaY == null ? 0.0 : this.deltaY, this.deltaZ == null ? 0.0 : this.deltaZ);
}
Function<Vec3, Vec3> function;
if (this.x == null && this.y == null && this.z == null) {
function = vec3 -> vec3;
} else {
function = vec3 -> new Vec3(this.x == null ? vec3.x : this.x, this.y == null ? vec3.y : this.y, this.z == null ? vec3.z : this.z);
}
return new EntitySelector(
this.maxResults,
this.includesEntities,
this.worldLimited,
List.copyOf(this.predicates),
this.distance,
function,
aABB,
this.order,
this.currentEntity,
this.playerName,
this.entityUUID,
this.type,
this.usesSelectors
);
}
private AABB createAabb(double sizeX, double sizeY, double sizeZ) {
boolean bl = sizeX < 0.0;
boolean bl2 = sizeY < 0.0;
boolean bl3 = sizeZ < 0.0;
double d = bl ? sizeX : 0.0;
double e = bl2 ? sizeY : 0.0;
double f = bl3 ? sizeZ : 0.0;
double g = (bl ? 0.0 : sizeX) + 1.0;
double h = (bl2 ? 0.0 : sizeY) + 1.0;
double i = (bl3 ? 0.0 : sizeZ) + 1.0;
return new AABB(d, e, f, g, h, i);
}
private void finalizePredicates() {
if (this.rotX != WrappedMinMaxBounds.ANY) {
this.predicates.add(this.createRotationPredicate(this.rotX, Entity::getXRot));
}
if (this.rotY != WrappedMinMaxBounds.ANY) {
this.predicates.add(this.createRotationPredicate(this.rotY, Entity::getYRot));
}
if (!this.level.isAny()) {
this.predicates.add((Predicate)entity -> !(entity instanceof ServerPlayer) ? false : this.level.matches(((ServerPlayer)entity).experienceLevel));
}
}
private Predicate<Entity> createRotationPredicate(WrappedMinMaxBounds angleBounds, ToDoubleFunction<Entity> angleFunction) {
double d = Mth.wrapDegrees(angleBounds.min() == null ? 0.0F : angleBounds.min());
double e = Mth.wrapDegrees(angleBounds.max() == null ? 359.0F : angleBounds.max());
return entity -> {
double f = Mth.wrapDegrees(angleFunction.applyAsDouble(entity));
return d > e ? f >= d || f <= e : f >= d && f <= e;
};
}
protected void parseSelector() throws CommandSyntaxException {
this.usesSelectors = true;
this.suggestions = this::suggestSelector;
if (!this.reader.canRead()) {
throw ERROR_MISSING_SELECTOR_TYPE.createWithContext(this.reader);
} else {
int i = this.reader.getCursor();
char c = this.reader.read();
if (switch (c) {
case 'a' -> {
this.maxResults = Integer.MAX_VALUE;
this.includesEntities = false;
this.order = EntitySelector.ORDER_ARBITRARY;
this.limitToType(EntityType.PLAYER);
yield false;
}
default -> {
this.reader.setCursor(i);
throw ERROR_UNKNOWN_SELECTOR_TYPE.createWithContext(this.reader, "@" + c);
}
case 'e' -> {
this.maxResults = Integer.MAX_VALUE;
this.includesEntities = true;
this.order = EntitySelector.ORDER_ARBITRARY;
yield true;
}
case 'n' -> {
this.maxResults = 1;
this.includesEntities = true;
this.order = ORDER_NEAREST;
yield true;
}
case 'p' -> {
this.maxResults = 1;
this.includesEntities = false;
this.order = ORDER_NEAREST;
this.limitToType(EntityType.PLAYER);
yield false;
}
case 'r' -> {
this.maxResults = 1;
this.includesEntities = false;
this.order = ORDER_RANDOM;
this.limitToType(EntityType.PLAYER);
yield false;
}
case 's' -> {
this.maxResults = 1;
this.includesEntities = true;
this.currentEntity = true;
yield false;
}
}) {
this.predicates.add(Entity::isAlive);
}
this.suggestions = this::suggestOpenOptions;
if (this.reader.canRead() && this.reader.peek() == '[') {
this.reader.skip();
this.suggestions = this::suggestOptionsKeyOrClose;
this.parseOptions();
}
}
}
protected void parseNameOrUUID() throws CommandSyntaxException {
if (this.reader.canRead()) {
this.suggestions = this::suggestName;
}
int i = this.reader.getCursor();
String string = this.reader.readString();
try {
this.entityUUID = UUID.fromString(string);
this.includesEntities = true;
} catch (IllegalArgumentException var4) {
if (string.isEmpty() || string.length() > 16) {
this.reader.setCursor(i);
throw ERROR_INVALID_NAME_OR_UUID.createWithContext(this.reader);
}
this.includesEntities = false;
this.playerName = string;
}
this.maxResults = 1;
}
protected void parseOptions() throws CommandSyntaxException {
this.suggestions = this::suggestOptionsKey;
this.reader.skipWhitespace();
while (this.reader.canRead() && this.reader.peek() != ']') {
this.reader.skipWhitespace();
int i = this.reader.getCursor();
String string = this.reader.readString();
EntitySelectorOptions.Modifier modifier = EntitySelectorOptions.get(this, string, i);
this.reader.skipWhitespace();
if (!this.reader.canRead() || this.reader.peek() != '=') {
this.reader.setCursor(i);
throw ERROR_EXPECTED_OPTION_VALUE.createWithContext(this.reader, string);
}
this.reader.skip();
this.reader.skipWhitespace();
this.suggestions = SUGGEST_NOTHING;
modifier.handle(this);
this.reader.skipWhitespace();
this.suggestions = this::suggestOptionsNextOrClose;
if (this.reader.canRead()) {
if (this.reader.peek() != ',') {
if (this.reader.peek() != ']') {
throw ERROR_EXPECTED_END_OF_OPTIONS.createWithContext(this.reader);
}
break;
}
this.reader.skip();
this.suggestions = this::suggestOptionsKey;
}
}
if (this.reader.canRead()) {
this.reader.skip();
this.suggestions = SUGGEST_NOTHING;
} else {
throw ERROR_EXPECTED_END_OF_OPTIONS.createWithContext(this.reader);
}
}
public boolean shouldInvertValue() {
this.reader.skipWhitespace();
if (this.reader.canRead() && this.reader.peek() == '!') {
this.reader.skip();
this.reader.skipWhitespace();
return true;
} else {
return false;
}
}
public boolean isTag() {
this.reader.skipWhitespace();
if (this.reader.canRead() && this.reader.peek() == '#') {
this.reader.skip();
this.reader.skipWhitespace();
return true;
} else {
return false;
}
}
public StringReader getReader() {
return this.reader;
}
public void addPredicate(Predicate<Entity> predicate) {
this.predicates.add(predicate);
}
public void setWorldLimited() {
this.worldLimited = true;
}
public MinMaxBounds.Doubles getDistance() {
return this.distance;
}
public void setDistance(MinMaxBounds.Doubles distance) {
this.distance = distance;
}
public MinMaxBounds.Ints getLevel() {
return this.level;
}
public void setLevel(MinMaxBounds.Ints level) {
this.level = level;
}
public WrappedMinMaxBounds getRotX() {
return this.rotX;
}
public void setRotX(WrappedMinMaxBounds rotX) {
this.rotX = rotX;
}
public WrappedMinMaxBounds getRotY() {
return this.rotY;
}
public void setRotY(WrappedMinMaxBounds rotY) {
this.rotY = rotY;
}
@Nullable
public Double getX() {
return this.x;
}
@Nullable
public Double getY() {
return this.y;
}
@Nullable
public Double getZ() {
return this.z;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void setZ(double z) {
this.z = z;
}
public void setDeltaX(double deltaX) {
this.deltaX = deltaX;
}
public void setDeltaY(double deltaY) {
this.deltaY = deltaY;
}
public void setDeltaZ(double deltaZ) {
this.deltaZ = deltaZ;
}
@Nullable
public Double getDeltaX() {
return this.deltaX;
}
@Nullable
public Double getDeltaY() {
return this.deltaY;
}
@Nullable
public Double getDeltaZ() {
return this.deltaZ;
}
public void setMaxResults(int maxResults) {
this.maxResults = maxResults;
}
public void setIncludesEntities(boolean includesEntities) {
this.includesEntities = includesEntities;
}
public BiConsumer<Vec3, List<? extends Entity>> getOrder() {
return this.order;
}
public void setOrder(BiConsumer<Vec3, List<? extends Entity>> order) {
this.order = order;
}
public EntitySelector parse() throws CommandSyntaxException {
this.startPosition = this.reader.getCursor();
this.suggestions = this::suggestNameOrSelector;
if (this.reader.canRead() && this.reader.peek() == '@') {
if (!this.allowSelectors) {
throw ERROR_SELECTORS_NOT_ALLOWED.createWithContext(this.reader);
}
this.reader.skip();
this.parseSelector();
} else {
this.parseNameOrUUID();
}
this.finalizePredicates();
return this.getSelector();
}
private static void fillSelectorSuggestions(SuggestionsBuilder builder) {
builder.suggest("@p", Component.translatable("argument.entity.selector.nearestPlayer"));
builder.suggest("@a", Component.translatable("argument.entity.selector.allPlayers"));
builder.suggest("@r", Component.translatable("argument.entity.selector.randomPlayer"));
builder.suggest("@s", Component.translatable("argument.entity.selector.self"));
builder.suggest("@e", Component.translatable("argument.entity.selector.allEntities"));
builder.suggest("@n", Component.translatable("argument.entity.selector.nearestEntity"));
}
private CompletableFuture<Suggestions> suggestNameOrSelector(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
consumer.accept(builder);
if (this.allowSelectors) {
fillSelectorSuggestions(builder);
}
return builder.buildFuture();
}
private CompletableFuture<Suggestions> suggestName(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
SuggestionsBuilder suggestionsBuilder = builder.createOffset(this.startPosition);
consumer.accept(suggestionsBuilder);
return builder.add(suggestionsBuilder).buildFuture();
}
private CompletableFuture<Suggestions> suggestSelector(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
SuggestionsBuilder suggestionsBuilder = builder.createOffset(builder.getStart() - 1);
fillSelectorSuggestions(suggestionsBuilder);
builder.add(suggestionsBuilder);
return builder.buildFuture();
}
private CompletableFuture<Suggestions> suggestOpenOptions(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
builder.suggest(String.valueOf('['));
return builder.buildFuture();
}
private CompletableFuture<Suggestions> suggestOptionsKeyOrClose(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
builder.suggest(String.valueOf(']'));
EntitySelectorOptions.suggestNames(this, builder);
return builder.buildFuture();
}
private CompletableFuture<Suggestions> suggestOptionsKey(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
EntitySelectorOptions.suggestNames(this, builder);
return builder.buildFuture();
}
private CompletableFuture<Suggestions> suggestOptionsNextOrClose(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
builder.suggest(String.valueOf(','));
builder.suggest(String.valueOf(']'));
return builder.buildFuture();
}
private CompletableFuture<Suggestions> suggestEquals(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
builder.suggest(String.valueOf('='));
return builder.buildFuture();
}
public boolean isCurrentEntity() {
return this.currentEntity;
}
public void setSuggestions(BiFunction<SuggestionsBuilder, Consumer<SuggestionsBuilder>, CompletableFuture<Suggestions>> suggestionHandler) {
this.suggestions = suggestionHandler;
}
public CompletableFuture<Suggestions> fillSuggestions(SuggestionsBuilder builder, Consumer<SuggestionsBuilder> consumer) {
return (CompletableFuture<Suggestions>)this.suggestions.apply(builder.createOffset(this.reader.getCursor()), consumer);
}
public boolean hasNameEquals() {
return this.hasNameEquals;
}
public void setHasNameEquals(boolean hasNameEquals) {
this.hasNameEquals = hasNameEquals;
}
public boolean hasNameNotEquals() {
return this.hasNameNotEquals;
}
public void setHasNameNotEquals(boolean hasNameNotEquals) {
this.hasNameNotEquals = hasNameNotEquals;
}
public boolean isLimited() {
return this.isLimited;
}
public void setLimited(boolean isLimited) {
this.isLimited = isLimited;
}
public boolean isSorted() {
return this.isSorted;
}
public void setSorted(boolean isSorted) {
this.isSorted = isSorted;
}
public boolean hasGamemodeEquals() {
return this.hasGamemodeEquals;
}
public void setHasGamemodeEquals(boolean hasGamemodeEquals) {
this.hasGamemodeEquals = hasGamemodeEquals;
}
public boolean hasGamemodeNotEquals() {
return this.hasGamemodeNotEquals;
}
public void setHasGamemodeNotEquals(boolean hasGamemodeNotEquals) {
this.hasGamemodeNotEquals = hasGamemodeNotEquals;
}
public boolean hasTeamEquals() {
return this.hasTeamEquals;
}
public void setHasTeamEquals(boolean hasTeamEquals) {
this.hasTeamEquals = hasTeamEquals;
}
public boolean hasTeamNotEquals() {
return this.hasTeamNotEquals;
}
public void setHasTeamNotEquals(boolean hasTeamNotEquals) {
this.hasTeamNotEquals = hasTeamNotEquals;
}
public void limitToType(EntityType<?> type) {
this.type = type;
}
public void setTypeLimitedInversely() {
this.typeInverse = true;
}
public boolean isTypeLimited() {
return this.type != null;
}
public boolean isTypeLimitedInversely() {
return this.typeInverse;
}
public boolean hasScores() {
return this.hasScores;
}
public void setHasScores(boolean hasScores) {
this.hasScores = hasScores;
}
public boolean hasAdvancements() {
return this.hasAdvancements;
}
public void setHasAdvancements(boolean hasAdvancements) {
this.hasAdvancements = hasAdvancements;
}
}