package net.minecraft.commands.arguments; import com.mojang.brigadier.ImmutableStringReader; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.serialization.Codec; import com.mojang.serialization.DynamicOps; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.SnbtGrammar; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.dialog.Dialog; import net.minecraft.util.parsing.packrat.Atom; import net.minecraft.util.parsing.packrat.Dictionary; import net.minecraft.util.parsing.packrat.NamedRule; import net.minecraft.util.parsing.packrat.Term; import net.minecraft.util.parsing.packrat.commands.Grammar; import net.minecraft.util.parsing.packrat.commands.ResourceLocationParseRule; import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.level.storage.loot.functions.LootItemFunction; import net.minecraft.world.level.storage.loot.functions.LootItemFunctions; import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; import org.jetbrains.annotations.Nullable; public class ResourceOrIdArgument implements ArgumentType> { private static final Collection EXAMPLES = List.of("foo", "foo:bar", "012", "{}", "true"); public static final DynamicCommandExceptionType ERROR_FAILED_TO_PARSE = new DynamicCommandExceptionType( object -> Component.translatableEscape("argument.resource_or_id.failed_to_parse", object) ); public static final Dynamic2CommandExceptionType ERROR_NO_SUCH_ELEMENT = new Dynamic2CommandExceptionType( (object, object2) -> Component.translatableEscape("argument.resource_or_id.no_such_element", object, object2) ); public static final DynamicOps OPS = NbtOps.INSTANCE; private final HolderLookup.Provider registryLookup; private final Optional> elementLookup; private final Codec codec; private final Grammar> grammar; private final ResourceKey> registryKey; protected ResourceOrIdArgument(CommandBuildContext registryLookup, ResourceKey> registryKey, Codec codec) { this.registryLookup = registryLookup; this.elementLookup = registryLookup.lookup(registryKey); this.registryKey = registryKey; this.codec = codec; this.grammar = createGrammar(registryKey, OPS); } public static Grammar> createGrammar(ResourceKey> registryKey, DynamicOps ops) { Grammar grammar = SnbtGrammar.createParser(ops); Dictionary dictionary = new Dictionary<>(); Atom> atom = Atom.of("result"); Atom atom2 = Atom.of("id"); Atom atom3 = Atom.of("value"); dictionary.put(atom2, ResourceLocationParseRule.INSTANCE); dictionary.put(atom3, grammar.top().value()); NamedRule> namedRule = dictionary.put( atom, Term.alternative(dictionary.named(atom2), dictionary.named(atom3)), scope -> { ResourceLocation resourceLocation = scope.get(atom2); if (resourceLocation != null) { return new ResourceOrIdArgument.ReferenceResult<>(ResourceKey.create(registryKey, resourceLocation)); } else { O object = scope.getOrThrow(atom3); return new ResourceOrIdArgument.InlineResult<>(object); } } ); return new Grammar<>(dictionary, namedRule); } public static ResourceOrIdArgument.LootTableArgument lootTable(CommandBuildContext context) { return new ResourceOrIdArgument.LootTableArgument(context); } public static Holder getLootTable(CommandContext context, String name) throws CommandSyntaxException { return getResource(context, name); } public static ResourceOrIdArgument.LootModifierArgument lootModifier(CommandBuildContext context) { return new ResourceOrIdArgument.LootModifierArgument(context); } public static Holder getLootModifier(CommandContext context, String name) { return getResource(context, name); } public static ResourceOrIdArgument.LootPredicateArgument lootPredicate(CommandBuildContext context) { return new ResourceOrIdArgument.LootPredicateArgument(context); } public static Holder getLootPredicate(CommandContext context, String name) { return getResource(context, name); } public static ResourceOrIdArgument.DialogArgument dialog(CommandBuildContext context) { return new ResourceOrIdArgument.DialogArgument(context); } public static Holder getDialog(CommandContext context, String name) { return getResource(context, name); } private static Holder getResource(CommandContext context, String name) { return context.getArgument(name, Holder.class); } @Nullable public Holder parse(StringReader reader) throws CommandSyntaxException { return this.parse(reader, this.grammar, OPS); } @Nullable private Holder parse(StringReader reader, Grammar> grammar, DynamicOps ops) throws CommandSyntaxException { ResourceOrIdArgument.Result result = grammar.parseForCommands(reader); return this.elementLookup.isEmpty() ? null : result.parse(reader, this.registryLookup, ops, this.codec, (HolderLookup.RegistryLookup)this.elementLookup.get()); } @Override public CompletableFuture listSuggestions(CommandContext commandContext, SuggestionsBuilder suggestionsBuilder) { return SharedSuggestionProvider.listSuggestions(commandContext, suggestionsBuilder, this.registryKey, SharedSuggestionProvider.ElementSuggestionType.ELEMENTS); } @Override public Collection getExamples() { return EXAMPLES; } public static class DialogArgument extends ResourceOrIdArgument { protected DialogArgument(CommandBuildContext context) { super(context, Registries.DIALOG, Dialog.DIRECT_CODEC); } } public record InlineResult(O value) implements ResourceOrIdArgument.Result { @Override public Holder parse( ImmutableStringReader reader, HolderLookup.Provider registryLookup, DynamicOps ops, Codec codec, HolderLookup.RegistryLookup elementLookup ) throws CommandSyntaxException { return Holder.direct( codec.parse(registryLookup.createSerializationContext(ops), this.value) .getOrThrow(string -> ResourceOrIdArgument.ERROR_FAILED_TO_PARSE.createWithContext(reader, string)) ); } } public static class LootModifierArgument extends ResourceOrIdArgument { protected LootModifierArgument(CommandBuildContext context) { super(context, Registries.ITEM_MODIFIER, LootItemFunctions.ROOT_CODEC); } } public static class LootPredicateArgument extends ResourceOrIdArgument { protected LootPredicateArgument(CommandBuildContext context) { super(context, Registries.PREDICATE, LootItemCondition.DIRECT_CODEC); } } public static class LootTableArgument extends ResourceOrIdArgument { protected LootTableArgument(CommandBuildContext context) { super(context, Registries.LOOT_TABLE, LootTable.DIRECT_CODEC); } } public record ReferenceResult(ResourceKey key) implements ResourceOrIdArgument.Result { @Override public Holder parse( ImmutableStringReader reader, HolderLookup.Provider registryLookup, DynamicOps ops, Codec codec, HolderLookup.RegistryLookup elementLookup ) throws CommandSyntaxException { return (Holder)elementLookup.get(this.key) .orElseThrow(() -> ResourceOrIdArgument.ERROR_NO_SUCH_ELEMENT.createWithContext(reader, this.key.location(), this.key.registry())); } } public sealed interface Result permits ResourceOrIdArgument.InlineResult, ResourceOrIdArgument.ReferenceResult { Holder parse( ImmutableStringReader reader, HolderLookup.Provider registryLookup, DynamicOps ops, Codec codec, HolderLookup.RegistryLookup elementLookup ) throws CommandSyntaxException; } }