fabric: Fully implement a registry entry argument type

This commit is contained in:
Zach Levis 2021-01-05 12:20:48 -08:00 committed by Jason
parent 98aea50d3c
commit 91b433c14b
10 changed files with 649 additions and 138 deletions

View file

@ -739,6 +739,10 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>,
return this.defaultDescription;
}
protected final @NonNull TypeToken<T> getValueType() {
return this.valueType;
}
}
/**

View file

@ -32,7 +32,7 @@ import java.util.List;
import java.util.Queue;
import java.util.function.BiFunction;
final class MappedArgumentParser<C, I, O> implements ArgumentParser<C, O> {
public final class MappedArgumentParser<C, I, O> implements ArgumentParser<C, O> {
private final ArgumentParser<C, I> base;
private final BiFunction<CommandContext<C>, I, ArgumentParseResult<O>> mapper;
@ -44,6 +44,15 @@ final class MappedArgumentParser<C, I, O> implements ArgumentParser<C, O> {
this.mapper = mapper;
}
/**
* Get the parser this one is derived from.
*
* @return the base parser
*/
public ArgumentParser<C, I> getBaseParser() {
return this.base;
}
@Override
public @NonNull ArgumentParseResult<@NonNull O> parse(
@NonNull final CommandContext<@NonNull C> commandContext,

View file

@ -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<Caption> 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);
}
}

View file

@ -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<C> extends SimpleCaptionRegistry<C> {
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);
}
}

View file

@ -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<CommandSource> NATIVE_COMMAND_SOURCE = SimpleCloudKey.of(
"cloud:fabric_command_source",
TypeToken.get(CommandSource.class)
);
}

View file

