More parser work

This commit is contained in:
Zach Levis 2021-01-07 19:36:00 -08:00 committed by Jason
parent 91b433c14b
commit 52c0796539
14 changed files with 950 additions and 4 deletions

View file

@ -42,6 +42,10 @@ public final class FabricCaptionKeys {
"argument.parse.failure.registry_entry.unknown_entry" "argument.parse.failure.registry_entry.unknown_entry"
); );
public static final Caption ARGUMENT_PARSE_FAILURE_TEAM_UNKNOWN = of(
"argument.parse.failure.team.unknown"
);
private static @NonNull Caption of(final @NonNull String key) { private static @NonNull Caption of(final @NonNull String key) {
final Caption caption = Caption.of(key); final Caption caption = Caption.of(key);
RECOGNIZED_CAPTIONS.add(caption); RECOGNIZED_CAPTIONS.add(caption);

View file

@ -30,11 +30,15 @@ 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}'"; public static final String ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY = "Could not find key {id} in registry '{registry}'";
public static final String ARGUMENT_PARSE_FAILURE_TEAM_UNKOWN = "Could not find any team named '{input}'!";
protected FabricCaptionRegistry() { protected FabricCaptionRegistry() {
super(); super();
this.registerMessageFactory(FabricCaptionKeys.ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY, this.registerMessageFactory(FabricCaptionKeys.ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY,
(caption, sender) -> ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY); (caption, sender) -> ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY);
this.registerMessageFactory(FabricCaptionKeys.ARGUMENT_PARSE_FAILURE_TEAM_UNKNOWN,
(caption, sender) -> ARGUMENT_PARSE_FAILURE_TEAM_UNKOWN);
} }

View file

@ -33,7 +33,10 @@ import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator; import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.fabric.argument.FabricArgumentParsers;
import cloud.commandframework.fabric.argument.RegistryEntryArgument; import cloud.commandframework.fabric.argument.RegistryEntryArgument;
import cloud.commandframework.fabric.argument.TeamArgument;
import cloud.commandframework.fabric.data.MinecraftTime;
import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.meta.SimpleCommandMeta;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
@ -62,6 +65,7 @@ import net.minecraft.command.argument.ObjectiveCriteriaArgumentType;
import net.minecraft.command.argument.OperationArgumentType; import net.minecraft.command.argument.OperationArgumentType;
import net.minecraft.command.argument.ParticleArgumentType; import net.minecraft.command.argument.ParticleArgumentType;
import net.minecraft.command.argument.SwizzleArgumentType; import net.minecraft.command.argument.SwizzleArgumentType;
import net.minecraft.command.argument.TeamArgumentType;
import net.minecraft.command.argument.UuidArgumentType; import net.minecraft.command.argument.UuidArgumentType;
import net.minecraft.command.suggestion.SuggestionProviders; import net.minecraft.command.suggestion.SuggestionProviders;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@ -69,6 +73,7 @@ import net.minecraft.nbt.Tag;
import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleEffect;
import net.minecraft.predicate.NumberRange; import net.minecraft.predicate.NumberRange;
import net.minecraft.scoreboard.ScoreboardCriterion; import net.minecraft.scoreboard.ScoreboardCriterion;
import net.minecraft.scoreboard.Team;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -160,6 +165,8 @@ public abstract class FabricCommandManager<C, S extends CommandSource> extends C
/* Cloud-native argument types */ /* Cloud-native argument types */
brigadier.registerMapping(new TypeToken<UUIDArgument.UUIDParser<C>>() {}, builder -> builder.toConstant(UuidArgumentType.uuid())); brigadier.registerMapping(new TypeToken<UUIDArgument.UUIDParser<C>>() {}, builder -> builder.toConstant(UuidArgumentType.uuid()));
this.registerRegistryEntryMappings(); this.registerRegistryEntryMappings();
brigadier.registerMapping(new TypeToken<TeamArgument.TeamParser<C>>() {}, builder -> builder.toConstant(TeamArgumentType.team()));
this.getParserRegistry().registerParserSupplier(TypeToken.get(Team.class), params -> new TeamArgument.TeamParser<>());
/* Wrapped/Constant Brigadier types, native value type */ /* Wrapped/Constant Brigadier types, native value type */
this.registerConstantNativeParserSupplier(Formatting.class, ColorArgumentType.color()); this.registerConstantNativeParserSupplier(Formatting.class, ColorArgumentType.color());
@ -194,8 +201,8 @@ public abstract class FabricCommandManager<C, S extends CommandSource> extends C
this.registerConstantNativeParserSupplier(ScoreboardSlotArgumentType.scoreboardSlot()); this.registerConstantNativeParserSupplier(ScoreboardSlotArgumentType.scoreboardSlot());
this.registerConstantNativeParserSupplier(Team.class, TeamArgumentType.team()); this.registerConstantNativeParserSupplier(Team.class, TeamArgumentType.team());
this.registerConstantNativeParserSupplier(/* slot *, ItemSlotArgumentType.itemSlot()); this.registerConstantNativeParserSupplier(/* slot *, ItemSlotArgumentType.itemSlot());
this.registerConstantNativeParserSupplier(CommandFunction.class, FunctionArgumentType.function()); this.registerConstantNativeParserSupplier(CommandFunction.class, FunctionArgumentType.function()); */
this.registerConstantNativeParserSupplier(/* time representation in ticks *, TimeArgumentType.time());*/ this.getParserRegistry().registerParserSupplier(TypeToken.get(MinecraftTime.class), params -> FabricArgumentParsers.time());
/* Wrapped brigadier requiring parameters */ /* Wrapped brigadier requiring parameters */
// score holder: single vs multiple // score holder: single vs multiple

View file

@ -117,7 +117,7 @@ public final class AngleArgument<C> extends CommandArgument<C, AngleArgumentType
} }
/** /**
* Build a new criterion argument * Build a new angle argument
* *
* @return Constructed argument * @return Constructed argument
*/ */

View file

@ -24,6 +24,44 @@
package cloud.commandframework.fabric.argument; package cloud.commandframework.fabric.argument;
public class FabricArgumentParsers { import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.fabric.FabricCommandContextKeys;
import cloud.commandframework.fabric.data.MinecraftTime;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.FunctionArgumentType;
import net.minecraft.command.argument.TimeArgumentType;
import net.minecraft.server.function.CommandFunction;
/**
* Parsers for Vanilla command argument types.
*
* @since 1.4.0
*/
public final class FabricArgumentParsers {
private FabricArgumentParsers() {
}
/**
* A parser for in-game time, in ticks.
*
* @param <C> sender type
* @return a parser instance
*/
public static <C> ArgumentParser<C, MinecraftTime> time() {
return new WrappedBrigadierParser<C, Integer>(TimeArgumentType.time())
.map((ctx, val) -> ArgumentParseResult.success(MinecraftTime.of(val)));
}
public static <C> ArgumentParser<C, CommandFunction> commandFunction() {
// TODO: Should probably write our own parser for this, it's either Identifier or tag.
// Server parsers
return new WrappedBrigadierParser<C, FunctionArgumentType.FunctionArgument>(FunctionArgumentType.function()).map((ctx, val) -> {
final CommandSource source = ctx.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
source.getCompletions()
})
}
} }

View file

@ -58,6 +58,7 @@ import static java.util.Objects.requireNonNull;
* *
* @param <C> the command sender type * @param <C> the command sender type
* @param <V> the registry entry type * @param <V> the registry entry type
* @since 1.4.0
*/ */
public class RegistryEntryArgument<C, V> extends CommandArgument<C, V> { public class RegistryEntryArgument<C, V> extends CommandArgument<C, V> {
private static final String NAMESPACE_MINECRAFT = "minecraft"; private static final String NAMESPACE_MINECRAFT = "minecraft";

View file

@ -0,0 +1,68 @@
package cloud.commandframework.fabric.argument;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.FabricCommandContextKeys;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.command.CommandSource;
import net.minecraft.server.command.ServerCommandSource;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Queue;
/**
* An argument parser that is resolved in different ways on the logical server and logical client.
*
* @param <C> command sender type
* @param <I> intermediate type to resolve
* @param <R> resolved type
*/
abstract class SidedArgumentParser<C, I, R> implements ArgumentParser<C, R> {
@Override
public @NonNull ArgumentParseResult<@NonNull R> parse(
@NonNull final CommandContext<@NonNull C> commandContext,
@NonNull final Queue<@NonNull String> inputQueue
) {
final CommandSource source = commandContext.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
final ArgumentParseResult<I> intermediate = this.parseIntermediate(commandContext, inputQueue);
return intermediate.flatMapParsedValue(value -> {
if (source instanceof ServerCommandSource) {
return this.resolveServer(commandContext, source, value);
} else if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
return this.resolveClient(commandContext, source, value);
} else {
throw new IllegalStateException("Cannot have non-server command source when not on client");
}
});
}
protected abstract ArgumentParseResult<I> parseIntermediate(
@NonNull CommandContext<@NonNull C> commandContext,
@NonNull Queue<@NonNull String> inputQueue
);
/**
* Resolve the final value for this argument when running on the client.
*
* @param context Command context
* @param source The command source
* @param value parsed intermediate value
* @return a resolved value
*/
protected abstract ArgumentParseResult<R> resolveClient(CommandContext<C> context, CommandSource source, I value);
/**
* Resolve the final value for this argument when running on the server.
*
* @param context Command context
* @param source The command source
* @param value Parsed intermediate value
* @return a resolved value
*/
protected abstract ArgumentParseResult<R> resolveServer(CommandContext<C> context, CommandSource source, I value);
}

View file

@ -0,0 +1,200 @@
//
// 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.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 net.minecraft.client.MinecraftClient;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.EntityAnchorArgumentType;
import net.minecraft.scoreboard.Team;
import net.minecraft.server.command.ServerCommandSource;
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.function.BiFunction;
/**
* An argument parsing an entity anchor.
*
* @param <C> the sender type
* @since 1.4.0
*/
public final class TeamArgument<C> extends CommandArgument<C, Team> {
TeamArgument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) {
super(
required,
name,
new TeamParser<>(),
defaultValue,
Team.class,
suggestionsProvider
);
}
/**
* Create a new builder.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
*/
public static <C> TeamArgument.@NonNull Builder<C> newBuilder(final @NonNull String name) {
return new TeamArgument.Builder<>(name);
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
*/
public static <C> @NonNull TeamArgument<C> of(final @NonNull String name) {
return TeamArgument.<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 TeamArgument<C> optional(final @NonNull String name) {
return TeamArgument.<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 TeamArgument<C> optional(
final @NonNull String name,
final EntityAnchorArgumentType.@NonNull EntityAnchor defaultValue
) {
return TeamArgument.<C>newBuilder(name).asOptionalWithDefault(defaultValue.name()).build();
}
public static final class TeamParser<C> extends SidedArgumentParser<C, String, Team> {
@Override
public @NonNull List<@NonNull String> suggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
return new ArrayList<>(commandContext.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE).getTeamNames());
}
@Override
protected ArgumentParseResult<String> parseIntermediate(
@NonNull final CommandContext<@NonNull C> commandContext,
@NonNull final Queue<@NonNull String> inputQueue
) {
final String input = inputQueue.peek();
if (input == null) {
return ArgumentParseResult.failure(new NoInputProvidedException(TeamParser.class, commandContext));
}
return ArgumentParseResult.success(input);
}
@Override
protected ArgumentParseResult<Team> resolveClient(final CommandContext<C> context,
final CommandSource source,
final String value) {
final Team result = MinecraftClient.getInstance().getNetworkHandler().getWorld().getScoreboard().getTeam(value);
if (result == null) {
return ArgumentParseResult.failure(new UnknownTeamException(context, value));
}
return ArgumentParseResult.success(result);
}
@Override
protected ArgumentParseResult<Team> resolveServer(final CommandContext<C> context,
final CommandSource source,
final String value) {
final Team result = ((ServerCommandSource) source).getWorld().getScoreboard().getTeam(value);
if (result == null) {
return ArgumentParseResult.failure(new UnknownTeamException(context, value));
}
return ArgumentParseResult.success(result);
}
}
public static final class Builder<C> extends TypedBuilder<C, Team, Builder<C>> {
Builder(final @NonNull String name) {
super(Team.class, name);
}
/**
* Build a new criterion argument
*
* @return Constructed argument
*/
@Override
public @NonNull TeamArgument<C> build() {
return new TeamArgument<>(this.isRequired(), this.getName(), this.getDefaultValue(), this.getSuggestionsProvider());
}
}
public static final class UnknownTeamException extends ParserException {
UnknownTeamException(
final @NonNull CommandContext<?> context,
final @NonNull String input
) {
super(
TeamParser.class,
context,
FabricCaptionKeys.ARGUMENT_PARSE_FAILURE_TEAM_UNKNOWN,
CaptionVariable.of("input", input)
);
}
}
}

View file

@ -0,0 +1,130 @@
//
// 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.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.data.MinecraftTime;
import net.minecraft.command.argument.TimeArgumentType;
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 for in-game time
*
* @param <C> the sender type
* @since 1.4.0
*/
public final class TimeArgument<C> extends CommandArgument<C, MinecraftTime> {
TimeArgument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider
) {
super(
required,
name,
FabricArgumentParsers.time(),
defaultValue,
MinecraftTime.class,
suggestionsProvider
);
}
/**
* Create a new builder.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
*/
public static <C> TimeArgument.@NonNull Builder<C> newBuilder(final @NonNull String name) {
return new TimeArgument.Builder<>(name);
}
/**
* Create a new required command argument.
*
* @param name Component name
* @param <C> Command sender type
* @return Created argument
*/
public static <C> @NonNull TimeArgument<C> of(final @NonNull String name) {
return TimeArgument.<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 TimeArgument<C> optional(final @NonNull String name) {
return TimeArgument.<C>newBuilder(name).asOptional().build();
}
/**
* Create a new optional command argument with a default value
*
* @param name Argument name
* @param defaultTime Default time, in ticks
* @param <C> Command sender type
* @return Created argument
*/
public static <C> @NonNull TimeArgument<C> optional(
final @NonNull String name,
final MinecraftTime defaultTime
) {
return TimeArgument.<C>newBuilder(name).asOptionalWithDefault(defaultTime.toString()).build();
}
public static final class Builder<C> extends TypedBuilder<C, MinecraftTime, Builder<C>> {
Builder(final @NonNull String name) {
super(MinecraftTime.class, name);
}
/**
* Build a new time argument
*
* @return Constructed argument
*/
@Override
public @NonNull TimeArgument<C> build() {
return new TimeArgument<>(this.isRequired(), this.getName(), this.getDefaultValue(), this.getSuggestionsProvider());
}
}
}

View file

@ -0,0 +1,313 @@
//
// 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.server;
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.server.function.CommandFunction;
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
* @since 1.4.0
*/
public class CommandFunctionArgument<C> extends CommandArgument<C, CommandFunction> {
CommandFunctionArgument(
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> CommandFunctionArgument.@NonNull Builder<C, V> newBuilder(
final @NonNull String name,
final @NonNull Class<V> type,
final @NonNull RegistryKey<? extends Registry<V>> registry) {
return new CommandFunctionArgument.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> CommandFunctionArgument.@NonNull Builder<C, V> newBuilder(
final @NonNull String name,
final @NonNull TypeToken<V> type,
final @NonNull RegistryKey<? extends Registry<V>> registry) {
return new CommandFunctionArgument.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 CommandFunctionArgument<C, V> of(
final @NonNull String name,
final @NonNull Class<V> type,
final @NonNull RegistryKey<? extends Registry<V>> registry
) {
return CommandFunctionArgument.<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 CommandFunctionArgument<C, V> optional(
final @NonNull String name,
final @NonNull Class<V> type,
final @NonNull RegistryKey<? extends Registry<V>> registry
) {
return CommandFunctionArgument.<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 CommandFunctionArgument<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 CommandFunctionArgument.<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(
CommandFunctionArgument.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 TypedBuilder<C, V, Builder<C, V>> {
Builder(
final @NonNull String name
) {
super(valueType, name);
}
@Override
public @NonNull CommandFunctionArgument<@NonNull C, @NonNull V> build() {
return new CommandFunctionArgument<>(
this.isRequired(),
this.getName(),
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(
CommandFunctionArgument.class,
context,
FabricCaptionKeys.ARGUMENT_PARSE_FAILURE_REGISTRY_ENTRY_UNKNOWN_ENTRY,
CaptionVariable.of("id", key.toString()),
CaptionVariable.of("registry", registry.toString())
);
}
}
}

View file

@ -0,0 +1,6 @@
/**
* Command arguments that can only be used on the logical server.
*
* @since 1.4.0
*/
package cloud.commandframework.fabric.argument.server;

View file

@ -0,0 +1,123 @@
package cloud.commandframework.fabric.data;
import java.time.temporal.TemporalUnit;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static java.util.Objects.requireNonNull;
/**
* An element of in-game time.
*
* <p>The basic unit is 1 <em>tick</em>, which aims to be {@code 50ms}</p>
*
* @since 1.4.0
*/
public final class MinecraftTime {
private static final MinecraftTime ZERO = new MinecraftTime(0);
private final long ticks;
/**
* Get the time instance for the specified number of ticks.
*
* @param ticks the number of ticks
* @return a time holder
*/
public static MinecraftTime of(final long ticks) {
return ticks == 0 ? ZERO : new MinecraftTime(ticks);
}
/**
* Given an amount of time in another unit, create a game time holding the number of ticks expected to pass in that time.
*
* @param amount the amount of time
* @param unit the unit
* @return a time holder
*/
public static MinecraftTime of(final long amount, final TemporalUnit unit) {
requireNonNull(unit, "unit");
return new MinecraftTime(Math.round(amount / 50d * unit.getDuration().toMillis()));
}
/**
* Given an amount of time in another unit, create a game time holding the number of ticks expected to pass in that time.
*
* @param amount the amount of time
* @param unit the unit
* @return a time holder
*/
public static MinecraftTime of(final long amount, final TimeUnit unit) {
requireNonNull(unit, "unit");
return amount == 0 ? ZERO : new MinecraftTime(TimeUnit.MILLISECONDS.convert(amount, unit) / 50);
}
MinecraftTime(final long ticks) {
this.ticks = ticks;
}
/**
* Get the number of in-game ticks represented by this time.
*
* <p>This time will be truncated to the maximum value of an integer.
* See {@link #getLongTicks()} for the full contents.</p>
*
* @return the time in ticks
*/
public int getTicks() {
return (int) this.ticks;
}
/**
* Get the number of in-game ticks represented by this time.
*
* @return the time in ticks
*/
public long getLongTicks() {
return this.ticks;
}
/**
* Convert this to another time unit.
*
* @param unit the target unit
* @return the target duration, as represented by the provided unit
*/
public long convertTo(final TemporalUnit unit) {
return this.ticks * 50 / unit.getDuration().toMillis();
}
/**
* Convert this to another time unit.
*
* @param unit the target unit
* @return the target duration, as represented by the provided unit
*/
public long convertTo(final TimeUnit unit) {
return unit.convert(this.ticks * 50, TimeUnit.MILLISECONDS);
}
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
}
if (other == null || this.getClass() != other.getClass()) {
return false;
}
return this.ticks == ((MinecraftTime) other).ticks;
}
@Override
public int hashCode() {
return Objects.hash(this.ticks);
}
@Override
public String toString() {
return Long.toString(this.ticks);
}
}

View file

@ -0,0 +1,48 @@
package cloud.commandframework.fabric.data;
import net.minecraft.command.EntitySelector;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
/**
* A selector string to query multiple entity-like values
*
* @param <V> Value type
*/
public interface Selector<V> {
/**
* Get the raw string associated with the selector.
*
* @return the input
*/
String getInput();
/**
* If this value came from a parsed selector, this will provide the details of that selector.
*
* @return the selector
*/
@Nullable EntitySelector getSelector();
/**
* Resolve the value of this selector.
*
* <p>A successfully parsed selector must match one or more values</p>
*
* @return all matched entities
*/
Collection<V> get();
/**
* A specialized selector that can only return one value.
*
* @param <V> the value type
*/
interface Single<V> extends Selector<V> {
V getSingle();
}
}

View file

@ -0,0 +1,4 @@
/**
* Data holders for representing Vanilla argument type values.
*/
package cloud.commandframework.fabric.data;