Paper icon indicating copy to clipboard operation
Paper copied to clipboard

Expose more argument types

Open Strokkur424 opened this issue 5 months ago • 1 comments

This PR aims to expose the following argument types:

  • [x] Angle
  • [x] Swizzle
  • [x] Block predicate
  • ~~Particle (custom data is currently broken)~~ Out of scope
  • [x] Vec2 (in form of a new ~~Vec2FinePosition~~. Renamed to ColumnFinePosition)
  • [x] ColumnPos (in form of a new ColumnBlockPosition)

~~The code to test it currently committed under TestPlugin.java, please remove that on merge.~~ No longer commited. For documentation purposes, the current state of the testing code (which includes example usage) is this:

TestPlugin.java
package io.papermc.testplugin;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.Commands;
import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
import io.papermc.paper.command.brigadier.argument.predicate.BlockPredicate;
import io.papermc.paper.command.brigadier.argument.resolvers.AngleResolver;
import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver;
import io.papermc.paper.command.brigadier.argument.resolvers.ColumnBlockPositionResolver;
import io.papermc.paper.command.brigadier.argument.resolvers.ColumnFinePositionResolver;
import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver;
import io.papermc.paper.math.BlockPosition;
import io.papermc.paper.math.ColumnBlockPosition;
import io.papermc.paper.math.ColumnFinePosition;
import io.papermc.paper.math.FinePosition;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import java.util.EnumSet;
import java.util.Set;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Axis;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;

public final class TestPlugin extends JavaPlugin implements Listener {

    @Override
    public void onEnable() {
        this.getServer().getPluginManager().registerEvents(this, this);

        Set<LiteralArgumentBuilder<CommandSourceStack>> commands = Set.of(
            angle(), swizzle(), blockPredicate(), particle(), columnFine(), columnBlock()
        );

        this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS.newHandler(
            event -> commands.forEach(builder -> event.registrar().register(builder.build()))
        ));
    }

    private LiteralArgumentBuilder<CommandSourceStack> angle() {
        return Commands.literal("angle")
            .then(Commands.argument("angle", ArgumentTypes.angle())
                .executes(ctx -> {
                    float angle = ctx.getArgument("angle", AngleResolver.class).resolve(ctx.getSource());
                    ctx.getSource().getSender().sendRichMessage("Your selected angle is <aqua>:" + angle);
                    return 1;
                })
            );
    }

    private LiteralArgumentBuilder<CommandSourceStack> swizzle() {
        return Commands.literal("swizzle")
            .then(Commands.argument("swizzle", ArgumentTypes.swizzle())
                .executes(ctx -> {
                    EnumSet<Axis> axes = (EnumSet<Axis>) ctx.getArgument("swizzle", EnumSet.class);

                    StringBuilder builder = new StringBuilder();
                    if (axes.contains(Axis.X)) {
                        builder.append("<red>X ");
                    }
                    if (axes.contains(Axis.Y)) {
                        builder.append("<green>Y ");
                    }
                    if (axes.contains(Axis.Z)) {
                        builder.append("<blue>Z");
                    }

                    String axesString = builder.toString().trim();
                    ctx.getSource().getSender().sendRichMessage("You selected: <axes>",
                        Placeholder.parsed("axes", axesString.isEmpty() ? "<gray>None" : axesString)
                    );
                    return 1;
                })
            );
    }

    private LiteralArgumentBuilder<CommandSourceStack> blockPredicate() {
        return Commands.literal("blockpredicate")
            .then(Commands.argument("position", ArgumentTypes.blockPosition())
                .then(Commands.argument("blockpredicate", ArgumentTypes.blockPredicate())
                    .executes(ctx -> {
                        BlockPredicate predicate = ctx.getArgument("blockpredicate", BlockPredicate.class);
                        BlockPosition position = ctx.getArgument("position", BlockPositionResolver.class).resolve(ctx.getSource());
                        World world = ctx.getSource().getLocation().getWorld();

                        if (predicate.test(position.toLocation(world).getBlock())) {
                            ctx.getSource().getSender().sendRichMessage("<green>Obviously that block matches, lol.");
                        } else {
                            ctx.getSource().getSender().sendRichMessage("<red>Why would that match. Use your eyes ☠️");
                        }

                        return 1;
                    })
                )
            );
    }

    private LiteralArgumentBuilder<CommandSourceStack> particle() {
        return Commands.literal("particle")
            .then(Commands.argument("particle", ArgumentTypes.particle())
                .then(Commands.argument("position", ArgumentTypes.finePosition(true))
                    .executes(ctx -> {
                        Particle particle = ctx.getArgument("particle", Particle.class);
                        FinePosition position = ctx.getArgument("position", FinePositionResolver.class).resolve(ctx.getSource());
                        World world = ctx.getSource().getLocation().getWorld();

                        // TODO: Fix particles
                        world.spawnParticle(particle, position.toLocation(world), 10);
                        return 1;
                    })
                )
            );
    }

    private LiteralArgumentBuilder<CommandSourceStack> columnFine() {
        return Commands.literal("columnfine")
            .then(Commands.argument("columnfine", ArgumentTypes.columnFinePosition())
                .executes(ctx -> {
                    ColumnFinePosition pos = ctx.getArgument("columnfine", ColumnFinePositionResolver.class).resolve(ctx.getSource());
                    ctx.getSource().getSender().sendRichMessage("The position you selected is at x: <red><x></red> z: <blue><z></blue>",
                        Placeholder.unparsed("x", Double.toString(pos.x())),
                        Placeholder.unparsed("z", Double.toString(pos.z()))
                    );
                    return 1;
                })
            );
    }

    private LiteralArgumentBuilder<CommandSourceStack> columnBlock() {
        return Commands.literal("columnblock")
            .then(Commands.argument("columnblock", ArgumentTypes.columnBlockPosition())
                .executes(ctx -> {
                    ColumnBlockPosition pos = ctx.getArgument("columnblock", ColumnBlockPositionResolver.class).resolve(ctx.getSource());
                    ctx.getSource().getSender().sendRichMessage("The position you selected is at x: <red><x></red> z: <blue><z></blue>",
                        Placeholder.unparsed("x", Double.toString(pos.x())),
                        Placeholder.unparsed("z", Double.toString(pos.z()))
                    );
                    return 1;
                })
            );
    }
}

