package net.minecraft.commands.arguments; import com.google.common.annotations.VisibleForTesting; 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.DynamicCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.serialization.Codec; import com.mojang.serialization.Dynamic; import com.mojang.serialization.DynamicOps; import java.util.Collection; import java.util.List; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; 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.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.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) ); private static final SimpleCommandExceptionType ERROR_INVALID = new SimpleCommandExceptionType(Component.translatable("argument.resource_or_id.invalid")); private static final TagParser VALUE_PARSER = TagParser.create(NbtOps.INSTANCE); private final HolderLookup.Provider registryLookup; private final boolean hasRegistry; private final Codec> codec; protected ResourceOrIdArgument(CommandBuildContext registryLookup, ResourceKey> registryKey, Codec> codec) { this.registryLookup = registryLookup; this.hasRegistry = registryLookup.lookup(registryKey).isPresent(); this.codec = codec; } 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); } 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, VALUE_PARSER); } @Nullable private Holder parse(StringReader reader, TagParser parser) throws CommandSyntaxException { RegistryOps registryOps = this.registryLookup.createSerializationContext(parser.getOps()); Dynamic dynamic = parseInlineOrId(registryOps, parser, reader); return !this.hasRegistry ? null : this.codec.parse(dynamic).getOrThrow(string -> ERROR_FAILED_TO_PARSE.createWithContext(reader, string)); } @VisibleForTesting static Dynamic parseInlineOrId(DynamicOps ops, TagParser tagParser, StringReader reader) throws CommandSyntaxException { int i = reader.getCursor(); T object = tagParser.parseAsArgument(reader); if (hasConsumedWholeArg(reader)) { return new Dynamic<>(ops, object); } else { reader.setCursor(i); ResourceLocation resourceLocation = ResourceLocation.read(reader); if (hasConsumedWholeArg(reader)) { return new Dynamic<>(ops, ops.createString(resourceLocation.toString())); } else { reader.setCursor(i); throw ERROR_INVALID.createWithContext(reader); } } } private static boolean hasConsumedWholeArg(StringReader reader) { return !reader.canRead() || reader.peek() == ' '; } @Override public Collection getExamples() { return EXAMPLES; } public static class LootModifierArgument extends ResourceOrIdArgument { protected LootModifierArgument(CommandBuildContext context) { super(context, Registries.ITEM_MODIFIER, LootItemFunctions.CODEC); } } public static class LootPredicateArgument extends ResourceOrIdArgument { protected LootPredicateArgument(CommandBuildContext context) { super(context, Registries.PREDICATE, LootItemCondition.CODEC); } } public static class LootTableArgument extends ResourceOrIdArgument { protected LootTableArgument(CommandBuildContext context) { super(context, Registries.LOOT_TABLE, LootTable.CODEC); } } }