package org.chrisoft.trashyaddon.commands; 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.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.predicate.NumberRange; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.math.Box; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.command.FloatRangeArgument; import org.jetbrains.annotations.Nullable; public class CEntitySelectorReader { public static final char SELECTOR_PREFIX = '@'; private static final char ARGUMENTS_OPENING = '['; private static final char ARGUMENTS_CLOSING = ']'; public static final char ARGUMENT_DEFINER = '='; private static final char ARGUMENT_SEPARATOR = ','; public static final char INVERT_MODIFIER = '!'; public static final char TAG_MODIFIER = '#'; private static final char NEAREST_PLAYER = 'p'; private static final char ALL_PLAYERS = 'a'; private static final char RANDOM_PLAYER = 'r'; private static final char SELF = 's'; private static final char ALL_ENTITIES = 'e'; private static final char NEAREST_ENTITY = 'n'; public static final SimpleCommandExceptionType INVALID_ENTITY_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.entity.invalid")); public static final DynamicCommandExceptionType UNKNOWN_SELECTOR_EXCEPTION = new DynamicCommandExceptionType( selectorType -> Text.stringifiedTranslatable("argument.entity.selector.unknown", selectorType) ); public static final SimpleCommandExceptionType NOT_ALLOWED_EXCEPTION = new SimpleCommandExceptionType( Text.translatable("argument.entity.selector.not_allowed") ); public static final SimpleCommandExceptionType MISSING_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.entity.selector.missing")); public static final SimpleCommandExceptionType UNTERMINATED_EXCEPTION = new SimpleCommandExceptionType( Text.translatable("argument.entity.options.unterminated") ); public static final DynamicCommandExceptionType VALUELESS_EXCEPTION = new DynamicCommandExceptionType( option -> Text.stringifiedTranslatable("argument.entity.options.valueless", option) ); public static final BiConsumer> NEAREST = (pos, entities) -> entities.sort( (entity1, entity2) -> Doubles.compare(entity1.squaredDistanceTo(pos), entity2.squaredDistanceTo(pos)) ); public static final BiConsumer> FURTHEST = (pos, entities) -> entities.sort( (entity1, entity2) -> Doubles.compare(entity2.squaredDistanceTo(pos), entity1.squaredDistanceTo(pos)) ); public static final BiConsumer> RANDOM = (pos, entities) -> Collections.shuffle(entities); public static final BiFunction, CompletableFuture> DEFAULT_SUGGESTION_PROVIDER = (builder, consumer) -> builder.buildFuture(); private final StringReader reader; private final boolean atAllowed; private int limit; private boolean includesNonPlayers; private boolean localWorldOnly; private NumberRange.DoubleRange distance = NumberRange.DoubleRange.ANY; private NumberRange.IntRange levelRange = NumberRange.IntRange.ANY; @Nullable private Double x; @Nullable private Double y; @Nullable private Double z; @Nullable private Double dx; @Nullable private Double dy; @Nullable private Double dz; private FloatRangeArgument pitchRange = FloatRangeArgument.ANY; private FloatRangeArgument yawRange = FloatRangeArgument.ANY; private final List> predicates = new ArrayList<>(); private BiConsumer> sorter = CEntitySelector.ARBITRARY; private boolean senderOnly; @Nullable private String playerName; private int startCursor; @Nullable private UUID uuid; private BiFunction, CompletableFuture> suggestionProvider = DEFAULT_SUGGESTION_PROVIDER; private boolean selectsName; private boolean excludesName; private boolean hasLimit; private boolean hasSorter; private boolean selectsGameMode; private boolean excludesGameMode; private boolean selectsTeam; private boolean excludesTeam; @Nullable private EntityType entityType; private boolean excludesEntityType; private boolean selectsScores; private boolean selectsAdvancements; private boolean usesAt; public CEntitySelectorReader(StringReader reader, boolean atAllowed) { this.reader = reader; this.atAllowed = atAllowed; } public static boolean shouldAllowAtSelectors(S source) { /*if (source instanceof CommandSource lv && lv.hasPermissionLevel(2)) { return true; } return false;*/ return true; } public CEntitySelector build() { Box lv; if (this.dx == null && this.dy == null && this.dz == null) { if (this.distance.max().isPresent()) { double d = this.distance.max().get(); lv = new Box(-d, -d, -d, d + 1.0, d + 1.0, d + 1.0); } else { lv = null; } } else { lv = this.createBox(this.dx == null ? 0.0 : this.dx, this.dy == null ? 0.0 : this.dy, this.dz == null ? 0.0 : this.dz); } Function function; if (this.x == null && this.y == null && this.z == null) { function = pos -> pos; } else { function = pos -> new Vec3d(this.x == null ? pos.x : this.x, this.y == null ? pos.y : this.y, this.z == null ? pos.z : this.z); } return new CEntitySelector( this.limit, this.includesNonPlayers, this.localWorldOnly, List.copyOf(this.predicates), this.distance, function, lv, this.sorter, this.senderOnly, this.playerName, this.uuid, this.entityType, this.usesAt ); } private Box createBox(double x, double y, double z) { boolean bl = x < 0.0; boolean bl2 = y < 0.0; boolean bl3 = z < 0.0; double g = bl ? x : 0.0; double h = bl2 ? y : 0.0; double i = bl3 ? z : 0.0; double j = (bl ? 0.0 : x) + 1.0; double k = (bl2 ? 0.0 : y) + 1.0; double l = (bl3 ? 0.0 : z) + 1.0; return new Box(g, h, i, j, k, l); } private void buildPredicate() { if (this.pitchRange != FloatRangeArgument.ANY) { this.predicates.add(this.rotationPredicate(this.pitchRange, Entity::getPitch)); } if (this.yawRange != FloatRangeArgument.ANY) { this.predicates.add(this.rotationPredicate(this.yawRange, Entity::getYaw)); } if (!this.levelRange.isDummy()) { this.predicates.add(entity -> !(entity instanceof ServerPlayerEntity) ? false : this.levelRange.test(((ServerPlayerEntity)entity).experienceLevel)); } } private Predicate rotationPredicate(FloatRangeArgument angleRange, ToDoubleFunction entityToAngle) { double d = (double)MathHelper.wrapDegrees(angleRange.min() == null ? 0.0F : angleRange.min()); double e = (double)MathHelper.wrapDegrees(angleRange.max() == null ? 359.0F : angleRange.max()); return entity -> { double f = MathHelper.wrapDegrees(entityToAngle.applyAsDouble(entity)); return d > e ? f >= d || f <= e : f >= d && f <= e; }; } protected void readAtVariable() throws CommandSyntaxException { this.usesAt = true; this.suggestionProvider = this::suggestSelectorRest; if (!this.reader.canRead()) { throw MISSING_EXCEPTION.createWithContext(this.reader); } else { int i = this.reader.getCursor(); char c = this.reader.read(); if (switch (c) { case 'a' -> { this.limit = Integer.MAX_VALUE; this.includesNonPlayers = false; this.sorter = CEntitySelector.ARBITRARY; this.setEntityType(EntityType.PLAYER); yield false; } default -> { this.reader.setCursor(i); throw UNKNOWN_SELECTOR_EXCEPTION.createWithContext(this.reader, "@" + c); } case 'e' -> { this.limit = Integer.MAX_VALUE; this.includesNonPlayers = true; this.sorter = CEntitySelector.ARBITRARY; yield true; } case 'n' -> { this.limit = 1; this.includesNonPlayers = true; this.sorter = NEAREST; yield true; } case 'p' -> { this.limit = 1; this.includesNonPlayers = false; this.sorter = NEAREST; this.setEntityType(EntityType.PLAYER); yield false; } case 'r' -> { this.limit = 1; this.includesNonPlayers = false; this.sorter = RANDOM; this.setEntityType(EntityType.PLAYER); yield false; } case 's' -> { this.limit = 1; this.includesNonPlayers = true; this.senderOnly = true; yield false; } }) { this.predicates.add(Entity::isAlive); } this.suggestionProvider = this::suggestOpen; if (this.reader.canRead() && this.reader.peek() == '[') { this.reader.skip(); this.suggestionProvider = this::suggestOptionOrEnd; this.readArguments(); } } } protected void readRegular() throws CommandSyntaxException { if (this.reader.canRead()) { this.suggestionProvider = this::suggestNormal; } int i = this.reader.getCursor(); String string = this.reader.readString(); try { this.uuid = UUID.fromString(string); this.includesNonPlayers = true; } catch (IllegalArgumentException var4) { if (string.isEmpty() || string.length() > 16) { this.reader.setCursor(i); throw INVALID_ENTITY_EXCEPTION.createWithContext(this.reader); } this.includesNonPlayers = false; this.playerName = string; } this.limit = 1; } protected void readArguments() throws CommandSyntaxException { this.suggestionProvider = this::suggestOption; this.reader.skipWhitespace(); while (this.reader.canRead() && this.reader.peek() != ']') { this.reader.skipWhitespace(); int i = this.reader.getCursor(); String string = this.reader.readString(); CEntitySelectorOptions.SelectorHandler lv = CEntitySelectorOptions.getHandler(this, string, i); this.reader.skipWhitespace(); if (!this.reader.canRead() || this.reader.peek() != '=') { this.reader.setCursor(i); throw VALUELESS_EXCEPTION.createWithContext(this.reader, string); } this.reader.skip(); this.reader.skipWhitespace(); this.suggestionProvider = DEFAULT_SUGGESTION_PROVIDER; lv.handle(this); this.reader.skipWhitespace(); this.suggestionProvider = this::suggestEndNext; if (this.reader.canRead()) { if (this.reader.peek() != ',') { if (this.reader.peek() != ']') { throw UNTERMINATED_EXCEPTION.createWithContext(this.reader); } break; } this.reader.skip(); this.suggestionProvider = this::suggestOption; } } if (this.reader.canRead()) { this.reader.skip(); this.suggestionProvider = DEFAULT_SUGGESTION_PROVIDER; } else { throw UNTERMINATED_EXCEPTION.createWithContext(this.reader); } } public boolean readNegationCharacter() { this.reader.skipWhitespace(); if (this.reader.canRead() && this.reader.peek() == '!') { this.reader.skip(); this.reader.skipWhitespace(); return true; } else { return false; } } public boolean readTagCharacter() { 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 predicate) { this.predicates.add(predicate); } public void setLocalWorldOnly() { this.localWorldOnly = true; } public NumberRange.DoubleRange getDistance() { return this.distance; } public void setDistance(NumberRange.DoubleRange distance) { this.distance = distance; } public NumberRange.IntRange getLevelRange() { return this.levelRange; } public void setLevelRange(NumberRange.IntRange levelRange) { this.levelRange = levelRange; } public FloatRangeArgument getPitchRange() { return this.pitchRange; } public void setPitchRange(FloatRangeArgument pitchRange) { this.pitchRange = pitchRange; } public FloatRangeArgument getYawRange() { return this.yawRange; } public void setYawRange(FloatRangeArgument yawRange) { this.yawRange = yawRange; } @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 setDx(double dx) { this.dx = dx; } public void setDy(double dy) { this.dy = dy; } public void setDz(double dz) { this.dz = dz; } @Nullable public Double getDx() { return this.dx; } @Nullable public Double getDy() { return this.dy; } @Nullable public Double getDz() { return this.dz; } public void setLimit(int limit) { this.limit = limit; } public void setIncludesNonPlayers(boolean includesNonPlayers) { this.includesNonPlayers = includesNonPlayers; } public BiConsumer> getSorter() { return this.sorter; } public void setSorter(BiConsumer> sorter) { this.sorter = sorter; } public CEntitySelector read() throws CommandSyntaxException { this.startCursor = this.reader.getCursor(); this.suggestionProvider = this::suggestSelector; if (this.reader.canRead() && this.reader.peek() == '@') { if (!this.atAllowed) { throw NOT_ALLOWED_EXCEPTION.createWithContext(this.reader); } this.reader.skip(); this.readAtVariable(); } else { this.readRegular(); } this.buildPredicate(); return this.build(); } private static void suggestSelector(SuggestionsBuilder builder) { builder.suggest("@p", Text.translatable("argument.entity.selector.nearestPlayer")); builder.suggest("@a", Text.translatable("argument.entity.selector.allPlayers")); builder.suggest("@r", Text.translatable("argument.entity.selector.randomPlayer")); builder.suggest("@s", Text.translatable("argument.entity.selector.self")); builder.suggest("@e", Text.translatable("argument.entity.selector.allEntities")); builder.suggest("@n", Text.translatable("argument.entity.selector.nearestEntity")); } private CompletableFuture suggestSelector(SuggestionsBuilder builder, Consumer consumer) { consumer.accept(builder); if (this.atAllowed) { suggestSelector(builder); } return builder.buildFuture(); } private CompletableFuture suggestNormal(SuggestionsBuilder builder, Consumer consumer) { SuggestionsBuilder suggestionsBuilder2 = builder.createOffset(this.startCursor); consumer.accept(suggestionsBuilder2); return builder.add(suggestionsBuilder2).buildFuture(); } private CompletableFuture suggestSelectorRest(SuggestionsBuilder builder, Consumer consumer) { SuggestionsBuilder suggestionsBuilder2 = builder.createOffset(builder.getStart() - 1); suggestSelector(suggestionsBuilder2); builder.add(suggestionsBuilder2); return builder.buildFuture(); } private CompletableFuture suggestOpen(SuggestionsBuilder builder, Consumer consumer) { builder.suggest(String.valueOf('[')); return builder.buildFuture(); } private CompletableFuture suggestOptionOrEnd(SuggestionsBuilder builder, Consumer consumer) { builder.suggest(String.valueOf(']')); CEntitySelectorOptions.suggestOptions(this, builder); return builder.buildFuture(); } private CompletableFuture suggestOption(SuggestionsBuilder builder, Consumer consumer) { CEntitySelectorOptions.suggestOptions(this, builder); return builder.buildFuture(); } private CompletableFuture suggestEndNext(SuggestionsBuilder builder, Consumer consumer) { builder.suggest(String.valueOf(',')); builder.suggest(String.valueOf(']')); return builder.buildFuture(); } private CompletableFuture suggestDefinerNext(SuggestionsBuilder builder, Consumer consumer) { builder.suggest(String.valueOf('=')); return builder.buildFuture(); } public boolean isSenderOnly() { return this.senderOnly; } public void setSuggestionProvider(BiFunction, CompletableFuture> suggestionProvider) { this.suggestionProvider = suggestionProvider; } public CompletableFuture listSuggestions(SuggestionsBuilder builder, Consumer consumer) { return this.suggestionProvider.apply(builder.createOffset(this.reader.getCursor()), consumer); } public boolean selectsName() { return this.selectsName; } public void setSelectsName(boolean selectsName) { this.selectsName = selectsName; } public boolean excludesName() { return this.excludesName; } public void setExcludesName(boolean excludesName) { this.excludesName = excludesName; } public boolean hasLimit() { return this.hasLimit; } public void setHasLimit(boolean hasLimit) { this.hasLimit = hasLimit; } public boolean hasSorter() { return this.hasSorter; } public void setHasSorter(boolean hasSorter) { this.hasSorter = hasSorter; } public boolean selectsGameMode() { return this.selectsGameMode; } public void setSelectsGameMode(boolean selectsGameMode) { this.selectsGameMode = selectsGameMode; } public boolean excludesGameMode() { return this.excludesGameMode; } public void setExcludesGameMode(boolean excludesGameMode) { this.excludesGameMode = excludesGameMode; } public boolean selectsTeam() { return this.selectsTeam; } public void setSelectsTeam(boolean selectsTeam) { this.selectsTeam = selectsTeam; } public boolean excludesTeam() { return this.excludesTeam; } public void setExcludesTeam(boolean excludesTeam) { this.excludesTeam = excludesTeam; } public void setEntityType(EntityType entityType) { this.entityType = entityType; } public void setExcludesEntityType() { this.excludesEntityType = true; } public boolean selectsEntityType() { return this.entityType != null; } public boolean excludesEntityType() { return this.excludesEntityType; } public boolean selectsScores() { return this.selectsScores; } public void setSelectsScores(boolean selectsScores) { this.selectsScores = selectsScores; } public boolean selectsAdvancements() { return this.selectsAdvancements; } public void setSelectsAdvancements(boolean selectsAdvancements) { this.selectsAdvancements = selectsAdvancements; } }