All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cloud.commandframework.fabric.argument.FabricArgumentParsers Maven / Gradle / Ivy

The newest version!
//
// MIT License
//
// Copyright (c) 2022 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.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.fabric.FabricCommandContextKeys;
import cloud.commandframework.fabric.FabricCommandManager;
import cloud.commandframework.fabric.data.Coordinates;
import cloud.commandframework.fabric.data.Message;
import cloud.commandframework.fabric.data.MinecraftTime;
import cloud.commandframework.fabric.data.MultipleEntitySelector;
import cloud.commandframework.fabric.data.MultiplePlayerSelector;
import cloud.commandframework.fabric.data.SingleEntitySelector;
import cloud.commandframework.fabric.data.SinglePlayerSelector;
import cloud.commandframework.fabric.internal.EntitySelectorAccess;
import cloud.commandframework.fabric.mixin.MessageArgumentMessageAccess;
import cloud.commandframework.fabric.mixin.MessageArgumentPartAccess;
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.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import net.minecraft.class_1297;
import net.minecraft.class_2168;
import net.minecraft.class_2172;
import net.minecraft.class_2186;
import net.minecraft.class_2196;
import net.minecraft.class_2245;
import net.minecraft.class_2262;
import net.minecraft.class_2264;
import net.minecraft.class_2274;
import net.minecraft.class_2277;
import net.minecraft.class_2300;
import net.minecraft.class_2338;
import net.minecraft.class_243;
import net.minecraft.class_2561;
import net.minecraft.class_3222;
import net.minecraft.class_7157;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.ApiStatus;

/**
 * Parsers for Vanilla command argument types.
 *
 * @since 1.5.0
 */
public final class FabricArgumentParsers {

    private FabricArgumentParsers() {
    }

    /**
     * A parser that wraps Brigadier argument types which need a {@link class_7157}
     *
     * @param      sender type
     * @param      argument value type
     * @param factory factory that creates these arguments
     * @return the parser
     */
    public static  @NonNull ArgumentParser contextual(final @NonNull Function> factory) {
        return new WrappedBrigadierParser<>(new ContextualArgumentTypeProvider<>(factory));
    }

    /**
     * A parser for in-game time, in ticks.
     *
     * @param  sender type
     * @return a parser instance
     * @since 1.5.0
     */
    public static  @NonNull ArgumentParser time() {
        return new WrappedBrigadierParser(class_2245.method_9489())
                .map((ctx, val) -> ArgumentParseResult.success(MinecraftTime.of(val)));
    }

    /**
     * A parser for block coordinates.
     *
     * @param  sender type
     * @return a parser instance
     */
    public static  @NonNull ArgumentParser blockPos() {
        return new WrappedBrigadierParser(class_2262.method_9698())
                .map(FabricArgumentParsers::mapToCoordinates);
    }

    /**
     * A parser for column coordinates.
     *
     * @param  sender type
     * @return a parser instance
     */
    public static  @NonNull ArgumentParser columnPos() {
        return new WrappedBrigadierParser(class_2264.method_9701())
                .map(FabricArgumentParsers::mapToCoordinates);
    }

    /**
     * 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 instance
     */
    public static  @NonNull ArgumentParser vec2(final boolean centerIntegers) {
        return new WrappedBrigadierParser(new class_2274(
                centerIntegers))
                .map(FabricArgumentParsers::mapToCoordinates);
    }

    /**
     * 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 instance
     */
    public static  @NonNull ArgumentParser vec3(final boolean centerIntegers) {
        return new WrappedBrigadierParser(class_2277.method_9735(
                centerIntegers))
                .map(FabricArgumentParsers::mapToCoordinates);
    }

    @SuppressWarnings("unchecked")
    private static  @NonNull ArgumentParseResult<@NonNull O> mapToCoordinates(
            final @NonNull CommandContext ctx,
            final net.minecraft.@NonNull class_2267 posArgument
    ) {
        return requireCommandSourceStack(
                ctx,
                serverCommandSource -> ArgumentParseResult.success((O) new CoordinatesImpl(
                        serverCommandSource,
                        posArgument
                ))
        );
    }