@ -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<C, S extends CommandSource> extends CommandManager<C> implements BrigadierManagerHolder<C> {
private static final Logger LOGGER = LogManager.getLogger();
private static final int MOD_PUBLIC_STATIC_FINAL = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
private final Function<S, C> commandSourceMapper;
private final Function<C, S> backwardsCommandSourceMapper;
@ -132,13 +150,16 @@ public abstract class FabricCommandManager<C, S extends CommandSource> extends C
));
this.brigadierManager.backwardsBrigadierSenderMapper(this.backwardsCommandSourceMapper);
this.registerNativeBrigadierMappings(this.brigadierManager);
this.setCaptionRegistry(new FabricCaptionRegistry<>());
this.registerCommandPreProcessor(new FabricCommandPreprocessor<>(this));
((FabricCommandRegistrationHandler<C, S>) this.getCommandRegistrationHandler()).initialize(this);
}
private void registerNativeBrigadierMappings(final CloudBrigadierManager<C, S> brigadier) {
/* Cloud-native argument types */
brigadier.registerMapping(new TypeToken<UUIDArgument.UUIDParser<C>>() {}, false, cloud -> UuidArgumentType.uuid());
brigadier.registerMapping(new TypeToken<UUIDArgument.UUIDParser<C>>() {}, 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<C, S extends CommandSource> extends C
this.registerConstantNativeParserSupplier(AngleArgumentType.Angle.class, AngleArgumentType.angle());
this.registerConstantNativeParserSupplier(new TypeToken<EnumSet<Direction.Axis>>() {}, 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<C, S extends CommandSource> 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<C, S extends CommandSource> 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<RegistryEntryArgument.RegistryEntryParser<C, ?>>() {},
builder -> builder.to(argument -> {
/* several registries have specialized argument types, so let's use those where possible */
final RegistryKey<? extends Registry<?>> 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<? extends Registry<?>> registry = argument.getRegistry();
if (registry.equals(Registry.SOUND_EVENT_KEY)) {
return (SuggestionProvider<S>) SuggestionProviders.AVAILABLE_SOUNDS;
} else if (registry.equals(Registry.BIOME_KEY)) {
return (SuggestionProvider<S>) 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<? extends Registry<?>> and register those */
/* This only works for vanilla registries really, we'll have to do other things for non-vanilla ones */
final Set<Class<?>> 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<? extends Registry<?>> */
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<V> */
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.
*

View file

@ -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<C> implements CommandPreprocessor<C> {
private final FabricCommandManager<C, ?> manager;
FabricCommandPreprocessor(final FabricCommandManager<C, ?> manager) {
this.manager = manager;
}
@Override
public void accept(@NonNull final CommandPreprocessingContext<C> context) {
context.getCommandContext().store(
FabricCommandContextKeys.NATIVE_COMMAND_SOURCE,
this.manager.getBackwardsCommandSourceMapper().apply(context.getCommandContext().getSender())
);
}
}

View file

@ -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 <C> the sender type
* @since 1.4.0
*/
public final class EnchantmentArgument<C> extends CommandArgument<C, Enchantment> {
EnchantmentArgument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) {
super(
required,
name,
new WrappedBrigadierParser<>(ItemEnchantmentArgumentType.itemEnchantment()),
defaultValue,
Enchantment.class,
suggestionsProvider
);
}
/**
* Create a new builder.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
*/
public static <C> EnchantmentArgument.@NonNull Builder<C> newBuilder(final @NonNull String name) {
return new EnchantmentArgument.Builder<>(name);
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
*/
public static <C> @NonNull EnchantmentArgument<C> of(final @NonNull String name) {
return EnchantmentArgument.<C>newBuilder(name).asRequired().build();
}
/**
* Create a new optional command argument
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
*/
public static <C> @NonNull EnchantmentArgument<C> optional(final @NonNull String name) {
return EnchantmentArgument.<C>newBuilder(name).asOptional().build();
}
/**
* Create a new optional command argument with a default value
*
* @param name Argument name
* @param defaultValue Default value
* @param <C> Command sender type
* @return Created argument
*/
public static <C> @NonNull EnchantmentArgument<C> optional(
final @NonNull String name,
final Enchantment defaultValue
) {
return EnchantmentArgument.<C>newBuilder(name).asOptionalWithDefault(Registry.ENCHANTMENT.getId(defaultValue).toString()).build();
}
public static final class Builder<C> extends TypedBuilder<C, Enchantment, Builder<C>> {
Builder(final @NonNull String name) {
super(Enchantment.class, name);
}
/**
* Build a new criterion argument
*
* @return Constructed argument
*/
@Override
public @NonNull EnchantmentArgument<C> build() {
return new EnchantmentArgument<>(this.isRequired(), this.getName(), this.getDefaultValue(), this.getSuggestionsProvider());
}
}
}

View file

@ -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 {
}

View file

@ -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.
*
* <p>Both static and dynamic registries are supported.</p>
*
* @param <C> the command sender type
* @param <V> the registry entry type
*/
public class RegistryEntryArgument<C, V> extends CommandArgument<C, V> {
private static final String NAMESPACE_MINECRAFT = "minecraft";
RegistryEntryArgument(
final boolean required,
final @NonNull String name,
final @NonNull RegistryKey<? extends Registry<V>> registry,
final @NonNull String defaultValue,
final @NonNull TypeToken<V> valueType,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> 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 <C> Command sender type
* @param <V> Registry entry type
* @return Created builder
*/
public static <C, V> RegistryEntryArgument.@NonNull Builder<C, V> newBuilder(
final @NonNull String name,
final @NonNull Class<V> type,
final @NonNull RegistryKey<? extends Registry<V>> 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 <C> Command sender type
* @param <V> Registry entry type
* @return Created builder
*/
public static <C, V> RegistryEntryArgument.@NonNull Builder<C, V> newBuilder(
final @NonNull String name,
final @NonNull TypeToken<V> type,
final @NonNull RegistryKey<? extends Registry<V>> 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 <C> Command sender type
* @param <V> Registry entry type
* @return Created argument
*/
public static <C, V> @NonNull RegistryEntryArgument<C, V> of(
final @NonNull String name,
final @NonNull Class<V> type,
final @NonNull RegistryKey<? extends Registry<V>> registry
) {
return RegistryEntryArgument.<C, V>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 <C> Command sender type
* @param <V> Registry entry type
* @return Created argument
*/
public static <C, V> @NonNull RegistryEntryArgument<C, V> optional(
final @NonNull String name,
final @NonNull Class<V> type,
final @NonNull RegistryKey<? extends Registry<V>> registry
) {
return RegistryEntryArgument.<C, V>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 <C> Command sender type
* @param <V> Registry entry type
* @return Created argument
*/
public static <C, V> @NonNull RegistryEntryArgument<C, V> optional(
final @NonNull String name,
final @NonNull Class<V> type,
final @NonNull RegistryKey<? extends Registry<V>> registry,
final @NonNull RegistryKey<V> defaultValue
) {
return RegistryEntryArgument.<C, V>newBuilder(name, type, registry)
.asOptionalWithDefault(defaultValue.getValue().toString())
.build();
}
/**
* A parser for values stored in a {@link Registry}
*
* @param <C> Command sender type
* @param <V> Registry entry type
*/
public static final class RegistryEntryParser<C, V> implements ArgumentParser<C, V> {
private final RegistryKey<? extends Registry<V>> registryIdent;
/**
* Create a new parser for registry entries.
*
* @param registryIdent the registry identifier
*/
public RegistryEntryParser(final RegistryKey<? extends Registry<V>> 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<V> 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<V> getRegistry(final CommandContext<C> ctx) {
final CommandSource reverseMapped = ctx.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
// First try dynamic registries (for things loaded from data-packs)
Registry<V> registry = reverseMapped.getRegistryManager().getOptional(this.registryIdent).orElse(null);
if (registry == null) {
// And then static registries
registry = (Registry<V>) Registry.REGISTRIES.get(this.registryIdent.getValue());
}
return registry;
}
@Override
public @NonNull List<@NonNull String> suggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
final Set<Identifier> ids = this.getRegistry(commandContext).getIds();
final List<String> 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<? extends Registry<?>> getRegistry() {
return this.registryIdent;
}
}
/**
* A builder for registry entry arguments.
*
* @param <C> The sender type
* @param <V> The registry value type
*/
public static final class Builder<C, V> extends CommandArgument.TypedBuilder<C, V, Builder<C, V>> {
private final RegistryKey<? extends Registry<V>> registryIdent;
Builder(
final RegistryKey<? extends Registry<V>> key,
final @NonNull Class<V> valueType,
final @NonNull String name
) {
super(valueType, name);
this.registryIdent = key;
}
Builder(
final RegistryKey<? extends Registry<V>> key,
final @NonNull TypeToken<V> 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<? extends Registry<?>> registry
) {
super(
RegistryEntryArgument.class,
context,
FabricCaptionKeys.ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY,
CaptionVariable.of("id", key.toString()),
CaptionVariable.of("registry", registry.toString())
);
}
}
}