Paper
Paper copied to clipboard
Expose more argument types
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 toColumnFinePosition) - [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;
})
);
}
}
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
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
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.
Rebased to 1.21.7 and addressed requested changes
Rebased to 1.21.8 (and accidentally removed the lynx branch merge, my bad ^^)
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.
I am for the second option: just a simple wrapper around the axes set. Nice and simple