Strokkur424 avatar Jun 13 '25 21:06 Strokkur424

Because of the particle type being very hacky, I decided to move it out of this PR for now. For documentation purposes, this is the patch for the particle argument type: particles.patch

Strokkur424 avatar Jun 21 '25 19:06 Strokkur424

The reason for making the column ones extend position so that they can be used for... something. Because adding a new type which basically does nothing but hold two numbers, but you cannot do anything with that class, is kinda weird in my opinion.

Then again, there is no API that I can think of which would deeply yearn for not having a y variable, so idk. Maybe some Location getLocationAt(double y) on those so that you can at least retrieve a location easily could be useful as a utility? No clue

Strokkur424 avatar Jun 24 '25 15:06 Strokkur424

They should not extend FinePosition or Position or be related in any way. If we want to add types for this, it should literally just be a separate interface with 2 methods to get the x and z. Don't need methods to mutate or anything. Cause like you said, I don't think its more useful than just as way to get values from the argument.

Machine-Maker avatar Jun 26 '25 03:06 Machine-Maker

Rebased to 1.21.7 and addressed requested changes

Strokkur424 avatar Jul 12 '25 12:07 Strokkur424

Rebased to 1.21.8 (and accidentally removed the lynx branch merge, my bad ^^)

Strokkur424 avatar Jul 17 '25 17:07 Strokkur424

So, the only thing that isn't very fun about this is the nested generic that Set<Axis> introduces as an argument type.

getArgument("key", Set.class) does not yield something usable unless unchecked assigned / raw types. Now, we have io.papermc.paper.command.brigadier.argument.RegistryArgumentExtractor which exists because TypedKey<T> is an argument, so that is inherently unchecked, we could extend/rename this.

We could however also introduce a AxisSetArgument with a simple toSet method on it.

Don't really have much of a preference yet so, input welcome.

lynxplay avatar Aug 23 '25 19:08 lynxplay

I am for the second option: just a simple wrapper around the axes set. Nice and simple

Strokkur424 avatar Aug 23 '25 19:08 Strokkur424