    /**
     * A parser for {@link SinglePlayerSelector}.
     *
     * @param  sender type
     * @return a parser instance
     * @since 1.5.0
     */
    public static  @NonNull ArgumentParser singlePlayerSelector() {
        return new WrappedBrigadierParser(class_2186.method_9305())
                .map((ctx, entitySelector) -> requireCommandSourceStack(
                        ctx,
                        serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
                                () -> ArgumentParseResult.success(new SinglePlayerSelectorImpl(
                                        ((EntitySelectorAccess) entitySelector).inputString(),
                                        entitySelector,
                                        entitySelector.method_9811(serverCommandSource)
                                ))
                        )
                ));
    }

    /**
     * A parser for {@link MultiplePlayerSelector}.
     *
     * @param  sender type
     * @return a parser instance
     * @since 1.5.0
     */
    public static  @NonNull ArgumentParser multiplePlayerSelector() {
        return new WrappedBrigadierParser(class_2186.method_9308())
                .map((ctx, entitySelector) -> requireCommandSourceStack(
                        ctx,
                        serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
                                () -> ArgumentParseResult.success(new MultiplePlayerSelectorImpl(
                                        ((EntitySelectorAccess) entitySelector).inputString(),
                                        entitySelector,
                                        entitySelector.method_9813(serverCommandSource)
                                ))
                        )
                ));
    }

    /**
     * A parser for {@link SingleEntitySelector}.
     *
     * @param  sender type
     * @return a parser instance
     * @since 1.5.0
     */
    public static  @NonNull ArgumentParser singleEntitySelector() {
        return new WrappedBrigadierParser(class_2186.method_9309())
                .map((ctx, entitySelector) -> requireCommandSourceStack(
                        ctx,
                        serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
                                () -> ArgumentParseResult.success(new SingleEntitySelectorImpl(
                                        ((EntitySelectorAccess) entitySelector).inputString(),
                                        entitySelector,
                                        entitySelector.method_9809(serverCommandSource)
                                ))
                        )
                ));
    }

    /**
     * A parser for {@link MultipleEntitySelector}.
     *
     * @param  sender type
     * @return a parser instance
     * @since 1.5.0
     */
    public static  @NonNull ArgumentParser multipleEntitySelector() {
        return new WrappedBrigadierParser(class_2186.method_9306())
                .map((ctx, entitySelector) -> requireCommandSourceStack(
                        ctx,
                        serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
                                () -> ArgumentParseResult.success(new MultipleEntitySelectorImpl(
                                        ((EntitySelectorAccess) entitySelector).inputString(),
                                        entitySelector,
                                        Collections.unmodifiableCollection(entitySelector.method_9816(serverCommandSource))
                                ))
                        )
                ));
    }

    /**
     * A parser for {@link Message}.
     *
     * @param  sender type
     * @return a parser instance
     * @since 1.5.0
     */
    public static  @NonNull ArgumentParser message() {
        return new WrappedBrigadierParser(class_2196.method_9340())
                .map((ctx, format) -> requireCommandSourceStack(
                        ctx,
                        serverCommandSource -> handleCommandSyntaxExceptionAsFailure(
                                () -> ArgumentParseResult.success(MessageImpl.from(
                                        serverCommandSource,
                                        format,
                                        true
                                ))
                        )
                ));
    }

    @FunctionalInterface
    private interface CommandSyntaxExceptionThrowingParseResultSupplier {

        @NonNull ArgumentParseResult result() throws CommandSyntaxException;
    }

    private static  @NonNull ArgumentParseResult handleCommandSyntaxExceptionAsFailure(
            final @NonNull CommandSyntaxExceptionThrowingParseResultSupplier resultSupplier
    ) {
        try {
            return resultSupplier.result();
        } catch (final CommandSyntaxException ex) {
            return ArgumentParseResult.failure(ex);
        }
    }

    private static @NonNull IllegalStateException serverOnly() {
        return new IllegalStateException("This command argument type is server-only.");
    }

    private static  @NonNull ArgumentParseResult requireCommandSourceStack(
            final @NonNull CommandContext context,
            final @NonNull Function> resultFunction
    ) {
        final class_2172 nativeSource = context.get(FabricCommandContextKeys.NATIVE_COMMAND_SOURCE);
        if (!(nativeSource instanceof class_2168)) {
            return ArgumentParseResult.failure(serverOnly());
        }
        return resultFunction.apply((class_2168) nativeSource);
    }

    static final class MessageImpl implements Message {

        private final Collection mentionedEntities;
        private final class_2561 contents;

        static MessageImpl from(
                final @NonNull class_2168 source,
                final class_2196.@NonNull class_2197 message,
                final boolean useSelectors
        ) throws CommandSyntaxException {
            final class_2561 contents = message.method_9341(source, useSelectors);
            final class_2196.class_2198[] selectors =
                    ((MessageArgumentMessageAccess) message).accessor$parts();
            final Collection entities;
            if (!useSelectors || selectors.length == 0) {
                entities = Collections.emptySet();
            } else {
                entities = new HashSet<>();
                for (final class_2196.class_2198 selector : selectors) {
                    entities.addAll(((MessageArgumentPartAccess) selector)
                            .accessor$selector()
                            .method_9816(source));
                }
            }

            return new MessageImpl(entities, contents);
        }

        MessageImpl(final Collection mentionedEntities, final class_2561 contents) {
            this.mentionedEntities = mentionedEntities;
            this.contents = contents;
        }

        @Override
        public @NonNull Collection mentionedEntities() {
            return this.mentionedEntities;
        }

        @Override
        public @NonNull class_2561 contents() {
            return this.contents;
        }
    }

    static final class CoordinatesImpl implements Coordinates,
            Coordinates.CoordinatesXZ,
            Coordinates.BlockCoordinates,
            Coordinates.ColumnCoordinates {

        private final class_2168 source;
        private final net.minecraft.class_2267 posArgument;

        CoordinatesImpl(
                final @NonNull class_2168 source,
                final net.minecraft.@NonNull class_2267 posArgument
        ) {
            this.source = source;
            this.posArgument = posArgument;
        }

        @Override
        public @NonNull class_243 position() {
            return this.posArgument.method_9708(this.source);
        }

        @Override
        public @NonNull class_2338 blockPos() {
            return class_2338.method_49638(this.position());
        }

        @Override
        public boolean isXRelative() {
            return this.posArgument.method_9705();
        }

        @Override
        public boolean isYRelative() {
            return this.posArgument.method_9706();
        }

        @Override
        public boolean isZRelative() {
            return this.posArgument.method_9707();
        }

        @Override
        public net.minecraft.@NonNull class_2267 wrappedCoordinates() {
            return this.posArgument;
        }
    }

    static final class SingleEntitySelectorImpl implements SingleEntitySelector {

        private final String inputString;
        private final class_2300 entitySelector;
        private final class_1297 selectedEntity;

        SingleEntitySelectorImpl(
                final @NonNull String inputString,
                final @NonNull class_2300 entitySelector,
                final @NonNull class_1297 selectedEntity
        ) {
            this.inputString = inputString;
            this.entitySelector = entitySelector;
            this.selectedEntity = selectedEntity;
        }

        @Override
        public @NonNull String inputString() {
            return this.inputString;
        }

        @Override
        public @NonNull class_2300 selector() {
            return this.entitySelector;
        }

        @Override
        public @NonNull class_1297 getSingle() {
            return this.selectedEntity;
        }
    }

    static final class MultipleEntitySelectorImpl implements MultipleEntitySelector {

        private final String inputString;
        private final class_2300 entitySelector;
        private final Collection selectedEntities;

        MultipleEntitySelectorImpl(
                final @NonNull String inputString,
                final @NonNull class_2300 entitySelector,
                final @NonNull Collection selectedEntities
        ) {
            this.inputString = inputString;
            this.entitySelector = entitySelector;
            this.selectedEntities = selectedEntities;
        }

        @Override
        public @NonNull String inputString() {
            return this.inputString;
        }

        @Override
        public @NonNull class_2300 selector() {
            return this.entitySelector;
        }

        @Override
        public @NonNull Collection get() {
            return this.selectedEntities;
        }
    }

    static final class SinglePlayerSelectorImpl implements SinglePlayerSelector {

        private final String inputString;
        private final class_2300 entitySelector;
        private final class_3222 selectedPlayer;

        SinglePlayerSelectorImpl(
                final @NonNull String inputString,
                final @NonNull class_2300 entitySelector,
                final @NonNull class_3222 selectedPlayer
        ) {
            this.inputString = inputString;
            this.entitySelector = entitySelector;
            this.selectedPlayer = selectedPlayer;
        }

        @Override
        public @NonNull String inputString() {
            return this.inputString;
        }

        @Override
        public @NonNull class_2300 selector() {
            return this.entitySelector;
        }

        @Override
        public @NonNull class_3222 getSingle() {
            return this.selectedPlayer;
        }
    }

    static final class MultiplePlayerSelectorImpl implements MultiplePlayerSelector {

        private final String inputString;
        private final class_2300 entitySelector;
        private final Collection selectedPlayers;

        MultiplePlayerSelectorImpl(
                final @NonNull String inputString,
                final @NonNull class_2300 entitySelector,
                final @NonNull Collection selectedPlayers
        ) {
            this.inputString = inputString;
            this.entitySelector = entitySelector;
            this.selectedPlayers = selectedPlayers;
        }

        @Override
        public @NonNull String inputString() {
            return this.inputString;
        }

        @Override
        public @NonNull class_2300 selector() {
            return this.entitySelector;
        }

        @Override
        public @NonNull Collection get() {
            return this.selectedPlayers;
        }
    }

    @ApiStatus.Internal
    public static final class ContextualArgumentTypeProvider implements Supplier> {

        private static final ThreadLocal CONTEXT = new ThreadLocal<>();
        private static final Map, Set>> INSTANCES =
                new WeakHashMap<>();

        private final Function> provider;
        private volatile ArgumentType provided;

        /**
         * Temporarily expose a command build context to providers called from this thread.
         *
         * @param ctx            the context
         * @param commandManager command manager to use
         * @param resetExisting  whether to clear cached state from existing provider instances for this command type
         * @param action         an action to perform while the context is exposed
         * @since 1.7.0
         */
        public static void withBuildContext(
                final FabricCommandManager commandManager,
                final class_7157 ctx,
                final boolean resetExisting,
                final Runnable action
        ) {
            final ThreadLocalContext context = new ThreadLocalContext(commandManager, ctx);
            CONTEXT.set(context);

            try {
                if (resetExisting) {
                    synchronized (INSTANCES) {
                        for (final ContextualArgumentTypeProvider contextualArgumentTypeProvider : context.instances()) {
                            contextualArgumentTypeProvider.provided = null;
                        }
                    }
                }

                action.run();
            } finally {
                CONTEXT.remove();
            }
        }

        private static final class ThreadLocalContext {

            private final FabricCommandManager commandManager;
            private final class_7157 commandBuildContext;

            private ThreadLocalContext(
                    final FabricCommandManager commandManager,
                    final class_7157 commandBuildContext
            ) {
                this.commandManager = commandManager;
                this.commandBuildContext = commandBuildContext;
            }

            private Set> instances() {
                return INSTANCES.computeIfAbsent(this.commandManager, $ -> Collections.newSetFromMap(new WeakHashMap<>()));
            }
        }

        ContextualArgumentTypeProvider(final @NonNull Function> provider) {
            this.provider = provider;
        }

        @Override
        public ArgumentType get() {
            final ThreadLocalContext ctx = CONTEXT.get();

            if (ctx != null) {
                synchronized (INSTANCES) {
                    ctx.instances().add(this);
                }
            }

            ArgumentType provided = this.provided;
            if (provided == null) {
                synchronized (this) {
                    if (this.provided == null) {
                        if (ctx == null) {
                            throw new IllegalStateException(
                                    "No build context was available while trying to compute an argument type");
                        }
                        provided = this.provider.apply(ctx.commandBuildContext);
                        this.provided = provided;
                    }
                }
            }
            return provided;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy