package net.minecraft.commands.arguments.item; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; 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 com.mojang.serialization.DataResult; import it.unimi.dsi.fastutil.objects.ReferenceArraySet; import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.PatchedDataComponentMap; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.nbt.TagParser; import net.minecraft.network.chat.Component; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Unit; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import org.apache.commons.lang3.mutable.MutableObject; public class ItemParser { static final DynamicCommandExceptionType ERROR_UNKNOWN_ITEM = new DynamicCommandExceptionType( object -> Component.translatableEscape("argument.item.id.invalid", object) ); static final DynamicCommandExceptionType ERROR_UNKNOWN_COMPONENT = new DynamicCommandExceptionType( object -> Component.translatableEscape("arguments.item.component.unknown", object) ); static final Dynamic2CommandExceptionType ERROR_MALFORMED_COMPONENT = new Dynamic2CommandExceptionType( (object, object2) -> Component.translatableEscape("arguments.item.component.malformed", object, object2) ); static final SimpleCommandExceptionType ERROR_EXPECTED_COMPONENT = new SimpleCommandExceptionType(Component.translatable("arguments.item.component.expected")); static final DynamicCommandExceptionType ERROR_REPEATED_COMPONENT = new DynamicCommandExceptionType( object -> Component.translatableEscape("arguments.item.component.repeated", object) ); private static final DynamicCommandExceptionType ERROR_MALFORMED_ITEM = new DynamicCommandExceptionType( object -> Component.translatableEscape("arguments.item.malformed", object) ); public static final char SYNTAX_START_COMPONENTS = '['; public static final char SYNTAX_END_COMPONENTS = ']'; public static final char SYNTAX_COMPONENT_SEPARATOR = ','; public static final char SYNTAX_COMPONENT_ASSIGNMENT = '='; public static final char SYNTAX_REMOVED_COMPONENT = '!'; static final Function> SUGGEST_NOTHING = SuggestionsBuilder::buildFuture; final HolderLookup.RegistryLookup items; final RegistryOps registryOps; final TagParser tagParser; public ItemParser(HolderLookup.Provider registries) { this.items = registries.lookupOrThrow(Registries.ITEM); this.registryOps = registries.createSerializationContext(NbtOps.INSTANCE); this.tagParser = TagParser.create(this.registryOps); } public ItemParser.ItemResult parse(StringReader reader) throws CommandSyntaxException { final MutableObject> mutableObject = new MutableObject<>(); final DataComponentPatch.Builder builder = DataComponentPatch.builder(); this.parse(reader, new ItemParser.Visitor() { @Override public void visitItem(Holder item) { mutableObject.setValue(item); } @Override public void visitComponent(DataComponentType componentType, T value) { builder.set(componentType, value); } @Override public void visitRemovedComponent(DataComponentType componentType) { builder.remove(componentType); } }); Holder holder = (Holder)Objects.requireNonNull(mutableObject.getValue(), "Parser gave no item"); DataComponentPatch dataComponentPatch = builder.build(); validateComponents(reader, holder, dataComponentPatch); return new ItemParser.ItemResult(holder, dataComponentPatch); } private static void validateComponents(StringReader reader, Holder item, DataComponentPatch components) throws CommandSyntaxException { DataComponentMap dataComponentMap = PatchedDataComponentMap.fromPatch(item.value().components(), components); DataResult dataResult = ItemStack.validateComponents(dataComponentMap); dataResult.getOrThrow(string -> ERROR_MALFORMED_ITEM.createWithContext(reader, string)); } public void parse(StringReader reader, ItemParser.Visitor visitor) throws CommandSyntaxException { int i = reader.getCursor(); try { new ItemParser.State(reader, visitor).parse(); } catch (CommandSyntaxException var5) { reader.setCursor(i); throw var5; } } public CompletableFuture fillSuggestions(SuggestionsBuilder builder) { StringReader stringReader = new StringReader(builder.getInput()); stringReader.setCursor(builder.getStart()); ItemParser.SuggestionsVisitor suggestionsVisitor = new ItemParser.SuggestionsVisitor(); ItemParser.State state = new ItemParser.State(stringReader, suggestionsVisitor); try { state.parse(); } catch (CommandSyntaxException var6) { } return suggestionsVisitor.resolveSuggestions(builder, stringReader); } public record ItemResult(Holder item, DataComponentPatch components) { } class State { private final StringReader reader; private final ItemParser.Visitor visitor; State(final StringReader reader, final ItemParser.Visitor visitor) { this.reader = reader; this.visitor = visitor; } public void parse() throws CommandSyntaxException { this.visitor.visitSuggestions(this::suggestItem); this.readItem(); this.visitor.visitSuggestions(this::suggestStartComponents); if (this.reader.canRead() && this.reader.peek() == '[') { this.visitor.visitSuggestions(ItemParser.SUGGEST_NOTHING); this.readComponents(); } } private void readItem() throws CommandSyntaxException { int i = this.reader.getCursor(); ResourceLocation resourceLocation = ResourceLocation.read(this.reader); this.visitor.visitItem((Holder)ItemParser.this.items.get(ResourceKey.create(Registries.ITEM, resourceLocation)).orElseThrow(() -> { this.reader.setCursor(i); return ItemParser.ERROR_UNKNOWN_ITEM.createWithContext(this.reader, resourceLocation); })); } private void readComponents() throws CommandSyntaxException { this.reader.expect('['); this.visitor.visitSuggestions(this::suggestComponentAssignmentOrRemoval); Set> set = new ReferenceArraySet<>(); while (this.reader.canRead() && this.reader.peek() != ']') { this.reader.skipWhitespace(); if (this.reader.canRead() && this.reader.peek() == '!') { this.reader.skip(); this.visitor.visitSuggestions(this::suggestComponent); DataComponentType dataComponentType = readComponentType(this.reader); if (!set.add(dataComponentType)) { throw ItemParser.ERROR_REPEATED_COMPONENT.create(dataComponentType); } this.visitor.visitRemovedComponent(dataComponentType); this.visitor.visitSuggestions(ItemParser.SUGGEST_NOTHING); this.reader.skipWhitespace(); } else { DataComponentType dataComponentType = readComponentType(this.reader); if (!set.add(dataComponentType)) { throw ItemParser.ERROR_REPEATED_COMPONENT.create(dataComponentType); } this.visitor.visitSuggestions(this::suggestAssignment); this.reader.skipWhitespace(); this.reader.expect('='); this.visitor.visitSuggestions(ItemParser.SUGGEST_NOTHING); this.reader.skipWhitespace(); this.readComponent(ItemParser.this.tagParser, ItemParser.this.registryOps, dataComponentType); this.reader.skipWhitespace(); } this.visitor.visitSuggestions(this::suggestNextOrEndComponents); if (!this.reader.canRead() || this.reader.peek() != ',') { break; } this.reader.skip(); this.reader.skipWhitespace(); this.visitor.visitSuggestions(this::suggestComponentAssignmentOrRemoval); if (!this.reader.canRead()) { throw ItemParser.ERROR_EXPECTED_COMPONENT.createWithContext(this.reader); } } this.reader.expect(']'); this.visitor.visitSuggestions(ItemParser.SUGGEST_NOTHING); } public static DataComponentType readComponentType(StringReader reader) throws CommandSyntaxException { if (!reader.canRead()) { throw ItemParser.ERROR_EXPECTED_COMPONENT.createWithContext(reader); } else { int i = reader.getCursor(); ResourceLocation resourceLocation = ResourceLocation.read(reader); DataComponentType dataComponentType = BuiltInRegistries.DATA_COMPONENT_TYPE.getValue(resourceLocation); if (dataComponentType != null && !dataComponentType.isTransient()) { return dataComponentType; } else { reader.setCursor(i); throw ItemParser.ERROR_UNKNOWN_COMPONENT.createWithContext(reader, resourceLocation); } } } private void readComponent(TagParser tagParser, RegistryOps ops, DataComponentType componentType) throws CommandSyntaxException { int i = this.reader.getCursor(); O object = tagParser.parseAsArgument(this.reader); DataResult dataResult = componentType.codecOrThrow().parse(ops, object); this.visitor.visitComponent(componentType, dataResult.getOrThrow(string -> { this.reader.setCursor(i); return ItemParser.ERROR_MALFORMED_COMPONENT.createWithContext(this.reader, componentType.toString(), string); })); } private CompletableFuture suggestStartComponents(SuggestionsBuilder builder) { if (builder.getRemaining().isEmpty()) { builder.suggest(String.valueOf('[')); } return builder.buildFuture(); } private CompletableFuture suggestNextOrEndComponents(SuggestionsBuilder builder) { if (builder.getRemaining().isEmpty()) { builder.suggest(String.valueOf(',')); builder.suggest(String.valueOf(']')); } return builder.buildFuture(); } private CompletableFuture suggestAssignment(SuggestionsBuilder builder) { if (builder.getRemaining().isEmpty()) { builder.suggest(String.valueOf('=')); } return builder.buildFuture(); } private CompletableFuture suggestItem(SuggestionsBuilder builder) { return SharedSuggestionProvider.suggestResource(ItemParser.this.items.listElementIds().map(ResourceKey::location), builder); } private CompletableFuture suggestComponentAssignmentOrRemoval(SuggestionsBuilder builder) { builder.suggest(String.valueOf('!')); return this.suggestComponent(builder, String.valueOf('=')); } private CompletableFuture suggestComponent(SuggestionsBuilder builder) { return this.suggestComponent(builder, ""); } private CompletableFuture suggestComponent(SuggestionsBuilder builder, String suffix) { String string = builder.getRemaining().toLowerCase(Locale.ROOT); SharedSuggestionProvider.filterResources( BuiltInRegistries.DATA_COMPONENT_TYPE.entrySet(), string, entry -> ((ResourceKey)entry.getKey()).location(), entry -> { DataComponentType dataComponentType = (DataComponentType)entry.getValue(); if (dataComponentType.codec() != null) { ResourceLocation resourceLocation = ((ResourceKey)entry.getKey()).location(); builder.suggest(resourceLocation + suffix); } } ); return builder.buildFuture(); } } static class SuggestionsVisitor implements ItemParser.Visitor { private Function> suggestions = ItemParser.SUGGEST_NOTHING; @Override public void visitSuggestions(Function> suggestions) { this.suggestions = suggestions; } public CompletableFuture resolveSuggestions(SuggestionsBuilder builder, StringReader reader) { return (CompletableFuture)this.suggestions.apply(builder.createOffset(reader.getCursor())); } } public interface Visitor { default void visitItem(Holder item) { } default void visitComponent(DataComponentType componentType, T value) { } default void visitRemovedComponent(DataComponentType componentType) { } default void visitSuggestions(Function> suggestions) { } } }