package org.chrisoft.trashyaddon.commands; import com.google.common.collect.Maps; 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.SuggestionsBuilder; import java.util.Arrays; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Map.Entry; import java.util.function.Predicate; import net.minecraft.advancement.AdvancementEntry; import net.minecraft.advancement.AdvancementProgress; import net.minecraft.advancement.PlayerAdvancementTracker; import net.minecraft.advancement.criterion.CriterionProgress; import net.minecraft.command.CommandSource; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemStack; import net.minecraft.loot.condition.LootCondition; import net.minecraft.loot.context.LootContext; import net.minecraft.loot.context.LootContextParameterSet; import net.minecraft.loot.context.LootContextParameters; import net.minecraft.loot.context.LootContextTypes; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtHelper; import net.minecraft.nbt.StringNbtReader; import net.minecraft.predicate.NumberRange; import net.minecraft.registry.Registries; import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.tag.TagKey; import net.minecraft.scoreboard.AbstractTeam; import net.minecraft.scoreboard.ReadableScoreboardScore; import net.minecraft.scoreboard.Scoreboard; import net.minecraft.scoreboard.ScoreboardObjective; import net.minecraft.server.ServerAdvancementLoader; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.math.MathHelper; import net.minecraft.world.GameMode; import net.minecraft.command.FloatRangeArgument; public class CEntitySelectorOptions { private static final Map OPTIONS = Maps.newHashMap(); public static final DynamicCommandExceptionType UNKNOWN_OPTION_EXCEPTION = new DynamicCommandExceptionType( option -> Text.stringifiedTranslatable("argument.entity.options.unknown", option) ); public static final DynamicCommandExceptionType INAPPLICABLE_OPTION_EXCEPTION = new DynamicCommandExceptionType( option -> Text.stringifiedTranslatable("argument.entity.options.inapplicable", option) ); public static final SimpleCommandExceptionType NEGATIVE_DISTANCE_EXCEPTION = new SimpleCommandExceptionType( Text.translatable("argument.entity.options.distance.negative") ); public static final SimpleCommandExceptionType NEGATIVE_LEVEL_EXCEPTION = new SimpleCommandExceptionType( Text.translatable("argument.entity.options.level.negative") ); public static final SimpleCommandExceptionType TOO_SMALL_LEVEL_EXCEPTION = new SimpleCommandExceptionType( Text.translatable("argument.entity.options.limit.toosmall") ); public static final DynamicCommandExceptionType IRREVERSIBLE_SORT_EXCEPTION = new DynamicCommandExceptionType( sortType -> Text.stringifiedTranslatable("argument.entity.options.sort.irreversible", sortType) ); public static final DynamicCommandExceptionType INVALID_MODE_EXCEPTION = new DynamicCommandExceptionType( gameMode -> Text.stringifiedTranslatable("argument.entity.options.mode.invalid", gameMode) ); public static final DynamicCommandExceptionType INVALID_TYPE_EXCEPTION = new DynamicCommandExceptionType( entity -> Text.stringifiedTranslatable("argument.entity.options.type.invalid", entity) ); private static void putOption(String id, CEntitySelectorOptions.SelectorHandler handler, Predicate condition, Text description) { OPTIONS.put(id, new CEntitySelectorOptions.SelectorOption(handler, condition, description)); } public static void register() { if (OPTIONS.isEmpty()) { putOption("name", reader -> { int i = reader.getReader().getCursor(); boolean bl = reader.readNegationCharacter(); String string = reader.getReader().readString(); if (reader.excludesName() && !bl) { reader.getReader().setCursor(i); throw INAPPLICABLE_OPTION_EXCEPTION.createWithContext(reader.getReader(), "name"); } else { if (bl) { reader.setExcludesName(true); } else { reader.setSelectsName(true); } reader.addPredicate(entity -> entity.getName().getString().equals(string) != bl); } }, reader -> !reader.selectsName(), Text.translatable("argument.entity.options.name.description")); putOption("distance", reader -> { int i = reader.getReader().getCursor(); NumberRange.DoubleRange lv = NumberRange.DoubleRange.parse(reader.getReader()); if ((!lv.min().isPresent() || !(lv.min().get() < 0.0)) && (!lv.max().isPresent() || !(lv.max().get() < 0.0))) { reader.setDistance(lv); reader.setLocalWorldOnly(); } else { reader.getReader().setCursor(i); throw NEGATIVE_DISTANCE_EXCEPTION.createWithContext(reader.getReader()); } }, reader -> reader.getDistance().isDummy(), Text.translatable("argument.entity.options.distance.description")); putOption("level", reader -> { int i = reader.getReader().getCursor(); NumberRange.IntRange lv = NumberRange.IntRange.parse(reader.getReader()); if ((!lv.min().isPresent() || lv.min().get() >= 0) && (!lv.max().isPresent() || lv.max().get() >= 0)) { reader.setLevelRange(lv); reader.setIncludesNonPlayers(false); } else { reader.getReader().setCursor(i); throw NEGATIVE_LEVEL_EXCEPTION.createWithContext(reader.getReader()); } }, reader -> reader.getLevelRange().isDummy(), Text.translatable("argument.entity.options.level.description")); putOption("x", reader -> { reader.setLocalWorldOnly(); reader.setX(reader.getReader().readDouble()); }, reader -> reader.getX() == null, Text.translatable("argument.entity.options.x.description")); putOption("y", reader -> { reader.setLocalWorldOnly(); reader.setY(reader.getReader().readDouble()); }, reader -> reader.getY() == null, Text.translatable("argument.entity.options.y.description")); putOption("z", reader -> { reader.setLocalWorldOnly(); reader.setZ(reader.getReader().readDouble()); }, reader -> reader.getZ() == null, Text.translatable("argument.entity.options.z.description")); putOption("dx", reader -> { reader.setLocalWorldOnly(); reader.setDx(reader.getReader().readDouble()); }, reader -> reader.getDx() == null, Text.translatable("argument.entity.options.dx.description")); putOption("dy", reader -> { reader.setLocalWorldOnly(); reader.setDy(reader.getReader().readDouble()); }, reader -> reader.getDy() == null, Text.translatable("argument.entity.options.dy.description")); putOption("dz", reader -> { reader.setLocalWorldOnly(); reader.setDz(reader.getReader().readDouble()); }, reader -> reader.getDz() == null, Text.translatable("argument.entity.options.dz.description")); putOption( "x_rotation", reader -> reader.setPitchRange(FloatRangeArgument.parse(reader.getReader(), true, MathHelper::wrapDegrees)), reader -> reader.getPitchRange() == FloatRangeArgument.ANY, Text.translatable("argument.entity.options.x_rotation.description") ); putOption( "y_rotation", reader -> reader.setYawRange(FloatRangeArgument.parse(reader.getReader(), true, MathHelper::wrapDegrees)), reader -> reader.getYawRange() == FloatRangeArgument.ANY, Text.translatable("argument.entity.options.y_rotation.description") ); putOption("limit", reader -> { int i = reader.getReader().getCursor(); int j = reader.getReader().readInt(); if (j < 1) { reader.getReader().setCursor(i); throw TOO_SMALL_LEVEL_EXCEPTION.createWithContext(reader.getReader()); } else { reader.setLimit(j); reader.setHasLimit(true); } }, reader -> !reader.isSenderOnly() && !reader.hasLimit(), Text.translatable("argument.entity.options.limit.description")); putOption( "sort", reader -> { int i = reader.getReader().getCursor(); String string = reader.getReader().readUnquotedString(); reader.setSuggestionProvider( (builder, consumer) -> CommandSource.suggestMatching(Arrays.asList("nearest", "furthest", "random", "arbitrary"), builder) ); reader.setSorter(switch (string) { case "nearest" -> CEntitySelectorReader.NEAREST; case "furthest" -> CEntitySelectorReader.FURTHEST; case "random" -> CEntitySelectorReader.RANDOM; case "arbitrary" -> CEntitySelector.ARBITRARY; default -> { reader.getReader().setCursor(i); throw IRREVERSIBLE_SORT_EXCEPTION.createWithContext(reader.getReader(), string); } }); reader.setHasSorter(true); }, reader -> !reader.isSenderOnly() && !reader.hasSorter(), Text.translatable("argument.entity.options.sort.description") ); putOption("gamemode", reader -> { reader.setSuggestionProvider((builder, consumer) -> { String stringx = builder.getRemaining().toLowerCase(Locale.ROOT); boolean blx = !reader.excludesGameMode(); boolean bl2 = true; if (!stringx.isEmpty()) { if (stringx.charAt(0) == '!') { blx = false; stringx = stringx.substring(1); } else { bl2 = false; } } for (GameMode lvx : GameMode.values()) { if (lvx.getName().toLowerCase(Locale.ROOT).startsWith(stringx)) { if (bl2) { builder.suggest("!" + lvx.getName()); } if (blx) { builder.suggest(lvx.getName()); } } } return builder.buildFuture(); }); int i = reader.getReader().getCursor(); boolean bl = reader.readNegationCharacter(); if (reader.excludesGameMode() && !bl) { reader.getReader().setCursor(i); throw INAPPLICABLE_OPTION_EXCEPTION.createWithContext(reader.getReader(), "gamemode"); } else { String string = reader.getReader().readUnquotedString(); GameMode lv = GameMode.byName(string, null); if (lv == null) { reader.getReader().setCursor(i); throw INVALID_MODE_EXCEPTION.createWithContext(reader.getReader(), string); } else { reader.setIncludesNonPlayers(false); reader.addPredicate(entity -> { if (!(entity instanceof ServerPlayerEntity)) { return false; } else { GameMode lvx = ((ServerPlayerEntity)entity).interactionManager.getGameMode(); return bl ? lvx != lv : lvx == lv; } }); if (bl) { reader.setExcludesGameMode(true); } else { reader.setSelectsGameMode(true); } } } }, reader -> !reader.selectsGameMode(), Text.translatable("argument.entity.options.gamemode.description")); putOption("team", reader -> { boolean bl = reader.readNegationCharacter(); String string = reader.getReader().readUnquotedString(); reader.addPredicate(entity -> { if (!(entity instanceof LivingEntity)) { return false; } else { AbstractTeam lv = entity.getScoreboardTeam(); String string2 = lv == null ? "" : lv.getName(); return string2.equals(string) != bl; } }); if (bl) { reader.setExcludesTeam(true); } else { reader.setSelectsTeam(true); } }, reader -> !reader.selectsTeam(), Text.translatable("argument.entity.options.team.description")); putOption("type", reader -> { reader.setSuggestionProvider((builder, consumer) -> { CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getIds(), builder, String.valueOf('!')); CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.streamTags().map(TagKey::id), builder, "!#"); if (!reader.excludesEntityType()) { CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getIds(), builder); CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.streamTags().map(TagKey::id), builder, String.valueOf('#')); } return builder.buildFuture(); }); int i = reader.getReader().getCursor(); boolean bl = reader.readNegationCharacter(); if (reader.excludesEntityType() && !bl) { reader.getReader().setCursor(i); throw INAPPLICABLE_OPTION_EXCEPTION.createWithContext(reader.getReader(), "type"); } else { if (bl) { reader.setExcludesEntityType(); } if (reader.readTagCharacter()) { TagKey> lv = TagKey.of(RegistryKeys.ENTITY_TYPE, Identifier.fromCommandInput(reader.getReader())); reader.addPredicate(entity -> entity.getType().isIn(lv) != bl); } else { Identifier lv2 = Identifier.fromCommandInput(reader.getReader()); EntityType lv3 = Registries.ENTITY_TYPE.getOrEmpty(lv2).orElseThrow(() -> { reader.getReader().setCursor(i); return INVALID_TYPE_EXCEPTION.createWithContext(reader.getReader(), lv2.toString()); }); if (Objects.equals(EntityType.PLAYER, lv3) && !bl) { reader.setIncludesNonPlayers(false); } reader.addPredicate(entity -> Objects.equals(lv3, entity.getType()) != bl); if (!bl) { reader.setEntityType(lv3); } } } }, reader -> !reader.selectsEntityType(), Text.translatable("argument.entity.options.type.description")); putOption("tag", reader -> { boolean bl = reader.readNegationCharacter(); String string = reader.getReader().readUnquotedString(); reader.addPredicate(entity -> "".equals(string) ? entity.getCommandTags().isEmpty() != bl : entity.getCommandTags().contains(string) != bl); }, reader -> true, Text.translatable("argument.entity.options.tag.description")); putOption("nbt", reader -> { boolean bl = reader.readNegationCharacter(); NbtCompound lv = new StringNbtReader(reader.getReader()).parseCompound(); reader.addPredicate(entity -> { NbtCompound lvx = entity.writeNbt(new NbtCompound()); if (entity instanceof ServerPlayerEntity lv2) { ItemStack lv3 = lv2.getInventory().getMainHandStack(); if (!lv3.isEmpty()) { lvx.put("SelectedItem", lv3.encode(lv2.getRegistryManager())); } } return NbtHelper.matches(lv, lvx, true) != bl; }); }, reader -> true, Text.translatable("argument.entity.options.nbt.description")); putOption("scores", reader -> { StringReader stringReader = reader.getReader(); Map map = Maps.newHashMap(); stringReader.expect('{'); stringReader.skipWhitespace(); while (stringReader.canRead() && stringReader.peek() != '}') { stringReader.skipWhitespace(); String string = stringReader.readUnquotedString(); stringReader.skipWhitespace(); stringReader.expect('='); stringReader.skipWhitespace(); NumberRange.IntRange lv = NumberRange.IntRange.parse(stringReader); map.put(string, lv); stringReader.skipWhitespace(); if (stringReader.canRead() && stringReader.peek() == ',') { stringReader.skip(); } } stringReader.expect('}'); if (!map.isEmpty()) { reader.addPredicate(entity -> { Scoreboard lvx = entity.getServer().getScoreboard(); for (Entry entry : map.entrySet()) { ScoreboardObjective lv2 = lvx.getNullableObjective(entry.getKey()); if (lv2 == null) { return false; } ReadableScoreboardScore lv3 = lvx.getScore(entity, lv2); if (lv3 == null) { return false; } if (!entry.getValue().test(lv3.getScore())) { return false; } } return true; }); } reader.setSelectsScores(true); }, reader -> !reader.selectsScores(), Text.translatable("argument.entity.options.scores.description")); putOption("advancements", reader -> { StringReader stringReader = reader.getReader(); Map> map = Maps.newHashMap(); stringReader.expect('{'); stringReader.skipWhitespace(); while (stringReader.canRead() && stringReader.peek() != '}') { stringReader.skipWhitespace(); Identifier lv = Identifier.fromCommandInput(stringReader); stringReader.skipWhitespace(); stringReader.expect('='); stringReader.skipWhitespace(); if (stringReader.canRead() && stringReader.peek() == '{') { Map> map2 = Maps.newHashMap(); stringReader.skipWhitespace(); stringReader.expect('{'); stringReader.skipWhitespace(); while (stringReader.canRead() && stringReader.peek() != '}') { stringReader.skipWhitespace(); String string = stringReader.readUnquotedString(); stringReader.skipWhitespace(); stringReader.expect('='); stringReader.skipWhitespace(); boolean bl = stringReader.readBoolean(); map2.put(string, criterionProgress -> criterionProgress.isObtained() == bl); stringReader.skipWhitespace(); if (stringReader.canRead() && stringReader.peek() == ',') { stringReader.skip(); } } stringReader.skipWhitespace(); stringReader.expect('}'); stringReader.skipWhitespace(); map.put(lv, advancementProgress -> { for (Entry> entry : map2.entrySet()) { CriterionProgress lvx = advancementProgress.getCriterionProgress(entry.getKey()); if (lvx == null || !entry.getValue().test(lvx)) { return false; } } return true; }); } else { boolean bl2 = stringReader.readBoolean(); map.put(lv, advancementProgress -> advancementProgress.isDone() == bl2); } stringReader.skipWhitespace(); if (stringReader.canRead() && stringReader.peek() == ',') { stringReader.skip(); } } stringReader.expect('}'); if (!map.isEmpty()) { reader.addPredicate(entity -> { if (!(entity instanceof ServerPlayerEntity lvx)) { return false; } else { PlayerAdvancementTracker lv2 = lvx.getAdvancementTracker(); ServerAdvancementLoader lv3 = lvx.getServer().getAdvancementLoader(); for (Entry> entry : map.entrySet()) { AdvancementEntry lv4 = lv3.get(entry.getKey()); if (lv4 == null || !entry.getValue().test(lv2.getProgress(lv4))) { return false; } } return true; } }); reader.setIncludesNonPlayers(false); } reader.setSelectsAdvancements(true); }, reader -> !reader.selectsAdvancements(), Text.translatable("argument.entity.options.advancements.description")); putOption( "predicate", reader -> { boolean bl = reader.readNegationCharacter(); RegistryKey lv = RegistryKey.of(RegistryKeys.PREDICATE, Identifier.fromCommandInput(reader.getReader())); reader.addPredicate( entity -> { if (!(entity.getWorld() instanceof ServerWorld)) { return false; } else { ServerWorld lvx = (ServerWorld)entity.getWorld(); Optional optional = lvx.getServer() .getReloadableRegistries() .createRegistryLookup() .getOptionalEntry(RegistryKeys.PREDICATE, lv) .map(RegistryEntry::value); if (optional.isEmpty()) { return false; } else { LootContextParameterSet lv2 = new LootContextParameterSet.Builder(lvx) .add(LootContextParameters.THIS_ENTITY, entity) .add(LootContextParameters.ORIGIN, entity.getPos()) .build(LootContextTypes.SELECTOR); LootContext lv3 = new LootContext.Builder(lv2).build(Optional.empty()); lv3.markActive(LootContext.predicate(optional.get())); return bl ^ optional.get().test(lv3); } } } ); }, reader -> true, Text.translatable("argument.entity.options.predicate.description") ); } } public static CEntitySelectorOptions.SelectorHandler getHandler(CEntitySelectorReader reader, String option, int restoreCursor) throws CommandSyntaxException { CEntitySelectorOptions.SelectorOption lv = OPTIONS.get(option); if (lv != null) { if (lv.condition.test(reader)) { return lv.handler; } else { throw INAPPLICABLE_OPTION_EXCEPTION.createWithContext(reader.getReader(), option); } } else { reader.getReader().setCursor(restoreCursor); throw UNKNOWN_OPTION_EXCEPTION.createWithContext(reader.getReader(), option); } } public static void suggestOptions(CEntitySelectorReader reader, SuggestionsBuilder suggestionBuilder) { String string = suggestionBuilder.getRemaining().toLowerCase(Locale.ROOT); for (Entry entry : OPTIONS.entrySet()) { if (entry.getValue().condition.test(reader) && entry.getKey().toLowerCase(Locale.ROOT).startsWith(string)) { suggestionBuilder.suggest(entry.getKey() + "=", entry.getValue().description); } } } public interface SelectorHandler { void handle(CEntitySelectorReader reader) throws CommandSyntaxException; } static record SelectorOption(CEntitySelectorOptions.SelectorHandler handler, Predicate condition, Text description) { } }