package carpet.script.command; import carpet.script.CarpetScriptHost; import carpet.script.CarpetScriptServer; import carpet.script.argument.FunctionArgument; import carpet.script.external.Carpet; import carpet.script.external.Vanilla; import carpet.script.value.BlockValue; import carpet.script.value.BooleanValue; import carpet.script.value.EntityValue; import carpet.script.value.FormattedTextValue; import carpet.script.value.ListValue; import carpet.script.value.MapValue; import carpet.script.value.NBTSerializableValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.script.value.ValueConversions; import com.mojang.datafixers.util.Either; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import com.google.common.collect.Lists; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.ParsedCommandNode; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import net.minecraft.ChatFormatting; import net.minecraft.advancements.predicates.MinMaxBounds; import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.commands.arguments.AngleArgument; import net.minecraft.commands.arguments.TeamColorArgument; import net.minecraft.commands.arguments.CompoundTagArgument; import net.minecraft.commands.arguments.DimensionArgument; import net.minecraft.commands.arguments.EntityAnchorArgument; import net.minecraft.commands.arguments.GameProfileArgument; import net.minecraft.commands.arguments.MessageArgument; import net.minecraft.commands.arguments.NbtPathArgument; import net.minecraft.commands.arguments.NbtTagArgument; import net.minecraft.commands.arguments.ObjectiveArgument; import net.minecraft.commands.arguments.ObjectiveCriteriaArgument; import net.minecraft.commands.arguments.ParticleArgument; import net.minecraft.commands.arguments.RangeArgument; import net.minecraft.commands.arguments.ResourceArgument; import net.minecraft.commands.arguments.IdentifierArgument; import net.minecraft.commands.arguments.ResourceOrTagArgument; import net.minecraft.commands.arguments.ScoreHolderArgument; import net.minecraft.commands.arguments.ScoreboardSlotArgument; import net.minecraft.commands.arguments.TeamArgument; import net.minecraft.commands.arguments.TimeArgument; import net.minecraft.commands.arguments.UuidArgument; import net.minecraft.commands.arguments.blocks.BlockInput; import net.minecraft.commands.arguments.blocks.BlockPredicateArgument; import net.minecraft.commands.arguments.blocks.BlockStateArgument; import net.minecraft.commands.arguments.coordinates.ColumnPosArgument; import net.minecraft.commands.arguments.coordinates.RotationArgument; import net.minecraft.commands.arguments.coordinates.SwizzleArgument; import net.minecraft.commands.arguments.coordinates.Vec2Argument; import net.minecraft.commands.arguments.coordinates.Vec3Argument; import net.minecraft.commands.arguments.item.ItemArgument; import net.minecraft.commands.synchronization.SuggestionProviders; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; import net.minecraft.server.commands.BossBarCommands; import net.minecraft.server.commands.LootCommand; import net.minecraft.server.players.NameAndId; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.phys.Vec2; import net.minecraft.world.scores.ScoreHolder; import net.minecraft.world.scores.TeamColor; import org.jspecify.annotations.Nullable; import static net.minecraft.commands.Commands.argument; public abstract class CommandArgument { public static CommandSyntaxException error(String text) { return new SimpleCommandExceptionType(Component.literal(text)).create(); } private static final List baseTypes = Lists.newArrayList( // default new StringArgument(), // vanilla arguments as per https://minecraft.wiki/w/Argument_types new VanillaUnconfigurableArgument("bool", BoolArgumentType::bool, (c, p) -> BooleanValue.of(BoolArgumentType.getBool(c, p)), false ), new FloatArgument(), new IntArgument(), new WordArgument(), new GreedyStringArgument(), new VanillaUnconfigurableArgument("yaw", AngleArgument::angle, // angle (c, p) -> new NumericValue(AngleArgument.getAngle(c, p)), true ), new BlockPosArgument(), new VanillaUnconfigurableArgument("block", BlockStateArgument::block, (c, p) -> { BlockInput result = BlockStateArgument.getBlock(c, p); return new BlockValue(result.getState(), c.getSource().getLevel(), Vanilla.BlockInput_getTag(result)); }, param -> (ctx, builder) -> ctx.getArgument(param, BlockStateArgument.class).listSuggestions(ctx, builder) ), new VanillaUnconfigurableArgument("blockpredicate", BlockPredicateArgument::blockPredicate, (c, p) -> ValueConversions.ofBlockPredicate(c.getSource().getServer().registryAccess(), BlockPredicateArgument.getBlockPredicate(c, p)), param -> (ctx, builder) -> ctx.getArgument(param, BlockPredicateArgument.class).listSuggestions(ctx, builder) ), new VanillaUnconfigurableArgument("teamcolor", TeamColorArgument::teamColor, (c, p) -> { TeamColor format = TeamColorArgument.getTeamColor(c, p); return ListValue.of(StringValue.of(format.getSerializedName()), ValueConversions.ofRGB(format.rgb())); }, false ), new VanillaUnconfigurableArgument("columnpos", ColumnPosArgument::columnPos, (c, p) -> ValueConversions.of(ColumnPosArgument.getColumnPos(c, p)), false ), // component // raw json new VanillaUnconfigurableArgument("dimension", DimensionArgument::dimension, (c, p) -> ValueConversions.of(DimensionArgument.getDimension(c, p)), false ), new EntityArgument(), new VanillaUnconfigurableArgument("anchor", EntityAnchorArgument::anchor, (c, p) -> StringValue.of(EntityAnchorArgument.getAnchor(c, p).name()), false ), new VanillaUnconfigurableArgument("entitytype", c -> ResourceArgument.resource(c, Registries.ENTITY_TYPE), (c, p) -> ValueConversions.of(ResourceArgument.getSummonableEntityType(c, p).key()), SuggestionProviders.cast(SuggestionProviders.SUMMONABLE_ENTITIES) ), new VanillaUnconfigurableArgument("floatrange", RangeArgument::floatRange, (c, p) -> ValueConversions.of(c.getArgument(p, MinMaxBounds.Doubles.class)), true ), // function?? new PlayerProfileArgument(), new VanillaUnconfigurableArgument("intrange", RangeArgument::intRange, (c, p) -> ValueConversions.of(RangeArgument.Ints.getRange(c, p)), true ), new VanillaUnconfigurableArgument("enchantment", Registries.ENCHANTMENT), // item_predicate ?? //same as item but accepts tags, not sure right now new SlotArgument(), new VanillaUnconfigurableArgument("item", ItemArgument::item, (c, p) -> ValueConversions.of(ItemArgument.getItem(c, p).createItemStack(1), c.getSource().registryAccess()), param -> (ctx, builder) -> ctx.getArgument(param, ItemArgument.class).listSuggestions(ctx, builder) ), new VanillaUnconfigurableArgument("message", MessageArgument::message, (c, p) -> new FormattedTextValue(MessageArgument.getMessage(c, p)), true ), new VanillaUnconfigurableArgument("effect", Registries.MOB_EFFECT), new TagArgument(), // for nbt_compound_tag and nbt_tag new VanillaUnconfigurableArgument("path", NbtPathArgument::nbtPath, (c, p) -> StringValue.of(NbtPathArgument.getPath(c, p).toString()), true ), new VanillaUnconfigurableArgument("objective", ObjectiveArgument::objective, (c, p) -> ValueConversions.of(ObjectiveArgument.getObjective(c, p)), false ), new VanillaUnconfigurableArgument("criterion", ObjectiveCriteriaArgument::criteria, (c, p) -> StringValue.of(ObjectiveCriteriaArgument.getCriteria(c, p).getName()), false ), // operation // not sure if we need it, you have scarpet for that new VanillaUnconfigurableArgument("particle", ParticleArgument::particle, (c, p) -> ValueConversions.of(ParticleArgument.getParticle(c, p), c.getSource().registryAccess()), (c, b) -> SharedSuggestionProvider.suggestResource(c.getSource().getServer().registryAccess().lookupOrThrow(Registries.PARTICLE_TYPE).keySet(), b) ), // resource / identifier section new VanillaUnconfigurableArgument("recipe", Registries.RECIPE), new VanillaUnconfigurableArgument("advancement", Registries.ADVANCEMENT), new VanillaUnconfigurableArgument("lootcondition", Registries.LOOT_CONDITION_TYPE), new VanillaUnconfigurableArgument("loottable", Registries.LOOT_TABLE), new VanillaUnconfigurableArgument("attribute", Registries.ATTRIBUTE), new VanillaUnconfigurableArgument("boss", IdentifierArgument::id, (c, p) -> ValueConversions.of(IdentifierArgument.getId(c, p)), BossBarCommands.SUGGEST_BOSS_BAR ), new VanillaUnconfigurableArgument("biome", c -> ResourceOrTagArgument.resourceOrTag(c, Registries.BIOME), (c, p) -> { ResourceOrTagArgument.Result result = ResourceOrTagArgument.getResourceOrTag(c, "biome", Registries.BIOME); Either, HolderSet.Named> res = result.unwrap(); if (res.left().isPresent()) { return ValueConversions.of(res.left().get().key()); } if (res.right().isPresent()) { return ValueConversions.of(res.right().get().key()); } return Value.NULL; }, (ctx, builder) -> SharedSuggestionProvider.suggestResource(ctx.getSource().getServer().registryAccess().lookupOrThrow(Registries.BIOME).keySet(), builder) ), new VanillaUnconfigurableArgument("sound", IdentifierArgument::id, (c, p) -> ValueConversions.of(IdentifierArgument.getId(c, p)), SuggestionProviders.cast(SuggestionProviders.AVAILABLE_SOUNDS) ), new VanillaUnconfigurableArgument("storekey", IdentifierArgument::id, (c, p) -> ValueConversions.of(IdentifierArgument.getId(c, p)), (ctx, builder) -> SharedSuggestionProvider.suggestResource(ctx.getSource().getServer().getCommandStorage().keys(), builder) ), // default new CustomIdentifierArgument(), // end resource / identifier // I would be great if you guys have suggestions for that. new VanillaUnconfigurableArgument("rotation", RotationArgument::rotation, (c, p) -> { Vec2 rot = RotationArgument.getRotation(c, p).getRotation(c.getSource()); return ListValue.of(new NumericValue(rot.x), new NumericValue(rot.y)); }, true ), new ScoreholderArgument(), new VanillaUnconfigurableArgument("scoreboardslot", ScoreboardSlotArgument::displaySlot, (c, p) -> StringValue.of(ScoreboardSlotArgument.getDisplaySlot(c, p).getSerializedName()), false ), new VanillaUnconfigurableArgument("swizzle", SwizzleArgument::swizzle, (c, p) -> StringValue.of(SwizzleArgument.getSwizzle(c, p).stream().map(Direction.Axis::getSerializedName).collect(Collectors.joining())), true ), new VanillaUnconfigurableArgument("team", TeamArgument::team, (c, p) -> StringValue.of(TeamArgument.getTeam(c, p).getName()), false ), new VanillaUnconfigurableArgument("time", TimeArgument::time, (c, p) -> new NumericValue(IntegerArgumentType.getInteger(c, p)), false ), new VanillaUnconfigurableArgument("uuid", UuidArgument::uuid, (c, p) -> StringValue.of(UuidArgument.getUuid(c, p).toString()), false ), new VanillaUnconfigurableArgument("surfacelocation", Vec2Argument::vec2, (c, p) -> { Vec2 res = Vec2Argument.getVec2(c, p); return ListValue.of(NumericValue.of(res.x), NumericValue.of(res.y)); }, false ), new LocationArgument() ); public static final Map builtIns = baseTypes.stream().collect(Collectors.toMap(CommandArgument::getTypeSuffix, a -> a)); public static final CommandArgument DEFAULT = baseTypes.get(0); public static CommandArgument getTypeForArgument(String argument, CarpetScriptHost host) { String[] components = argument.split("_"); CommandArgument arg; for (int i = 0; i < components.length; i++) { String candidate = String.join("_", Arrays.asList(components).subList(i, components.length)); arg = host.appArgTypes.get(candidate); if (arg != null) { return arg; } arg = builtIns.get(candidate); if (arg != null) { return arg; } } return DEFAULT; } public static RequiredArgumentBuilder argumentNode(String param, CarpetScriptHost host) throws CommandSyntaxException { CommandArgument arg = getTypeForArgument(param, host); if (arg.suggestionProvider != null) { return argument(param, arg.getArgumentType(host)).suggests(arg.suggestionProvider.apply(param)); } if (!arg.needsMatching) { return argument(param, arg.getArgumentType(host)); } String hostName = host.getName(); CarpetScriptServer scriptServer = host.scriptServer(); return argument(param, arg.getArgumentType(host)).suggests((ctx, b) -> { CarpetScriptHost cHost = scriptServer.modules.get(hostName).retrieveOwnForExecution(ctx.getSource()); return arg.suggest(ctx, b, cHost); }); } protected String suffix; @Nullable protected Collection examples; protected boolean needsMatching; protected boolean caseSensitive = true; protected Function> suggestionProvider; protected FunctionArgument customSuggester; protected CommandArgument( String suffix, @Nullable Collection examples, boolean suggestFromExamples) { this.suffix = suffix; this.examples = examples; this.needsMatching = suggestFromExamples; } protected abstract ArgumentType getArgumentType(CarpetScriptHost host) throws CommandSyntaxException; public static Value getValue(CommandContext context, String param, CarpetScriptHost host) throws CommandSyntaxException { return getTypeForArgument(param, host).getValueFromContext(context, param); } protected abstract Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException; public String getTypeSuffix() { return suffix; } public static CommandArgument buildFromConfig(String suffix, Map config, CarpetScriptHost host) throws CommandSyntaxException { if (!config.containsKey("type")) { throw CommandArgument.error("Custom type " + suffix + " should at least specify the type"); } String baseType = config.get("type").getString(); if (!builtIns.containsKey(baseType)) { throw CommandArgument.error("Unknown base type " + baseType + " for custom type " + suffix); } CommandArgument variant = builtIns.get(baseType).factory(host.scriptServer().server).get(); variant.suffix = suffix; variant.configure(config, host); return variant; } protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { caseSensitive = config.getOrDefault("case_sensitive", Value.TRUE).getBoolean(); if (config.containsKey("suggester")) { customSuggester = FunctionArgument.fromCommandSpec(host, config.get("suggester")); } if (config.containsKey("suggest")) { if (config.containsKey("suggester")) { throw error("Attempted to provide 'suggest' list while 'suggester' is present" + " for custom type " + suffix); } Value suggestionValue = config.get("suggest"); if (!(suggestionValue instanceof ListValue)) { throw error("Argument suggestions needs to be a list" + " for custom type " + suffix); } examples = ((ListValue) suggestionValue).getItems().stream() .map(Value::getString) .collect(Collectors.toSet()); if (!examples.isEmpty()) { needsMatching = true; } } } public CompletableFuture suggest( CommandContext context, SuggestionsBuilder suggestionsBuilder, CarpetScriptHost host ) throws CommandSyntaxException { String prefix = suggestionsBuilder.getRemaining(); if (!caseSensitive) { prefix = prefix.toLowerCase(Locale.ROOT); } suggestFor(context, prefix, host).forEach(suggestionsBuilder::suggest); return suggestionsBuilder.buildFuture(); } protected List suggestFor(CommandContext context, String prefix, CarpetScriptHost host) throws CommandSyntaxException { return getOptions(context, host).stream().filter(s -> optionMatchesPrefix(prefix, s)).collect(Collectors.toList()); } protected Collection getOptions(CommandContext context, CarpetScriptHost host) throws CommandSyntaxException { if (customSuggester != null) { Runnable currentSection = Carpet.startProfilerSection("Scarpet command"); Map params = new HashMap<>(); for (ParsedCommandNode pnode : context.getNodes()) { CommandNode node = pnode.getNode(); if (node instanceof ArgumentCommandNode) { params.put(StringValue.of(node.getName()), CommandArgument.getValue(context, node.getName(), host)); } } List args = new ArrayList<>(customSuggester.args.size() + 1); args.add(MapValue.wrap(params)); args.addAll(customSuggester.args); Value response = host.handleCommand(context.getSource(), customSuggester.function, args); if (!(response instanceof ListValue)) { throw error("Custom suggester should return a list of options" + " for custom type " + suffix); } Collection res = ((ListValue) response).getItems().stream().map(Value::getString).collect(Collectors.toList()); currentSection.run(); return res; } return needsMatching ? examples : Collections.singletonList("... " + getTypeSuffix()); } protected boolean optionMatchesPrefix(String prefix, String option) { if (!caseSensitive) { option = option.toLowerCase(Locale.ROOT); } for (int i = 0; !option.startsWith(prefix, i); ++i) { i = option.indexOf('_', i); if (i < 0) { return false; } } return true; } protected abstract Supplier factory(MinecraftServer server); private static class StringArgument extends CommandArgument { Set validOptions = Collections.emptySet(); private StringArgument() { super("string", StringArgumentType.StringType.QUOTABLE_PHRASE.getExamples(), true); } @Override public ArgumentType getArgumentType(CarpetScriptHost host) { return StringArgumentType.string(); } @Override public Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException { String choseValue = StringArgumentType.getString(context, param); if (!caseSensitive) { choseValue = choseValue.toLowerCase(Locale.ROOT); } if (!validOptions.isEmpty() && !validOptions.contains(choseValue)) { throw new SimpleCommandExceptionType(Component.literal("Incorrect value for " + param + ": " + choseValue + " for custom type " + suffix)).create(); } return StringValue.of(choseValue); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); if (config.containsKey("options")) { Value optionsValue = config.get("options"); if (!(optionsValue instanceof ListValue)) { throw error("Custom string type requires options passed as a list" + " for custom type " + suffix); } validOptions = ((ListValue) optionsValue).getItems().stream() .map(v -> caseSensitive ? v.getString() : (v.getString().toLowerCase(Locale.ROOT))) .collect(Collectors.toSet()); } } @Override protected Collection getOptions(CommandContext context, CarpetScriptHost host) throws CommandSyntaxException { return validOptions.isEmpty() ? super.getOptions(context, host) : validOptions; } @Override protected Supplier factory(MinecraftServer server) { return StringArgument::new; } } private static class WordArgument extends StringArgument { private WordArgument() { super(); suffix = "term"; examples = StringArgumentType.StringType.SINGLE_WORD.getExamples(); } @Override public ArgumentType getArgumentType(CarpetScriptHost host) { return StringArgumentType.word(); } @Override protected Supplier factory(MinecraftServer server) { return WordArgument::new; } } private static class GreedyStringArgument extends StringArgument { private GreedyStringArgument() { super(); suffix = "text"; examples = StringArgumentType.StringType.GREEDY_PHRASE.getExamples(); } @Override public ArgumentType getArgumentType(CarpetScriptHost host) { return StringArgumentType.greedyString(); } @Override protected Supplier factory(MinecraftServer server) { return GreedyStringArgument::new; } } private static class BlockPosArgument extends CommandArgument { private boolean mustBeLoaded = false; private BlockPosArgument() { super("pos", net.minecraft.commands.arguments.coordinates.BlockPosArgument.blockPos().getExamples(), false); } @Override public ArgumentType getArgumentType(CarpetScriptHost host) { return net.minecraft.commands.arguments.coordinates.BlockPosArgument.blockPos(); } @Override public Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException { BlockPos pos = mustBeLoaded ? net.minecraft.commands.arguments.coordinates.BlockPosArgument.getLoadedBlockPos(context, param) : net.minecraft.commands.arguments.coordinates.BlockPosArgument.getSpawnablePos(context, param); return ValueConversions.of(pos); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); mustBeLoaded = config.getOrDefault("loaded", Value.FALSE).getBoolean(); } @Override protected Supplier factory(MinecraftServer server) { return BlockPosArgument::new; } } private static class LocationArgument extends CommandArgument { boolean blockCentered; private LocationArgument() { super("location", Vec3Argument.vec3().getExamples(), false); blockCentered = true; } @Override protected ArgumentType getArgumentType(CarpetScriptHost host) { return Vec3Argument.vec3(blockCentered); } @Override protected Value getValueFromContext(CommandContext context, String param) { return ValueConversions.of(Vec3Argument.getVec3(context, param)); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); blockCentered = config.getOrDefault("block_centered", Value.TRUE).getBoolean(); } @Override protected Supplier factory(MinecraftServer server) { return LocationArgument::new; } } private static class EntityArgument extends CommandArgument { boolean onlyFans; boolean single; private EntityArgument() { super("entities", net.minecraft.commands.arguments.EntityArgument.entities().getExamples(), false); onlyFans = false; single = false; } @Override protected ArgumentType getArgumentType(CarpetScriptHost host) { if (onlyFans) { return single ? net.minecraft.commands.arguments.EntityArgument.player() : net.minecraft.commands.arguments.EntityArgument.players(); } return single ? net.minecraft.commands.arguments.EntityArgument.entity() : net.minecraft.commands.arguments.EntityArgument.entities(); } @Override protected Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException { Collection founds = net.minecraft.commands.arguments.EntityArgument.getOptionalEntities(context, param); if (!single) { return ListValue.wrap(founds.stream().map(EntityValue::new)); } if (founds.isEmpty()) { return Value.NULL; } if (founds.size() == 1) { return new EntityValue(founds.iterator().next()); } throw new SimpleCommandExceptionType(Component.literal("Multiple entities returned while only one was requested" + " for custom type " + suffix)).create(); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); onlyFans = config.getOrDefault("players", Value.FALSE).getBoolean(); single = config.getOrDefault("single", Value.FALSE).getBoolean(); } @Override protected Supplier factory(MinecraftServer server) { return EntityArgument::new; } } private static class PlayerProfileArgument extends CommandArgument { boolean single; private PlayerProfileArgument() { super("players", GameProfileArgument.gameProfile().getExamples(), false); single = false; } @Override protected ArgumentType getArgumentType(CarpetScriptHost host) { return GameProfileArgument.gameProfile(); } @Override protected Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException { Collection profiles = GameProfileArgument.getGameProfiles(context, param); if (!single) { return ListValue.wrap(profiles.stream().map(p -> StringValue.of(p.name()))); } int size = profiles.size(); if (size == 0) { return Value.NULL; } if (size == 1) { return StringValue.of(profiles.iterator().next().name()); } throw new SimpleCommandExceptionType(Component.literal("Multiple game profiles returned while only one was requested" + " for custom type " + suffix)).create(); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); single = config.getOrDefault("single", Value.FALSE).getBoolean(); } @Override protected Supplier factory(MinecraftServer server) { return PlayerProfileArgument::new; } } private static class ScoreholderArgument extends CommandArgument { boolean single; private ScoreholderArgument() { super("scoreholder", ScoreHolderArgument.scoreHolder().getExamples(), false); single = false; suggestionProvider = param -> ScoreHolderArgument.SUGGEST_SCORE_HOLDERS; } @Override protected ArgumentType getArgumentType(CarpetScriptHost host) { return single ? ScoreHolderArgument.scoreHolder() : ScoreHolderArgument.scoreHolders(); } @Override protected Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException { Collection holders = ScoreHolderArgument.getNames(context, param); if (!single) { return ListValue.wrap(holders.stream().map(ValueConversions::of)); } int size = holders.size(); if (size == 0) { return Value.NULL; } if (size == 1) { return ValueConversions.of(holders.iterator().next()); } throw new SimpleCommandExceptionType(Component.literal("Multiple score holders returned while only one was requested" + " for custom type " + suffix)).create(); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); single = config.getOrDefault("single", Value.FALSE).getBoolean(); } @Override protected Supplier factory(MinecraftServer server) { return PlayerProfileArgument::new; } } private static class TagArgument extends CommandArgument { boolean mapRequired; private TagArgument() { super("tag", CompoundTagArgument.compoundTag().getExamples(), false); mapRequired = true; } @Override protected ArgumentType getArgumentType(CarpetScriptHost host) { return mapRequired ? CompoundTagArgument.compoundTag() : NbtTagArgument.nbtTag(); } @Override protected Value getValueFromContext(CommandContext context, String param) { return mapRequired ? new NBTSerializableValue(CompoundTagArgument.getCompoundTag(context, param)) : new NBTSerializableValue(NbtTagArgument.getNbtTag(context, param)); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); mapRequired = !config.getOrDefault("allow_element", Value.FALSE).getBoolean(); } @Override protected Supplier factory(MinecraftServer server) { return TagArgument::new; } } private static class CustomIdentifierArgument extends CommandArgument { Set validOptions = Collections.emptySet(); protected CustomIdentifierArgument() { super("identifier", Collections.emptyList(), true); } @Override protected ArgumentType getArgumentType(CarpetScriptHost host) { return IdentifierArgument.id(); } @Override protected Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException { Identifier choseValue = IdentifierArgument.getId(context, param); if (!validOptions.isEmpty() && !validOptions.contains(choseValue)) { throw new SimpleCommandExceptionType(Component.literal("Incorrect value for " + param + ": " + choseValue + " for custom type " + suffix)).create(); } return ValueConversions.of(choseValue); } @Override protected Supplier factory(MinecraftServer server) { return CustomIdentifierArgument::new; } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); if (config.containsKey("options")) { Value optionsValue = config.get("options"); if (!(optionsValue instanceof ListValue)) { throw error("Custom sting type requires options passed as a list" + " for custom type " + suffix); } validOptions = ((ListValue) optionsValue).getItems().stream().map(v -> Identifier.parse(v.getString())).collect(Collectors.toSet()); } } } private static class FloatArgument extends CommandArgument { private Double min = null; private Double max = null; private FloatArgument() { super("float", DoubleArgumentType.doubleArg().getExamples(), true); } @Override public ArgumentType getArgumentType(CarpetScriptHost host) { if (min != null) { if (max != null) { return DoubleArgumentType.doubleArg(min, max); } return DoubleArgumentType.doubleArg(min); } return DoubleArgumentType.doubleArg(); } @Override public Value getValueFromContext(CommandContext context, String param) { return new NumericValue(DoubleArgumentType.getDouble(context, param)); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); if (config.containsKey("min")) { min = NumericValue.asNumber(config.get("min"), "min").getDouble(); } if (config.containsKey("max")) { max = NumericValue.asNumber(config.get("max"), "max").getDouble(); } if (max != null && min == null) { throw error("Double types cannot be only upper-bounded" + " for custom type " + suffix); } } @Override protected Supplier factory(MinecraftServer server) { return FloatArgument::new; } } private static class IntArgument extends CommandArgument { private Long min = null; private Long max = null; private IntArgument() { super("int", LongArgumentType.longArg().getExamples(), true); } @Override public ArgumentType getArgumentType(CarpetScriptHost host) { if (min != null) { if (max != null) { return LongArgumentType.longArg(min, max); } return LongArgumentType.longArg(min); } return LongArgumentType.longArg(); } @Override public Value getValueFromContext(CommandContext context, String param) { return new NumericValue(LongArgumentType.getLong(context, param)); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); if (config.containsKey("min")) { min = NumericValue.asNumber(config.get("min"), "min").getLong(); } if (config.containsKey("max")) { max = NumericValue.asNumber(config.get("max"), "max").getLong(); } if (max != null && min == null) { throw error("Double types cannot be only upper-bounded" + " for custom type " + suffix); } } @Override protected Supplier factory(MinecraftServer server) { return IntArgument::new; } } private static class SlotArgument extends CommandArgument { private record ContainerIds(IntSet numericalIds, Set commandIds) { } private String restrict; private static final Map RESTRICTED_CONTAINERS = new HashMap<>() {{ int i; for (String source : Arrays.asList("player", "enderchest", "equipment", "armor", "weapon", "container", "villager", "horse")) { put(source, new ContainerIds(new IntOpenHashSet(), new HashSet<>())); } for (i = 0; i < 41; i++) { get("player").numericalIds().add(i); } for (i = 0; i < 41; i++) { get("player").commandIds().add("container." + i); } for (i = 0; i < 9; i++) { get("player").commandIds().add("hotbar." + i); } for (i = 0; i < 27; i++) { get("player").commandIds().add("inventory." + i); } for (String place : Arrays.asList("weapon", "weapon.mainhand", "weapon.offhand")) { get("player").commandIds().add(place); get("equipment").commandIds().add(place); get("weapon").commandIds().add(place); } for (String place : Arrays.asList("armor.feet", "armor.legs", "armor.chest", "armor.head")) { get("player").commandIds().add(place); get("equipment").commandIds().add(place); get("armor").commandIds().add(place); } for (i = 0; i < 27; i++) { get("enderchest").numericalIds().add(200 + i); } for (i = 0; i < 27; i++) { get("enderchest").commandIds().add("enderchest." + i); } for (i = 0; i < 6; i++) { get("equipment").numericalIds().add(98 + i); } for (i = 0; i < 4; i++) { get("armor").numericalIds().add(100 + i); } for (i = 0; i < 2; i++) { get("weapon").numericalIds().add(98 + i); } for (i = 0; i < 54; i++) { get("container").numericalIds().add(i); } for (i = 0; i < 41; i++) { get("container").commandIds().add("container." + i); } for (i = 0; i < 8; i++) { get("villager").numericalIds().add(i); } for (i = 0; i < 8; i++) { get("villager").commandIds().add("villager." + i); } for (i = 0; i < 15; i++) { get("horse").numericalIds().add(500 + i); } for (i = 0; i < 15; i++) { get("horse").commandIds().add("horse." + i); } get("horse").numericalIds().add(400); get("horse").commandIds().add("horse.saddle"); get("horse").numericalIds().add(401); get("horse").commandIds().add("horse.armor"); }}; protected SlotArgument() { super("slot", net.minecraft.commands.arguments.SlotArgument.slot().getExamples(), false); } @Override protected ArgumentType getArgumentType(CarpetScriptHost host) { return net.minecraft.commands.arguments.SlotArgument.slot(); } @Override protected Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException { int slot = net.minecraft.commands.arguments.SlotArgument.getSlot(context, param); if (restrict != null && !RESTRICTED_CONTAINERS.get(restrict).numericalIds().contains(slot)) { throw new SimpleCommandExceptionType(Component.literal("Incorrect slot restricted to " + restrict + " for custom type " + suffix)).create(); } return ValueConversions.ofVanillaSlotResult(slot); } @Override protected void configure(Map config, CarpetScriptHost host) throws CommandSyntaxException { super.configure(config, host); if (config.containsKey("restrict")) { restrict = config.get("restrict").getString().toLowerCase(Locale.ROOT); needsMatching = true; if (!RESTRICTED_CONTAINERS.containsKey(restrict)) { throw error("Incorrect slot restriction " + restrict + " for custom type " + suffix); } } } @Override protected Collection getOptions(CommandContext context, CarpetScriptHost host) throws CommandSyntaxException { return restrict == null ? super.getOptions(context, host) : RESTRICTED_CONTAINERS.get(restrict).commandIds(); } @Override protected Supplier factory(MinecraftServer server) { return SlotArgument::new; } } @FunctionalInterface private interface ValueExtractor { Value apply(CommandContext ctx, String param) throws CommandSyntaxException; } @FunctionalInterface private interface ArgumentProvider { ArgumentType get() throws CommandSyntaxException; } @FunctionalInterface private interface ArgumentProviderEx { ArgumentType get(CommandBuildContext regAccess) throws CommandSyntaxException; } public static class VanillaUnconfigurableArgument extends CommandArgument { private final ArgumentProvider argumentTypeSupplier; private final ArgumentProviderEx argumentTypeSupplierEx; private final ValueExtractor valueExtractor; private final boolean providesExamples; public VanillaUnconfigurableArgument( String suffix, ArgumentProvider argumentTypeSupplier, ValueExtractor valueExtractor, boolean suggestFromExamples ) { super(suffix, null, suggestFromExamples); try { this.examples = argumentTypeSupplier.get().getExamples(); } catch (CommandSyntaxException e) { this.examples = Collections.emptyList(); } this.providesExamples = suggestFromExamples; this.argumentTypeSupplier = argumentTypeSupplier; this.valueExtractor = valueExtractor; this.argumentTypeSupplierEx = null; } public VanillaUnconfigurableArgument( String suffix, ArgumentProvider argumentTypeSupplier, ValueExtractor valueExtractor, SuggestionProvider suggester ) { super(suffix, Collections.emptyList(), false); this.suggestionProvider = param -> suggester; this.providesExamples = false; this.argumentTypeSupplier = argumentTypeSupplier; this.valueExtractor = valueExtractor; this.argumentTypeSupplierEx = null; } public VanillaUnconfigurableArgument( String suffix, ArgumentProviderEx argumentTypeSupplier, ValueExtractor valueExtractor, boolean suggestFromExamples, MinecraftServer server) { super(suffix, null, suggestFromExamples); try { CommandBuildContext context = CommandBuildContext.simple(server.registryAccess(), server.getWorldData().enabledFeatures()); this.examples = argumentTypeSupplier.get(context).getExamples(); } catch (CommandSyntaxException e) { this.examples = Collections.emptyList(); } this.providesExamples = suggestFromExamples; this.argumentTypeSupplierEx = argumentTypeSupplier; this.valueExtractor = valueExtractor; this.argumentTypeSupplier = null; } public VanillaUnconfigurableArgument( String suffix, ArgumentProviderEx argumentTypeSupplier, ValueExtractor valueExtractor, SuggestionProvider suggester ) { super(suffix, Collections.emptyList(), false); this.suggestionProvider = param -> suggester; this.providesExamples = false; this.argumentTypeSupplierEx = argumentTypeSupplier; this.valueExtractor = valueExtractor; this.argumentTypeSupplier = null; } public VanillaUnconfigurableArgument( String suffix, ArgumentProviderEx argumentTypeSupplier, ValueExtractor valueExtractor, Function> suggesterGen ) { super(suffix, Collections.emptyList(), false); this.suggestionProvider = suggesterGen; this.providesExamples = false; this.argumentTypeSupplierEx = argumentTypeSupplier; this.valueExtractor = valueExtractor; this.argumentTypeSupplier = null; } public VanillaUnconfigurableArgument( String suffix, ResourceKey> registry ) { this( suffix, c -> ResourceArgument.resource(c, registry), (c, p) -> ValueConversions.of(ResourceArgument.getResource(c, p, registry).key()), (c, b) -> SharedSuggestionProvider.suggestResource(c.getSource().getServer().registryAccess().lookupOrThrow(registry).keySet(), b) ); } @Override protected ArgumentType getArgumentType(CarpetScriptHost host) throws CommandSyntaxException { return argumentTypeSupplier != null ? argumentTypeSupplier.get() : argumentTypeSupplierEx.get(CommandBuildContext.simple(host.scriptServer().server.registryAccess(), host.scriptServer().server.getWorldData().enabledFeatures())); } @Override protected Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException { return valueExtractor.apply(context, param); } @Override protected Supplier factory(MinecraftServer server) { return argumentTypeSupplier != null ? (() -> new VanillaUnconfigurableArgument(getTypeSuffix(), argumentTypeSupplier, valueExtractor, providesExamples)) : (() -> new VanillaUnconfigurableArgument(getTypeSuffix(), argumentTypeSupplierEx, valueExtractor, providesExamples, server)); } } }