From 91b433c14ba8f1a19e8df98d80fc4a1dba09d05b Mon Sep 17 00:00:00 2001 From: Zach Levis Date: Tue, 5 Jan 2021 12:20:48 -0800 Subject: [PATCH] fabric: Fully implement a registry entry argument type --- .../arguments/CommandArgument.java | 4 + .../parser/MappedArgumentParser.java | 11 +- .../fabric/FabricCaptionKeys.java | 60 ++++ .../fabric/FabricCaptionRegistry.java | 41 +++ .../fabric/FabricCommandContextKeys.java | 22 ++ .../fabric/FabricCommandManager.java | 116 ++++++- .../fabric/FabricCommandPreprocessor.java | 47 +++ .../fabric/argument/EnchantmentArgument.java | 130 ------- .../argument/FabricArgumentParsers.java | 29 ++ .../argument/RegistryEntryArgument.java | 327 ++++++++++++++++++ 10 files changed, 649 insertions(+), 138 deletions(-) create mode 100644 cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCaptionKeys.java create mode 100644 cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCaptionRegistry.java create mode 100644 cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandContextKeys.java create mode 100644 cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandPreprocessor.java delete mode 100644 cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/EnchantmentArgument.java create mode 100644 cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/FabricArgumentParsers.java create mode 100644 cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/RegistryEntryArgument.java diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/CommandArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/CommandArgument.java index 16ab4575..e2cb6a6a 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/CommandArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/CommandArgument.java @@ -739,6 +739,10 @@ public class CommandArgument implements Comparable>, return this.defaultDescription; } + protected final @NonNull TypeToken getValueType() { + return this.valueType; + } + } /** diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/MappedArgumentParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/MappedArgumentParser.java index ccca7b6a..86e95b3c 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/MappedArgumentParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/MappedArgumentParser.java @@ -32,7 +32,7 @@ import java.util.List; import java.util.Queue; import java.util.function.BiFunction; -final class MappedArgumentParser implements ArgumentParser { +public final class MappedArgumentParser implements ArgumentParser { private final ArgumentParser base; private final BiFunction, I, ArgumentParseResult> mapper; @@ -44,6 +44,15 @@ final class MappedArgumentParser implements ArgumentParser { this.mapper = mapper; } + /** + * Get the parser this one is derived from. + * + * @return the base parser + */ + public ArgumentParser getBaseParser() { + return this.base; + } + @Override public @NonNull ArgumentParseResult<@NonNull O> parse( @NonNull final CommandContext<@NonNull C> commandContext, diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCaptionKeys.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCaptionKeys.java new file mode 100644 index 00000000..4aab6b05 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCaptionKeys.java @@ -0,0 +1,60 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +package cloud.commandframework.fabric; + +import cloud.commandframework.captions.Caption; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +public final class FabricCaptionKeys { + + private FabricCaptionKeys() { + } + + private static final Collection RECOGNIZED_CAPTIONS = new HashSet<>(); + + public static final Caption ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY = of( + "argument.parse.failure.registry_entry.unknown_entry" + ); + + private static @NonNull Caption of(final @NonNull String key) { + final Caption caption = Caption.of(key); + RECOGNIZED_CAPTIONS.add(caption); + return caption; + } + + /** + * Get an immutable collection containing all standard caption keys + * + * @return Immutable collection of keys + */ + public static @NonNull Collection<@NonNull Caption> getFabricCaptionKeys() { + return Collections.unmodifiableCollection(RECOGNIZED_CAPTIONS); + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCaptionRegistry.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCaptionRegistry.java new file mode 100644 index 00000000..bfacada8 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCaptionRegistry.java @@ -0,0 +1,41 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +package cloud.commandframework.fabric; + +import cloud.commandframework.captions.SimpleCaptionRegistry; + +public class FabricCaptionRegistry extends SimpleCaptionRegistry { + + public static final String ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY = "Could not find key {id} in registry '{registry}'"; + + protected FabricCaptionRegistry() { + super(); + + this.registerMessageFactory(FabricCaptionKeys.ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY, + (caption, sender) -> ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY); + } + + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandContextKeys.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandContextKeys.java new file mode 100644 index 00000000..bd9cbb22 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandContextKeys.java @@ -0,0 +1,22 @@ +package cloud.commandframework.fabric; + +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.keys.CloudKey; +import cloud.commandframework.keys.SimpleCloudKey; +import io.leangen.geantyref.TypeToken; +import net.minecraft.command.CommandSource; + +/** + * Keys used in {@link CommandContext}s available within a {@link FabricCommandManager} + */ +public final class FabricCommandContextKeys { + + private FabricCommandContextKeys() { + } + + public static final CloudKey NATIVE_COMMAND_SOURCE = SimpleCloudKey.of( + "cloud:fabric_command_source", + TypeToken.get(CommandSource.class) + ); + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java index bd1f9fc5..d833367c 100644 --- a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java @@ -33,15 +33,21 @@ import cloud.commandframework.brigadier.argument.WrappedBrigadierParser; import cloud.commandframework.context.CommandContext; import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator; import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.fabric.argument.RegistryEntryArgument; import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.SimpleCommandMeta; import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.serialization.Codec; +import io.leangen.geantyref.GenericTypeReflector; import io.leangen.geantyref.TypeToken; import net.minecraft.command.CommandSource; import net.minecraft.command.argument.AngleArgumentType; import net.minecraft.command.argument.BlockPredicateArgumentType; import net.minecraft.command.argument.ColorArgumentType; +import net.minecraft.command.argument.DimensionArgumentType; import net.minecraft.command.argument.EntityAnchorArgumentType; +import net.minecraft.command.argument.EntitySummonArgumentType; import net.minecraft.command.argument.IdentifierArgumentType; import net.minecraft.command.argument.ItemEnchantmentArgumentType; import net.minecraft.command.argument.ItemStackArgument; @@ -57,8 +63,7 @@ import net.minecraft.command.argument.OperationArgumentType; import net.minecraft.command.argument.ParticleArgumentType; import net.minecraft.command.argument.SwizzleArgumentType; import net.minecraft.command.argument.UuidArgumentType; -import net.minecraft.enchantment.Enchantment; -import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.command.suggestion.SuggestionProviders; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.particle.ParticleEffect; @@ -68,9 +73,20 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.math.Direction; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.NonNull; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -89,6 +105,8 @@ import java.util.function.Supplier; * @since 1.4.0 */ public abstract class FabricCommandManager extends CommandManager implements BrigadierManagerHolder { + private static final Logger LOGGER = LogManager.getLogger(); + private static final int MOD_PUBLIC_STATIC_FINAL = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL; private final Function commandSourceMapper; private final Function backwardsCommandSourceMapper; @@ -132,13 +150,16 @@ public abstract class FabricCommandManager extends C )); this.brigadierManager.backwardsBrigadierSenderMapper(this.backwardsCommandSourceMapper); this.registerNativeBrigadierMappings(this.brigadierManager); + this.setCaptionRegistry(new FabricCaptionRegistry<>()); + this.registerCommandPreProcessor(new FabricCommandPreprocessor<>(this)); ((FabricCommandRegistrationHandler) this.getCommandRegistrationHandler()).initialize(this); } private void registerNativeBrigadierMappings(final CloudBrigadierManager brigadier) { /* Cloud-native argument types */ - brigadier.registerMapping(new TypeToken>() {}, false, cloud -> UuidArgumentType.uuid()); + brigadier.registerMapping(new TypeToken>() {}, builder -> builder.toConstant(UuidArgumentType.uuid())); + this.registerRegistryEntryMappings(); /* Wrapped/Constant Brigadier types, native value type */ this.registerConstantNativeParserSupplier(Formatting.class, ColorArgumentType.color()); @@ -151,11 +172,9 @@ public abstract class FabricCommandManager extends C this.registerConstantNativeParserSupplier(AngleArgumentType.Angle.class, AngleArgumentType.angle()); this.registerConstantNativeParserSupplier(new TypeToken>() {}, SwizzleArgumentType.swizzle()); this.registerConstantNativeParserSupplier(Identifier.class, IdentifierArgumentType.identifier()); - this.registerConstantNativeParserSupplier(StatusEffect.class, MobEffectArgumentType.mobEffect()); this.registerConstantNativeParserSupplier(EntityAnchorArgumentType.EntityAnchor.class, EntityAnchorArgumentType.entityAnchor()); this.registerConstantNativeParserSupplier(NumberRange.IntRange.class, NumberRangeArgumentType.numberRange()); this.registerConstantNativeParserSupplier(NumberRange.FloatRange.class, NumberRangeArgumentType.method_30918()); - this.registerConstantNativeParserSupplier(Enchantment.class, ItemEnchantmentArgumentType.itemEnchantment()); // todo: can we add a compound argument -- MC `ItemStackArgument` is just type and tag, and count is separate this.registerConstantNativeParserSupplier(ItemStackArgument.class, ItemStackArgumentType.itemStack()); @@ -176,8 +195,6 @@ public abstract class FabricCommandManager extends C this.registerConstantNativeParserSupplier(Team.class, TeamArgumentType.team()); this.registerConstantNativeParserSupplier(/* slot *, ItemSlotArgumentType.itemSlot()); this.registerConstantNativeParserSupplier(CommandFunction.class, FunctionArgumentType.function()); - this.registerConstantNativeParserSupplier(EntityType.class, EntitySummonArgumentType.entitySummon()); // entity summon - this.registerConstantNativeParserSupplier(ServerWorld.class, DimensionArgumentType.dimension()); this.registerConstantNativeParserSupplier(/* time representation in ticks *, TimeArgumentType.time());*/ /* Wrapped brigadier requiring parameters */ @@ -185,6 +202,91 @@ public abstract class FabricCommandManager extends C // entity argument type: single or multiple, players or any entity -- returns EntitySelector, but do we want that? } + @SuppressWarnings({"unchecked", "rawtypes"}) + private void registerRegistryEntryMappings() { + this.brigadierManager.registerMapping(new TypeToken>() {}, + builder -> builder.to(argument -> { + /* several registries have specialized argument types, so let's use those where possible */ + final RegistryKey> registry = argument.getRegistry(); + if (registry.equals(Registry.ENTITY_TYPE_KEY)) { + return EntitySummonArgumentType.entitySummon(); + } else if (registry.equals(Registry.ENCHANTMENT_KEY)) { + return ItemEnchantmentArgumentType.itemEnchantment(); + } else if (registry.equals(Registry.MOB_EFFECT_KEY)) { // yarn wai + return MobEffectArgumentType.mobEffect(); + } else if (registry.equals(Registry.DIMENSION)) { + return DimensionArgumentType.dimension(); + } + return IdentifierArgumentType.identifier(); + } + ).suggestedBy((argument, useCloud) -> { + /* A few other registries have client-side suggestion providers but no argument type */ + /* Type parameters are messed up here for some reason */ + final RegistryKey> registry = argument.getRegistry(); + if (registry.equals(Registry.SOUND_EVENT_KEY)) { + return (SuggestionProvider) SuggestionProviders.AVAILABLE_SOUNDS; + } else if (registry.equals(Registry.BIOME_KEY)) { + return (SuggestionProvider) SuggestionProviders.ALL_BIOMES; + } else if (registry.equals(Registry.ENTITY_TYPE_KEY) + || registry.equals(Registry.ENCHANTMENT_KEY) + || registry.equals(Registry.MOB_EFFECT_KEY) + || registry.equals(Registry.DIMENSION)) { + return null; /* for types with their own argument type, use Brigadier */ + } + return useCloud; /* use cloud suggestions for anything else */ + }) + ); + + /* Find all fields of RegistryKey> and register those */ + /* This only works for vanilla registries really, we'll have to do other things for non-vanilla ones */ + final Set> seenClasses = new HashSet<>(); + /* Some registries have types that are too generic... we'll skip those for now. + * Eventually, these could be resolved by using ParserParameters in some way? */ + seenClasses.add(Identifier.class); + seenClasses.add(Codec.class); + for (final Field field : Registry.class.getDeclaredFields()) { + if ((field.getModifiers() & MOD_PUBLIC_STATIC_FINAL) != MOD_PUBLIC_STATIC_FINAL) { + continue; + } + if (!field.getType().equals(RegistryKey.class)) { + continue; + } + + final Type generic = field.getGenericType(); /* RegistryKey> */ + if (!(generic instanceof ParameterizedType)) { + continue; + } + + Type registryType = ((ParameterizedType) generic).getActualTypeArguments()[0]; + while (registryType instanceof WildcardType) { + registryType = ((WildcardType) registryType).getUpperBounds()[0]; + } + + if (!(registryType instanceof ParameterizedType)) { /* expected: Registry */ + continue; + } + + final RegistryKey key; + try { + key = (RegistryKey) field.get(null); + } catch (final IllegalAccessException ex) { + LOGGER.warn("Failed to access value of registry key in field {} of type {}", field.getName(), generic, ex); + continue; + } + + final Type valueType = ((ParameterizedType) registryType).getActualTypeArguments()[0]; + if (seenClasses.contains(GenericTypeReflector.erase(valueType))) { + LOGGER.debug("Encountered duplicate type in registry {}: type {}", key, valueType); + continue; + } + seenClasses.add(GenericTypeReflector.erase(valueType)); + + /* and now, finally, we can register */ + this.getParserRegistry().registerParserSupplier(TypeToken.get(valueType), + params -> new RegistryEntryArgument.RegistryEntryParser(key)); + } + } + /** * Register a parser supplier for a brigadier type that has no options and whose output can be directly used. * diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandPreprocessor.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandPreprocessor.java new file mode 100644 index 00000000..5024ef97 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandPreprocessor.java @@ -0,0 +1,47 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +package cloud.commandframework.fabric; + +import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext; +import cloud.commandframework.execution.preprocessor.CommandPreprocessor; +import org.checkerframework.checker.nullness.qual.NonNull; + +final class FabricCommandPreprocessor implements CommandPreprocessor { + + private final FabricCommandManager manager; + + FabricCommandPreprocessor(final FabricCommandManager manager) { + this.manager = manager; + } + + @Override + public void accept(@NonNull final CommandPreprocessingContext context) { + context.getCommandContext().store( + FabricCommandContextKeys.NATIVE_COMMAND_SOURCE, + this.manager.getBackwardsCommandSourceMapper().apply(context.getCommandContext().getSender()) + ); + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/EnchantmentArgument.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/EnchantmentArgument.java deleted file mode 100644 index 4d1d4da9..00000000 --- a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/EnchantmentArgument.java +++ /dev/null @@ -1,130 +0,0 @@ -// -// MIT License -// -// Copyright (c) 2020 Alexander Söderberg & Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -package cloud.commandframework.fabric.argument; - -import cloud.commandframework.arguments.CommandArgument; -import cloud.commandframework.brigadier.argument.WrappedBrigadierParser; -import cloud.commandframework.context.CommandContext; -import net.minecraft.command.argument.ItemEnchantmentArgumentType; -import net.minecraft.enchantment.Enchantment; -import net.minecraft.util.registry.Registry; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.List; -import java.util.function.BiFunction; - -/** - * An argument parsing enchantment identifier from the {@link Registry#ENCHANTMENT enchantments registry}. - * - * @param the sender type - * @since 1.4.0 - */ -public final class EnchantmentArgument extends CommandArgument { - - EnchantmentArgument( - final boolean required, - final @NonNull String name, - final @NonNull String defaultValue, - final @Nullable BiFunction, String, List> suggestionsProvider - ) { - super( - required, - name, - new WrappedBrigadierParser<>(ItemEnchantmentArgumentType.itemEnchantment()), - defaultValue, - Enchantment.class, - suggestionsProvider - ); - } - - /** - * Create a new builder. - * - * @param name Name of the argument - * @param Command sender type - * @return Created builder - */ - public static EnchantmentArgument.@NonNull Builder newBuilder(final @NonNull String name) { - return new EnchantmentArgument.Builder<>(name); - } - - /** - * Create a new required command argument. - * - * @param name Component name - * @param Command sender type - * @return Created argument - */ - public static @NonNull EnchantmentArgument of(final @NonNull String name) { - return EnchantmentArgument.newBuilder(name).asRequired().build(); - } - - /** - * Create a new optional command argument - * - * @param name Component name - * @param Command sender type - * @return Created argument - */ - public static @NonNull EnchantmentArgument optional(final @NonNull String name) { - return EnchantmentArgument.newBuilder(name).asOptional().build(); - } - - /** - * Create a new optional command argument with a default value - * - * @param name Argument name - * @param defaultValue Default value - * @param Command sender type - * @return Created argument - */ - public static @NonNull EnchantmentArgument optional( - final @NonNull String name, - final Enchantment defaultValue - ) { - return EnchantmentArgument.newBuilder(name).asOptionalWithDefault(Registry.ENCHANTMENT.getId(defaultValue).toString()).build(); - } - - - public static final class Builder extends TypedBuilder> { - - Builder(final @NonNull String name) { - super(Enchantment.class, name); - } - - /** - * Build a new criterion argument - * - * @return Constructed argument - */ - @Override - public @NonNull EnchantmentArgument build() { - return new EnchantmentArgument<>(this.isRequired(), this.getName(), this.getDefaultValue(), this.getSuggestionsProvider()); - } - - } - -} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/FabricArgumentParsers.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/FabricArgumentParsers.java new file mode 100644 index 00000000..a2c38029 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/FabricArgumentParsers.java @@ -0,0 +1,29 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +package cloud.commandframework.fabric.argument; + +public class FabricArgumentParsers { + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/RegistryEntryArgument.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/RegistryEntryArgument.java new file mode 100644 index 00000000..10ff48f2 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/argument/RegistryEntryArgument.java @@ -0,0 +1,327 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +package cloud.commandframework.fabric.argument; + +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.captions.CaptionVariable; +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.NoInputProvidedException; +import cloud.commandframework.exceptions.parsing.ParserException; +import cloud.commandframework.fabric.FabricCaptionKeys; +import cloud.commandframework.fabric.FabricCommandContextKeys; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.leangen.geantyref.TypeToken; +import net.minecraft.command.CommandSource; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.function.BiFunction; + +import static java.util.Objects.requireNonNull; + +/** + * Get a value from a registry. + * + *

Both static and dynamic registries are supported.

+ * + * @param the command sender type + * @param the registry entry type + */ +public class RegistryEntryArgument extends CommandArgument { + private static final String NAMESPACE_MINECRAFT = "minecraft"; + + RegistryEntryArgument( + final boolean required, + final @NonNull String name, + final @NonNull RegistryKey> registry, + final @NonNull String defaultValue, + final @NonNull TypeToken valueType, + final @Nullable BiFunction, String, List> suggestionsProvider + ) { + super(required, name, new RegistryEntryParser<>(registry), defaultValue, valueType, suggestionsProvider); + } + + /** + * Create a new builder. + * + * @param name Name of the argument + * @param type The type of registry entry + * @param registry A key for the registry to get values from + * @param Command sender type + * @param Registry entry type + * @return Created builder + */ + public static RegistryEntryArgument.@NonNull Builder newBuilder( + final @NonNull String name, + final @NonNull Class type, + final @NonNull RegistryKey> registry) { + return new RegistryEntryArgument.Builder<>(registry, type, name); + } + + /** + * Create a new builder. + * + * @param name Name of the argument + * @param type The type of registry entry + * @param registry A key for the registry to get values from + * @param Command sender type + * @param Registry entry type + * @return Created builder + */ + public static RegistryEntryArgument.@NonNull Builder newBuilder( + final @NonNull String name, + final @NonNull TypeToken type, + final @NonNull RegistryKey> registry) { + return new RegistryEntryArgument.Builder<>(registry, type, name); + } + + /** + * Create a new required command argument. + * + * @param name Argument name + * @param type The type of registry entry + * @param registry A key for the registry to get values from + * @param Command sender type + * @param Registry entry type + * @return Created argument + */ + public static @NonNull RegistryEntryArgument of( + final @NonNull String name, + final @NonNull Class type, + final @NonNull RegistryKey> registry + ) { + return RegistryEntryArgument.newBuilder(name, type, registry).asRequired().build(); + } + + /** + * Create a new optional command argument + * + * @param name Argument name + * @param type The type of registry entry + * @param registry A key for the registry to get values from + * @param Command sender type + * @param Registry entry type + * @return Created argument + */ + public static @NonNull RegistryEntryArgument optional( + final @NonNull String name, + final @NonNull Class type, + final @NonNull RegistryKey> registry + ) { + return RegistryEntryArgument.newBuilder(name, type, registry).asOptional().build(); + } + + /** + * Create a new optional command argument with a default value + * + * @param name Argument name + * @param type The type of registry entry + * @param registry A key for the registry to get values from + * @param defaultValue Default value + * @param Command sender type + * @param Registry entry type + * @return Created argument + */ + public static @NonNull RegistryEntryArgument optional( + final @NonNull String name, + final @NonNull Class type, + final @NonNull RegistryKey> registry, + final @NonNull RegistryKey defaultValue + ) { + return RegistryEntryArgument.newBuilder(name, type, registry) + .asOptionalWithDefault(defaultValue.getValue().toString()) + .build(); + } + + /** + * A parser for values stored in a {@link Registry} + * + * @param Command sender type + * @param Registry entry type + */ + public static final class RegistryEntryParser implements ArgumentParser { + private final RegistryKey> registryIdent; + + /** + * Create a new parser for registry entries. + * + * @param registryIdent the registry identifier + */ + public RegistryEntryParser(final RegistryKey> registryIdent) { + this.registryIdent = requireNonNull(registryIdent, "registryIdent"); + } + + @Override + public @NonNull ArgumentParseResult<@NonNull V> parse( + @NonNull final CommandContext<@NonNull C> commandContext, + @NonNull final Queue<@NonNull String> inputQueue + ) { + final String possibleIdentifier = inputQueue.peek(); + if (possibleIdentifier == null) { + return ArgumentParseResult.failure(new NoInputProvidedException( + RegistryEntryArgument.class, + commandContext + )); + } + + final Identifier key; + try { + key = Identifier.fromCommandInput(new StringReader(possibleIdentifier)); + } catch (final CommandSyntaxException ex) { + return ArgumentParseResult.failure(ex); + } + inputQueue.poll(); + + final Registry registry = this.getRegistry(commandContext); + if (registry == null) { + return ArgumentParseResult.failure(new IllegalArgumentException("Unknown registry " + this.registryIdent)); + } + + final V entry = registry.get(key); + if (entry == null) { + return ArgumentParseResult.failure(new UnknownEntryException(commandContext, key, this.registryIdent)); + } + + return ArgumentParseResult.success(entry); + } + + @SuppressWarnings("unchecked") + Registry getRegistry(final CommandContext ctx) { + final CommandSource reverseMapped = ctx.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE); + // First try dynamic registries (for things loaded from data-packs) + Registry registry = reverseMapped.getRegistryManager().getOptional(this.registryIdent).orElse(null); + if (registry == null) { + // And then static registries + registry = (Registry) Registry.REGISTRIES.get(this.registryIdent.getValue()); + } + return registry; + } + + @Override + public @NonNull List<@NonNull String> suggestions( + final @NonNull CommandContext commandContext, + final @NonNull String input + ) { + final Set ids = this.getRegistry(commandContext).getIds(); + final List results = new ArrayList<>(ids.size()); + for (final Identifier entry : ids) { + if (entry.getNamespace().equals(NAMESPACE_MINECRAFT)) { + results.add(entry.getPath()); + } + results.add(entry.toString()); + } + + return results; + } + + @Override + public boolean isContextFree() { + return true; + } + + /** + * Get the registry associated with this parser + * @return the registry + */ + public RegistryKey> getRegistry() { + return this.registryIdent; + } + + } + + /** + * A builder for registry entry arguments. + * + * @param The sender type + * @param The registry value type + */ + public static final class Builder extends CommandArgument.TypedBuilder> { + private final RegistryKey> registryIdent; + + Builder( + final RegistryKey> key, + final @NonNull Class valueType, + final @NonNull String name + ) { + super(valueType, name); + this.registryIdent = key; + } + + Builder( + final RegistryKey> key, + final @NonNull TypeToken valueType, + final @NonNull String name + ) { + super(valueType, name); + this.registryIdent = key; + } + + @Override + public @NonNull RegistryEntryArgument<@NonNull C, @NonNull V> build() { + return new RegistryEntryArgument<>( + this.isRequired(), + this.getName(), + this.registryIdent, + this.getDefaultValue(), + this.getValueType(), + this.getSuggestionsProvider() + ); + } + } + + /** + * An exception thrown when an entry in a registry could not be found. + */ + private static final class UnknownEntryException extends ParserException { + + private static final long serialVersionUID = 7694424294461849903L; + + UnknownEntryException( + final CommandContext context, + final Identifier key, + final RegistryKey> registry + ) { + super( + RegistryEntryArgument.class, + context, + FabricCaptionKeys.ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY, + CaptionVariable.of("id", key.toString()), + CaptionVariable.of("registry", registry.toString()) + ); + } + + } + +}