Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.incendo.cloud.minecraft.modded.parser.VanillaArgumentParsers Maven / Gradle / Ivy
//
// MIT License
//
// Copyright (c) 2024 Incendo
//
// 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 org.incendo.cloud.minecraft.modded.parser;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.MessageArgument;
import net.minecraft.commands.arguments.TimeArgument;
import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
import net.minecraft.commands.arguments.coordinates.ColumnPosArgument;
import net.minecraft.commands.arguments.coordinates.Vec2Argument;
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
import net.minecraft.commands.arguments.item.ItemArgument;
import net.minecraft.commands.arguments.item.ItemInput;
import net.minecraft.commands.arguments.selector.EntitySelector;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.brigadier.parser.WrappedBrigadierParser;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.minecraft.modded.ModdedCommandContextKeys;
import org.incendo.cloud.minecraft.modded.data.Coordinates;
import org.incendo.cloud.minecraft.modded.data.Message;
import org.incendo.cloud.minecraft.modded.data.MinecraftTime;
import org.incendo.cloud.minecraft.modded.data.MultipleEntitySelector;
import org.incendo.cloud.minecraft.modded.data.MultiplePlayerSelector;
import org.incendo.cloud.minecraft.modded.data.SingleEntitySelector;
import org.incendo.cloud.minecraft.modded.data.SinglePlayerSelector;
import org.incendo.cloud.minecraft.modded.internal.ContextualArgumentTypeProvider;
import org.incendo.cloud.minecraft.modded.internal.EntitySelectorAccess;
import org.incendo.cloud.parser.ArgumentParseResult;
import org.incendo.cloud.parser.ArgumentParser;
import org.incendo.cloud.parser.ParserDescriptor;
/**
* Parsers for Vanilla command argument types.
*/
public final class VanillaArgumentParsers {
private VanillaArgumentParsers() {
}
/**
* A parser that wraps Brigadier argument types which need a {@link CommandBuildContext}
*
* @param sender type
* @param argument value type
* @param factory factory that creates these arguments
* @param valueType value type of parsers output
* @return the parser
*/
public static @NonNull ParserDescriptor contextualParser(
final @NonNull Function> factory,
final @NonNull Class valueType
) {
return ParserDescriptor.of(new WrappedBrigadierParser<>(new ContextualArgumentTypeProvider<>(factory)), valueType);
}
/**
* A parser for {@link ItemInput}.
*
* @param command sender type
* @return the parser
*/
public static @NonNull ParserDescriptor itemInput() {
return contextualParser(ItemArgument::item, ItemInput.class);
}
/**
* A parser for in-game time, in ticks.
*
* @param sender type
* @return a parser descriptor
*/
public static @NonNull ParserDescriptor timeParser() {
ArgumentParser parser = new WrappedBrigadierParser(TimeArgument.time())
.flatMapSuccess((ctx, val) -> ArgumentParseResult.successFuture(MinecraftTime.of(val)));
return ParserDescriptor.of(parser, MinecraftTime.class);
}
/**
* A parser for block coordinates.
*
* @param sender type
* @return a parser descriptor
*/
public static @NonNull ParserDescriptor blockPosParser() {
ArgumentParser parser = new WrappedBrigadierParser(BlockPosArgument.blockPos())
.flatMapSuccess(VanillaArgumentParsers::mapToCoordinates);
return ParserDescriptor.of(parser, Coordinates.BlockCoordinates.class);
}
/**
* A parser for column coordinates.
*
* @param sender type
* @return a parser descriptor
*/
public static @NonNull ParserDescriptor columnPosParser() {
ArgumentParser parser = new WrappedBrigadierParser(ColumnPosArgument.columnPos())
.flatMapSuccess(VanillaArgumentParsers::mapToCoordinates);
return ParserDescriptor.of(parser, Coordinates.ColumnCoordinates.class);
}
/**
* A parser for coordinates, relative or absolute, from 2 doubles for x and z,
* with y always defaulting to 0.
*
* @param centerIntegers whether to center integers at x.5
* @param sender type
* @return a parser descriptor
*/
public static @NonNull ParserDescriptor vec2Parser(final boolean centerIntegers) {
ArgumentParser parser = new WrappedBrigadierParser(new Vec2Argument(centerIntegers))
.flatMapSuccess(VanillaArgumentParsers::mapToCoordinates);
return ParserDescriptor.of(parser, Coordinates.CoordinatesXZ.class);
}
/**
* A parser for coordinates, relative or absolute, from 3 doubles.
*
* @param centerIntegers whether to center integers at x.5
* @param sender type
* @return a parser descriptor
*/
public static @NonNull ParserDescriptor vec3Parser(final boolean centerIntegers) {
ArgumentParser parser = new WrappedBrigadierParser(Vec3Argument.vec3(centerIntegers))
.flatMapSuccess(VanillaArgumentParsers::mapToCoordinates);
return ParserDescriptor.of(parser, Coordinates.class);
}
@SuppressWarnings("unchecked")
private static @NonNull CompletableFuture<@NonNull ArgumentParseResult> mapToCoordinates(
final @NonNull CommandContext ctx,
final net.minecraft.commands.arguments.coordinates.@NonNull Coordinates posArgument
) {
return requireServer(
ctx,
serverCommandSource -> ArgumentParseResult.successFuture((O) new CoordinatesImpl(
serverCommandSource,
posArgument
))
);
}
/**
* A parser for {@link SinglePlayerSelector}.
*
* @param sender type
* @return a parser descriptor
*/
public static @NonNull ParserDescriptor singlePlayerSelectorParser() {
ArgumentParser parser = new WrappedBrigadierParser(EntityArgument.player())
.flatMapSuccess((ctx, entitySelector) -> requireServer(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(new SinglePlayerSelectorImpl(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.findSinglePlayer(serverCommandSource)
))
)
));
return ParserDescriptor.of(parser, SinglePlayerSelector.class);
}
/**
* A parser for {@link MultiplePlayerSelector}.
*
* @param sender type
* @return a parser descriptor
*/
public static @NonNull ParserDescriptor multiplePlayerSelectorParser() {
ArgumentParser parser = new WrappedBrigadierParser(EntityArgument.players())
.flatMapSuccess((ctx, entitySelector) -> requireServer(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(new MultiplePlayerSelectorImpl(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.findPlayers(serverCommandSource)
))
)
));
return ParserDescriptor.of(parser, MultiplePlayerSelector.class);
}
/**
* A parser for {@link SingleEntitySelector}.
*
* @param sender type
* @return a parser instance
*/
public static @NonNull ParserDescriptor singleEntitySelectorParser() {
ArgumentParser parser = new WrappedBrigadierParser(EntityArgument.entity())
.flatMapSuccess((ctx, entitySelector) -> requireServer(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(new SingleEntitySelectorImpl(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
entitySelector.findSingleEntity(serverCommandSource)
))
)
));
return ParserDescriptor.of(parser, SingleEntitySelector.class);
}
/**
* A parser for {@link MultipleEntitySelector}.
*
* @param sender type
* @return a parser instance
*/
public static @NonNull ParserDescriptor multipleEntitySelectorParser() {
ArgumentParser parser = new WrappedBrigadierParser(EntityArgument.entities())
.flatMapSuccess((ctx, entitySelector) -> requireServer(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(new MultipleEntitySelectorImpl(
((EntitySelectorAccess) entitySelector).inputString(),
entitySelector,
Collections.unmodifiableCollection(entitySelector.findEntities(serverCommandSource))
))
)
));
return ParserDescriptor.of(parser, MultipleEntitySelector.class);
}
/**
* A parser for {@link Message}.
*
* @param sender type
* @return a parser instance
*/
public static @NonNull ParserDescriptor messageParser() {
ArgumentParser parser = new WrappedBrigadierParser(MessageArgument.message())
.flatMapSuccess((ctx, format) -> requireServer(
ctx,
serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
() -> ArgumentParseResult.success(MessageImpl.from(
serverCommandSource,
format,
true
))
)
));
return ParserDescriptor.of(parser, Message.class);
}
@FunctionalInterface
private interface CommandSyntaxExceptionThrowingParseResultSupplier {
@NonNull ArgumentParseResult result() throws CommandSyntaxException;
}
private static @NonNull CompletableFuture> handleCommandSyntaxExceptionAsFailure(
final @NonNull CommandSyntaxExceptionThrowingParseResultSupplier resultSupplier
) {
try {
return CompletableFuture.completedFuture(resultSupplier.result());
} catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failureFuture(ex);
}
}
private static @NonNull IllegalStateException serverOnly() {
return new IllegalStateException("This command argument type is server-only.");
}
private static @NonNull CompletableFuture> requireServer(
final @NonNull CommandContext context,
final @NonNull Function>> resultFunction
) {
final SharedSuggestionProvider nativeSource = context.get(ModdedCommandContextKeys.SHARED_SUGGESTION_PROVIDER);
if (!(nativeSource instanceof CommandSourceStack) || isClientSource(nativeSource)) {
return ArgumentParseResult.failureFuture(serverOnly());
}
return resultFunction.apply((CommandSourceStack) nativeSource);
}
/**
* Checks whether a source is from client commands.
*
* @param sharedSuggestionProvider source
* @return whether the source is from client commands
*/
@API(status = API.Status.INTERNAL)
public static boolean isClientSource(final SharedSuggestionProvider sharedSuggestionProvider) {
// NeoForge extends CommandSourceStack with ClientCommandSourceStack; Fabric has its own SharedSuggestionProvider impl
return !sharedSuggestionProvider.getClass().equals(CommandSourceStack.class);
}
private record MessageImpl(Collection mentionedEntities, Component contents) implements Message {
static MessageImpl from(
final @NonNull CommandSourceStack source,
final MessageArgument.@NonNull Message message,
final boolean useSelectors
) throws CommandSyntaxException {
final Component contents = message.toComponent(source, useSelectors);
final MessageArgument.Part[] selectors = message.parts();
final Collection entities;
if (!useSelectors || selectors.length == 0) {
entities = Collections.emptySet();
} else {
entities = new HashSet<>();
for (final MessageArgument.Part selector : selectors) {
entities.addAll(selector.selector()
.findEntities(source));
}
}
return new MessageImpl(entities, contents);
}
}
private record CoordinatesImpl(CommandSourceStack source, net.minecraft.commands.arguments.coordinates.Coordinates wrappedCoordinates)
implements Coordinates,
Coordinates.CoordinatesXZ,
Coordinates.BlockCoordinates,
Coordinates.ColumnCoordinates {
@Override
public @NonNull Vec3 position() {
return this.wrappedCoordinates.getPosition(this.source);
}
@Override
public @NonNull BlockPos blockPos() {
return BlockPos.containing(this.position());
}
@Override
public boolean isXRelative() {
return this.wrappedCoordinates.isXRelative();
}
@Override
public boolean isYRelative() {
return this.wrappedCoordinates.isYRelative();
}
@Override
public boolean isZRelative() {
return this.wrappedCoordinates.isZRelative();
}
}
private record SingleEntitySelectorImpl(
String inputString, EntitySelector selector, Entity single
) implements SingleEntitySelector {}
private record MultipleEntitySelectorImpl(
String inputString, EntitySelector selector, Collection values
) implements MultipleEntitySelector {}
private record SinglePlayerSelectorImpl(
String inputString, EntitySelector selector, ServerPlayer single
) implements SinglePlayerSelector {}
private record MultiplePlayerSelectorImpl(
String inputString, EntitySelector selector, Collection values
) implements MultiplePlayerSelector {}
}