From b98d20a8ac9c21789d532652df86638a202093c7 Mon Sep 17 00:00:00 2001 From: Owen <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 11 May 2024 16:30:30 -0400 Subject: [PATCH] Brigadier Command Support (#8235) Adds the ability for plugins to register their own brigadier commands --------- Co-authored-by: Jake Potrebic Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Co-authored-by: Bjarne Koll --- Paper-MojangAPI/build.gradle.kts | 36 - .../brigadier/BukkitBrigadierCommand.java | 14 - .../BukkitBrigadierCommandSource.java | 21 - .../AsyncPlayerSendCommandsEvent.java | 72 - .../AsyncPlayerSendSuggestionsEvent.java | 84 - .../brigadier/CommandRegisteredEvent.java | 168 - .../paper/brigadier/PaperBrigadier.java | 42 - .../brigadier/PaperBrigadierProvider.java | 30 - build.gradle.kts | 3 +- .../0480-Brigadier-based-command-API.patch | 1900 +++++++++++ patches/server/0020-Plugin-remapping.patch | 18 +- .../0092-Configurable-Player-Collision.patch | 4 +- ...dd-Early-Warning-Feature-to-WatchDog.patch | 6 +- .../server/0282-Brigadier-Mojang-API.patch | 12 - .../server/0362-Implement-Mob-Goal-API.patch | 6 +- ...612-Vanilla-command-permission-fixes.patch | 12 +- .../0722-Add-support-for-Proxy-Protocol.patch | 4 +- ...ocity-compression-and-cipher-natives.patch | 4 +- ...d-experimental-improved-give-command.patch | 8 +- .../1050-Brigadier-based-command-API.patch | 2770 +++++++++++++++++ settings.gradle.kts | 2 +- test-plugin/build.gradle.kts | 1 - .../io/papermc/testplugin/TestPlugin.java | 3 + .../testplugin/TestPluginBootstrap.java | 1 + .../testplugin/brigtests/Registration.java | 166 + .../ComponentCommandExceptionType.java | 25 + .../example/ExampleAdminCommand.java | 154 + .../brigtests/example/IceCreamType.java | 9 + .../example/IceCreamTypeArgument.java | 47 + .../example/MaterialArgumentType.java | 88 + 30 files changed, 5195 insertions(+), 515 deletions(-) delete mode 100644 Paper-MojangAPI/build.gradle.kts delete mode 100644 Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java delete mode 100644 Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java delete mode 100644 Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java delete mode 100644 Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java delete mode 100644 Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java delete mode 100644 Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java delete mode 100644 Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProvider.java create mode 100644 patches/api/0480-Brigadier-based-command-API.patch create mode 100644 patches/server/1050-Brigadier-based-command-API.patch create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ComponentCommandExceptionType.java create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ExampleAdminCommand.java create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamType.java create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamTypeArgument.java create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/MaterialArgumentType.java diff --git a/Paper-MojangAPI/build.gradle.kts b/Paper-MojangAPI/build.gradle.kts deleted file mode 100644 index e60be45e25..0000000000 --- a/Paper-MojangAPI/build.gradle.kts +++ /dev/null @@ -1,36 +0,0 @@ -plugins { - `java-library` - `maven-publish` -} - -java { - withSourcesJar() - withJavadocJar() -} - -dependencies { - implementation(project(":paper-api")) - api("com.mojang:brigadier:1.0.18") - - compileOnly("it.unimi.dsi:fastutil:8.5.6") - compileOnly("org.jetbrains:annotations:23.0.0") - - testImplementation("junit:junit:4.13.2") - testImplementation("org.hamcrest:hamcrest-library:1.3") - testImplementation("org.ow2.asm:asm-tree:9.7") -} - -configure { - publications.create("maven") { - from(components["java"]) - } -} - -val scanJar = tasks.register("scanJarForBadCalls", io.papermc.paperweight.tasks.ScanJarForBadCalls::class) { - badAnnotations.add("Lio/papermc/paper/annotation/DoNotUse;") - jarToScan.set(tasks.jar.flatMap { it.archiveFile }) - classpath.from(configurations.compileClasspath) -} -tasks.check { - dependsOn(scanJar) -} diff --git a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java b/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java deleted file mode 100644 index 0b1af3a8d4..0000000000 --- a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.destroystokyo.paper.brigadier; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.suggestion.SuggestionProvider; - -import java.util.function.Predicate; - -/** - * Brigadier {@link Command}, {@link SuggestionProvider}, and permission checker for Bukkit {@link Command}s. - * - * @param command source type - */ -public interface BukkitBrigadierCommand extends Command, Predicate, SuggestionProvider { -} diff --git a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java b/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java deleted file mode 100644 index 7a0e81658c..0000000000 --- a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.destroystokyo.paper.brigadier; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; -import org.jetbrains.annotations.Nullable; - -public interface BukkitBrigadierCommandSource { - - @Nullable - Entity getBukkitEntity(); - - @Nullable - World getBukkitWorld(); - - @Nullable - Location getBukkitLocation(); - - CommandSender getBukkitSender(); -} diff --git a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java b/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java deleted file mode 100644 index 495b0f0d2f..0000000000 --- a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.destroystokyo.paper.event.brigadier; - -import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; -import com.mojang.brigadier.tree.RootCommandNode; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.HandlerList; -import org.bukkit.event.player.PlayerEvent; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** - * Fired any time a Brigadier RootCommandNode is generated for a player to inform the client of commands. - * You may manipulate this CommandNode to change what the client sees. - * - *

This event may fire on login, world change, and permission rebuilds, by plugin request, and potentially future means.

- * - *

This event will fire before {@link org.bukkit.event.player.PlayerCommandSendEvent}, so no filtering has been done by - * other plugins yet.

- * - *

WARNING: This event will potentially (and most likely) fire twice! Once for Async, and once again for Sync. - * It is important that you check event.isAsynchronous() and event.hasFiredAsync() to ensure you only act once. - * If for some reason we are unable to send this asynchronously in the future, only the sync method will fire.

- * - *

Your logic should look like this: - * {@code if (event.isAsynchronous() || !event.hasFiredAsync()) { // do stuff }}

- * - *

If your logic is not safe to run asynchronously, only react to the synchronous version.

- * - *

This is a draft/experimental API and is subject to change.

- */ -@ApiStatus.Experimental -public class AsyncPlayerSendCommandsEvent extends PlayerEvent { - - private static final HandlerList handlers = new HandlerList(); - private final RootCommandNode node; - private final boolean hasFiredAsync; - - public AsyncPlayerSendCommandsEvent(Player player, RootCommandNode node, boolean hasFiredAsync) { - super(player, !Bukkit.isPrimaryThread()); - this.node = node; - this.hasFiredAsync = hasFiredAsync; - } - - /** - * Gets the full Root Command Node being sent to the client, which is mutable. - * - * @return the root command node - */ - public RootCommandNode getCommandNode() { - return node; - } - - /** - * Gets if this event has already fired asynchronously. - * - * @return whether this event has already fired asynchronously - */ - public boolean hasFiredAsync() { - return hasFiredAsync; - } - - @NotNull - public HandlerList getHandlers() { - return handlers; - } - - @NotNull - public static HandlerList getHandlerList() { - return handlers; - } -} diff --git a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java b/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java deleted file mode 100644 index 4755ab24a6..0000000000 --- a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.destroystokyo.paper.event.brigadier; - -import com.mojang.brigadier.suggestion.Suggestions; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.bukkit.event.player.PlayerEvent; -import org.jetbrains.annotations.NotNull; - -/** - * Called when sending {@link Suggestions} to the client. Will be called asynchronously if a plugin - * marks the {@link com.destroystokyo.paper.event.server.AsyncTabCompleteEvent} event handled asynchronously, - * otherwise called synchronously. - */ -public class AsyncPlayerSendSuggestionsEvent extends PlayerEvent implements Cancellable { - - private static final HandlerList handlers = new HandlerList(); - private boolean cancelled = false; - - private Suggestions suggestions; - private final String buffer; - - public AsyncPlayerSendSuggestionsEvent(Player player, Suggestions suggestions, String buffer) { - super(player, !Bukkit.isPrimaryThread()); - this.suggestions = suggestions; - this.buffer = buffer; - } - - /** - * Gets the input buffer sent to request these suggestions. - * - * @return the input buffer - */ - public String getBuffer() { - return buffer; - } - - /** - * Gets the suggestions to be sent to client. - * - * @return the suggestions - */ - public Suggestions getSuggestions() { - return suggestions; - } - - /** - * Sets the suggestions to be sent to client. - * - * @param suggestions suggestions - */ - public void setSuggestions(Suggestions suggestions) { - this.suggestions = suggestions; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isCancelled() { - return this.cancelled; - } - - /** - * Cancels sending suggestions to the client. - * {@inheritDoc} - */ - @Override - public void setCancelled(boolean cancel) { - this.cancelled = cancel; - } - - @NotNull - public HandlerList getHandlers() { - return handlers; - } - - @NotNull - public static HandlerList getHandlerList() { - return handlers; - } -} diff --git a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java b/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java deleted file mode 100644 index eb0409d816..0000000000 --- a/Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.destroystokyo.paper.event.brigadier; - -import com.destroystokyo.paper.brigadier.BukkitBrigadierCommand; -import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; -import com.mojang.brigadier.tree.ArgumentCommandNode; -import com.mojang.brigadier.tree.LiteralCommandNode; -import com.mojang.brigadier.tree.RootCommandNode; -import org.bukkit.command.Command; -import org.bukkit.event.Cancellable; -import org.bukkit.event.HandlerList; -import org.bukkit.event.server.ServerEvent; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** - * Fired anytime the server synchronizes Bukkit commands to Brigadier. - * - *

Allows a plugin to control the command node structure for its commands. - * This is done at Plugin Enable time after commands have been registered, but may also - * run at a later point in the server lifetime due to plugins, a server reload, etc.

- * - *

This is a draft/experimental API and is subject to change.

- */ -@ApiStatus.Experimental -public class CommandRegisteredEvent extends ServerEvent implements Cancellable { - - private static final HandlerList handlers = new HandlerList(); - private final String commandLabel; - private final Command command; - private final BukkitBrigadierCommand brigadierCommand; - private final RootCommandNode root; - private final ArgumentCommandNode defaultArgs; - private LiteralCommandNode literal; - private boolean rawCommand = false; - private boolean cancelled = false; - - public CommandRegisteredEvent(String commandLabel, BukkitBrigadierCommand brigadierCommand, Command command, RootCommandNode root, LiteralCommandNode literal, ArgumentCommandNode defaultArgs) { - this.commandLabel = commandLabel; - this.brigadierCommand = brigadierCommand; - this.command = command; - this.root = root; - this.literal = literal; - this.defaultArgs = defaultArgs; - } - - /** - * Gets the command label of the {@link Command} being registered. - * - * @return the command label - */ - public String getCommandLabel() { - return this.commandLabel; - } - - /** - * Gets the {@link BukkitBrigadierCommand} for the {@link Command} being registered. This can be used - * as the {@link com.mojang.brigadier.Command command executor} or - * {@link com.mojang.brigadier.suggestion.SuggestionProvider} of a {@link com.mojang.brigadier.tree.CommandNode} - * to delegate to the {@link Command} being registered. - * - * @return the {@link BukkitBrigadierCommand} - */ - public BukkitBrigadierCommand getBrigadierCommand() { - return this.brigadierCommand; - } - - /** - * Gets the {@link Command} being registered. - * - * @return the {@link Command} - */ - public Command getCommand() { - return this.command; - } - - /** - * Gets the {@link RootCommandNode} which is being registered to. - * - * @return the {@link RootCommandNode} - */ - public RootCommandNode getRoot() { - return this.root; - } - - /** - * Gets the Bukkit APIs default arguments node (greedy string), for if - * you wish to reuse it. - * - * @return default arguments node - */ - public ArgumentCommandNode getDefaultArgs() { - return this.defaultArgs; - } - - /** - * Gets the {@link LiteralCommandNode} to be registered for the {@link Command}. - * - * @return the {@link LiteralCommandNode} - */ - public LiteralCommandNode getLiteral() { - return this.literal; - } - - /** - * Sets the {@link LiteralCommandNode} used to register this command. The default literal is mutable, so - * this is primarily if you want to completely replace the object. - * - * @param literal new node - */ - public void setLiteral(LiteralCommandNode literal) { - this.literal = literal; - } - - /** - * Gets whether this command should is treated as "raw". - * - * @see #setRawCommand(boolean) - * @return whether this command is treated as "raw" - */ - public boolean isRawCommand() { - return this.rawCommand; - } - - /** - * Sets whether this command should be treated as "raw". - * - *

A "raw" command will only use the node provided by this event for - * sending the command tree to the client. For execution purposes, the default - * greedy string execution of a standard Bukkit {@link Command} is used.

- * - *

On older versions of Paper, this was the default and only behavior of this - * event.

- * - * @param rawCommand whether this command should be treated as "raw" - */ - public void setRawCommand(final boolean rawCommand) { - this.rawCommand = rawCommand; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isCancelled() { - return this.cancelled; - } - - /** - * Cancels registering this command to Brigadier, but will remain in Bukkit Command Map. Can be used to hide a - * command from all players. - * - * {@inheritDoc} - */ - @Override - public void setCancelled(boolean cancel) { - this.cancelled = cancel; - } - - @NotNull - public HandlerList getHandlers() { - return handlers; - } - - @NotNull - public static HandlerList getHandlerList() { - return handlers; - } -} diff --git a/Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java b/Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java deleted file mode 100644 index 1ed5a6d271..0000000000 --- a/Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.papermc.paper.brigadier; - -import com.mojang.brigadier.Message; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.ComponentLike; -import net.kyori.adventure.text.TextComponent; -import org.checkerframework.checker.nullness.qual.NonNull; - -/** - * Helper methods to bridge the gaps between Brigadier and Paper-MojangAPI. - */ -public final class PaperBrigadier { - private PaperBrigadier() { - throw new RuntimeException("PaperBrigadier is not to be instantiated!"); - } - - /** - * Create a new Brigadier {@link Message} from a {@link ComponentLike}. - * - *

Mostly useful for creating rich suggestion tooltips in combination with other Paper-MojangAPI APIs.

- * - * @param componentLike The {@link ComponentLike} to use for the {@link Message} contents - * @return A new Brigadier {@link Message} - */ - public static @NonNull Message message(final @NonNull ComponentLike componentLike) { - return PaperBrigadierProvider.instance().message(componentLike); - } - - /** - * Create a new {@link Component} from a Brigadier {@link Message}. - * - *

If the {@link Message} was created from a {@link Component}, it will simply be - * converted back, otherwise a new {@link TextComponent} will be created with the - * content of {@link Message#getString()}

- * - * @param message The {@link Message} to create a {@link Component} from - * @return The created {@link Component} - */ - public static @NonNull Component componentFromMessage(final @NonNull Message message) { - return PaperBrigadierProvider.instance().componentFromMessage(message); - } -} diff --git a/Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProvider.java b/Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProvider.java deleted file mode 100644 index 7f24806384..0000000000 --- a/Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.papermc.paper.brigadier; - -import com.mojang.brigadier.Message; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.ComponentLike; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; - -import static java.util.Objects.requireNonNull; - -interface PaperBrigadierProvider { - final class Holder { - private static @MonotonicNonNull PaperBrigadierProvider INSTANCE; - } - - static @NonNull PaperBrigadierProvider instance() { - return requireNonNull(Holder.INSTANCE, "PaperBrigadierProvider has not yet been initialized!"); - } - - static void initialize(final @NonNull PaperBrigadierProvider instance) { - if (Holder.INSTANCE != null) { - throw new IllegalStateException("PaperBrigadierProvider has already been initialized!"); - } - Holder.INSTANCE = instance; - } - - @NonNull Message message(@NonNull ComponentLike componentLike); - - @NonNull Component componentFromMessage(@NonNull Message message); -} diff --git a/build.gradle.kts b/build.gradle.kts index 6ff60215b6..c5d7f953fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ import kotlin.io.path.* plugins { java `maven-publish` - id("io.papermc.paperweight.core") version "1.6.3" + id("io.papermc.paperweight.core") version "1.7.1" } allprojects { @@ -109,7 +109,6 @@ paperweight { tasks.generateDevelopmentBundle { apiCoordinates = "io.papermc.paper:paper-api" - mojangApiCoordinates = "io.papermc.paper:paper-mojangapi" libraryRepositories.addAll( "https://repo.maven.apache.org/maven2/", paperMavenPublicUrl, diff --git a/patches/api/0480-Brigadier-based-command-API.patch b/patches/api/0480-Brigadier-based-command-API.patch new file mode 100644 index 0000000000..911d9fb384 --- /dev/null +++ b/patches/api/0480-Brigadier-based-command-API.patch @@ -0,0 +1,1900 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 1 Aug 2022 22:50:29 -0400 +Subject: [PATCH] Brigadier based command API + + +diff --git a/build.gradle.kts b/build.gradle.kts +index eecf458e1250ee9968630cf5c3c3287a1693e52e..fd39ed209b20c927054b8482c400beeeeab460a3 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -27,6 +27,7 @@ configurations.api { + } + + dependencies { ++ api("com.mojang:brigadier:1.2.9") // Paper - Brigadier command api + // api dependencies are listed transitively to API consumers + api("com.google.guava:guava:32.1.2-jre") + api("com.google.code.gson:gson:2.10.1") +@@ -92,9 +93,29 @@ sourceSets { + } + } + // Paper end ++// Paper start - brigadier API ++val outgoingVariants = arrayOf("runtimeElements", "apiElements", "sourcesElements", "javadocElements") ++configurations { ++ val outgoing = outgoingVariants.map { named(it) } ++ for (config in outgoing) { ++ config { ++ outgoing { ++ capability("${project.group}:${project.name}:${project.version}") ++ capability("io.papermc.paper:paper-mojangapi:${project.version}") ++ capability("com.destroystokyo.paper:paper-mojangapi:${project.version}") ++ } ++ } ++ } ++} ++// Paper end + + configure { + publications.create("maven") { ++ // Paper start - brigadier API ++ outgoingVariants.forEach { ++ suppressPomMetadataWarningsFor(it) ++ } ++ // Paper end + from(components["java"]) + } + } +diff --git a/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java b/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..03a1078446f84b998cd7fe8d64abecb2e36bab0a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java +@@ -0,0 +1,16 @@ ++package com.destroystokyo.paper.brigadier; ++ ++import com.mojang.brigadier.Command; ++import com.mojang.brigadier.suggestion.SuggestionProvider; ++ ++import java.util.function.Predicate; ++ ++/** ++ * Brigadier {@link Command}, {@link SuggestionProvider}, and permission checker for Bukkit {@link Command}s. ++ * ++ * @param command source type ++ * @deprecated For removal, see {@link io.papermc.paper.command.brigadier.Commands} on how to use the new Brigadier API. ++ */ ++@Deprecated(forRemoval = true, since = "1.20.6") ++public interface BukkitBrigadierCommand extends Command, Predicate, SuggestionProvider { ++} +diff --git a/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java b/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..28b44789e3be586c4b680fff56e5d2ff095f9ac2 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java +@@ -0,0 +1,25 @@ ++package com.destroystokyo.paper.brigadier; ++ ++import org.bukkit.Location; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Entity; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * @deprecated For removal, see {@link io.papermc.paper.command.brigadier.Commands} on how to use the new Brigadier API. ++ */ ++@Deprecated(forRemoval = true) ++public interface BukkitBrigadierCommandSource { ++ ++ @Nullable ++ Entity getBukkitEntity(); ++ ++ @Nullable ++ World getBukkitWorld(); ++ ++ @Nullable ++ Location getBukkitLocation(); ++ ++ CommandSender getBukkitSender(); ++} +diff --git a/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a56ab5a031f8e254bf4e5ea063df0fad2e585206 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java +@@ -0,0 +1,72 @@ ++package com.destroystokyo.paper.event.brigadier; ++ ++import com.mojang.brigadier.tree.RootCommandNode; ++import org.bukkit.Bukkit; ++import org.bukkit.entity.Player; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.player.PlayerEvent; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Fired any time a Brigadier RootCommandNode is generated for a player to inform the client of commands. ++ * You may manipulate this CommandNode to change what the client sees. ++ * ++ *

This event may fire on login, world change, and permission rebuilds, by plugin request, and potentially future means.

++ * ++ *

This event will fire before {@link org.bukkit.event.player.PlayerCommandSendEvent}, so no filtering has been done by ++ * other plugins yet.

++ * ++ *

WARNING: This event will potentially (and most likely) fire twice! Once for Async, and once again for Sync. ++ * It is important that you check event.isAsynchronous() and event.hasFiredAsync() to ensure you only act once. ++ * If for some reason we are unable to send this asynchronously in the future, only the sync method will fire.

++ * ++ *

Your logic should look like this: ++ * {@code if (event.isAsynchronous() || !event.hasFiredAsync()) { // do stuff }}

++ * ++ *

If your logic is not safe to run asynchronously, only react to the synchronous version.

++ * ++ *

This is a draft/experimental API and is subject to change.

++ */ ++@ApiStatus.Experimental ++public class AsyncPlayerSendCommandsEvent extends PlayerEvent { ++ ++ private static final HandlerList handlers = new HandlerList(); ++ private final RootCommandNode node; ++ private final boolean hasFiredAsync; ++ ++ @ApiStatus.Internal ++ public AsyncPlayerSendCommandsEvent(@NotNull Player player, @NotNull RootCommandNode node, boolean hasFiredAsync) { ++ super(player, !Bukkit.isPrimaryThread()); ++ this.node = node; ++ this.hasFiredAsync = hasFiredAsync; ++ } ++ ++ /** ++ * Gets the full Root Command Node being sent to the client, which is mutable. ++ * ++ * @return the root command node ++ */ ++ public @NotNull RootCommandNode getCommandNode() { ++ return node; ++ } ++ ++ /** ++ * Gets if this event has already fired asynchronously. ++ * ++ * @return whether this event has already fired asynchronously ++ */ ++ public boolean hasFiredAsync() { ++ return hasFiredAsync; ++ } ++ ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6ac205de582983863bd5b3c0fa70d4375dd751c5 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java +@@ -0,0 +1,85 @@ ++package com.destroystokyo.paper.event.brigadier; ++ ++import com.mojang.brigadier.suggestion.Suggestions; ++import org.bukkit.Bukkit; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.player.PlayerEvent; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Called when sending {@link Suggestions} to the client. Will be called asynchronously if a plugin ++ * marks the {@link com.destroystokyo.paper.event.server.AsyncTabCompleteEvent} event handled asynchronously, ++ * otherwise called synchronously. ++ */ ++public class AsyncPlayerSendSuggestionsEvent extends PlayerEvent implements Cancellable { ++ ++ private static final HandlerList handlers = new HandlerList(); ++ private boolean cancelled = false; ++ ++ private Suggestions suggestions; ++ private final String buffer; ++ ++ @ApiStatus.Internal ++ public AsyncPlayerSendSuggestionsEvent(@NotNull Player player, @NotNull Suggestions suggestions, @NotNull String buffer) { ++ super(player, !Bukkit.isPrimaryThread()); ++ this.suggestions = suggestions; ++ this.buffer = buffer; ++ } ++ ++ /** ++ * Gets the input buffer sent to request these suggestions. ++ * ++ * @return the input buffer ++ */ ++ public @NotNull String getBuffer() { ++ return buffer; ++ } ++ ++ /** ++ * Gets the suggestions to be sent to client. ++ * ++ * @return the suggestions ++ */ ++ public @NotNull Suggestions getSuggestions() { ++ return suggestions; ++ } ++ ++ /** ++ * Sets the suggestions to be sent to client. ++ * ++ * @param suggestions suggestions ++ */ ++ public void setSuggestions(@NotNull Suggestions suggestions) { ++ this.suggestions = suggestions; ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean isCancelled() { ++ return this.cancelled; ++ } ++ ++ /** ++ * Cancels sending suggestions to the client. ++ * {@inheritDoc} ++ */ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancelled = cancel; ++ } ++ ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java b/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8a0c7266cc3fe63d3c6fd83bcd75c54de21038b4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java +@@ -0,0 +1,169 @@ ++package com.destroystokyo.paper.event.brigadier; ++ ++import com.destroystokyo.paper.brigadier.BukkitBrigadierCommand; ++import com.mojang.brigadier.tree.ArgumentCommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import com.mojang.brigadier.tree.RootCommandNode; ++import org.bukkit.command.Command; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.server.ServerEvent; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Fired anytime the server synchronizes Bukkit commands to Brigadier. ++ * ++ *

Allows a plugin to control the command node structure for its commands. ++ * This is done at Plugin Enable time after commands have been registered, but may also ++ * run at a later point in the server lifetime due to plugins, a server reload, etc.

++ * ++ *

This is a draft/experimental API and is subject to change.

++ * @deprecated For removal, use the new brigadier api. ++ */ ++@ApiStatus.Experimental ++@Deprecated(since = "1.20.6") ++public class CommandRegisteredEvent extends ServerEvent implements Cancellable { ++ ++ private static final HandlerList handlers = new HandlerList(); ++ private final String commandLabel; ++ private final Command command; ++ private final com.destroystokyo.paper.brigadier.BukkitBrigadierCommand brigadierCommand; ++ private final RootCommandNode root; ++ private final ArgumentCommandNode defaultArgs; ++ private LiteralCommandNode literal; ++ private boolean rawCommand = false; ++ private boolean cancelled = false; ++ ++ public CommandRegisteredEvent(String commandLabel, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand brigadierCommand, Command command, RootCommandNode root, LiteralCommandNode literal, ArgumentCommandNode defaultArgs) { ++ this.commandLabel = commandLabel; ++ this.brigadierCommand = brigadierCommand; ++ this.command = command; ++ this.root = root; ++ this.literal = literal; ++ this.defaultArgs = defaultArgs; ++ } ++ ++ /** ++ * Gets the command label of the {@link Command} being registered. ++ * ++ * @return the command label ++ */ ++ public String getCommandLabel() { ++ return this.commandLabel; ++ } ++ ++ /** ++ * Gets the {@link BukkitBrigadierCommand} for the {@link Command} being registered. This can be used ++ * as the {@link com.mojang.brigadier.Command command executor} or ++ * {@link com.mojang.brigadier.suggestion.SuggestionProvider} of a {@link com.mojang.brigadier.tree.CommandNode} ++ * to delegate to the {@link Command} being registered. ++ * ++ * @return the {@link BukkitBrigadierCommand} ++ */ ++ public BukkitBrigadierCommand getBrigadierCommand() { ++ return this.brigadierCommand; ++ } ++ ++ /** ++ * Gets the {@link Command} being registered. ++ * ++ * @return the {@link Command} ++ */ ++ public Command getCommand() { ++ return this.command; ++ } ++ ++ /** ++ * Gets the {@link RootCommandNode} which is being registered to. ++ * ++ * @return the {@link RootCommandNode} ++ */ ++ public RootCommandNode getRoot() { ++ return this.root; ++ } ++ ++ /** ++ * Gets the Bukkit APIs default arguments node (greedy string), for if ++ * you wish to reuse it. ++ * ++ * @return default arguments node ++ */ ++ public ArgumentCommandNode getDefaultArgs() { ++ return this.defaultArgs; ++ } ++ ++ /** ++ * Gets the {@link LiteralCommandNode} to be registered for the {@link Command}. ++ * ++ * @return the {@link LiteralCommandNode} ++ */ ++ public LiteralCommandNode getLiteral() { ++ return this.literal; ++ } ++ ++ /** ++ * Sets the {@link LiteralCommandNode} used to register this command. The default literal is mutable, so ++ * this is primarily if you want to completely replace the object. ++ * ++ * @param literal new node ++ */ ++ public void setLiteral(LiteralCommandNode literal) { ++ this.literal = literal; ++ } ++ ++ /** ++ * Gets whether this command should is treated as "raw". ++ * ++ * @see #setRawCommand(boolean) ++ * @return whether this command is treated as "raw" ++ */ ++ public boolean isRawCommand() { ++ return this.rawCommand; ++ } ++ ++ /** ++ * Sets whether this command should be treated as "raw". ++ * ++ *

A "raw" command will only use the node provided by this event for ++ * sending the command tree to the client. For execution purposes, the default ++ * greedy string execution of a standard Bukkit {@link Command} is used.

++ * ++ *

On older versions of Paper, this was the default and only behavior of this ++ * event.

++ * ++ * @param rawCommand whether this command should be treated as "raw" ++ */ ++ public void setRawCommand(final boolean rawCommand) { ++ this.rawCommand = rawCommand; ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean isCancelled() { ++ return this.cancelled; ++ } ++ ++ /** ++ * Cancels registering this command to Brigadier, but will remain in Bukkit Command Map. Can be used to hide a ++ * command from all players. ++ * ++ * {@inheritDoc} ++ */ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancelled = cancel; ++ } ++ ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9df87708206e26167a2c4934deff7fc6f1657106 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java +@@ -0,0 +1,47 @@ ++package io.papermc.paper.brigadier; ++ ++import com.mojang.brigadier.Message; ++import io.papermc.paper.command.brigadier.MessageComponentSerializer; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.kyori.adventure.text.TextComponent; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++/** ++ * Helper methods to bridge the gaps between Brigadier and Paper-MojangAPI. ++ * @deprecated for removal. See {@link MessageComponentSerializer} for a direct replacement of functionality found in ++ * this class. ++ * As a general entrypoint to brigadier on paper, see {@link io.papermc.paper.command.brigadier.Commands}. ++ */ ++@Deprecated(forRemoval = true, since = "1.20.6") ++public final class PaperBrigadier { ++ private PaperBrigadier() { ++ throw new RuntimeException("PaperBrigadier is not to be instantiated!"); ++ } ++ ++ /** ++ * Create a new Brigadier {@link Message} from a {@link ComponentLike}. ++ * ++ *

Mostly useful for creating rich suggestion tooltips in combination with other Paper-MojangAPI APIs.

++ * ++ * @param componentLike The {@link ComponentLike} to use for the {@link Message} contents ++ * @return A new Brigadier {@link Message} ++ */ ++ public static @NonNull Message message(final @NonNull ComponentLike componentLike) { ++ return MessageComponentSerializer.message().serialize(componentLike.asComponent()); ++ } ++ ++ /** ++ * Create a new {@link Component} from a Brigadier {@link Message}. ++ * ++ *

If the {@link Message} was created from a {@link Component}, it will simply be ++ * converted back, otherwise a new {@link TextComponent} will be created with the ++ * content of {@link Message#getString()}

++ * ++ * @param message The {@link Message} to create a {@link Component} from ++ * @return The created {@link Component} ++ */ ++ public static @NonNull Component componentFromMessage(final @NonNull Message message) { ++ return MessageComponentSerializer.message().deserialize(message); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java b/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0f6b921b4bcf983cf25188823f78a061eec5263d +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java +@@ -0,0 +1,36 @@ ++package io.papermc.paper.command.brigadier; ++ ++import java.util.Collection; ++import java.util.Collections; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Implementing this interface allows for easily creating "Bukkit-style" {@code String[] args} commands. ++ * The implementation handles converting the command to a representation compatible with Brigadier on registration, usually in the form of {@literal /commandlabel }. ++ */ ++@ApiStatus.Experimental ++@FunctionalInterface ++public interface BasicCommand { ++ ++ /** ++ * Executes the command with the given {@link CommandSourceStack} and arguments. ++ * ++ * @param commandSourceStack the commandSourceStack of the command ++ * @param args the arguments of the command ignoring repeated spaces ++ */ ++ @ApiStatus.OverrideOnly ++ void execute(@NotNull CommandSourceStack commandSourceStack, @NotNull String[] args); ++ ++ /** ++ * Suggests possible completions for the given command {@link CommandSourceStack} and arguments. ++ * ++ * @param commandSourceStack the commandSourceStack of the command ++ * @param args the arguments of the command including repeated spaces ++ * @return a collection of suggestions ++ */ ++ @ApiStatus.OverrideOnly ++ default @NotNull Collection suggest(final @NotNull CommandSourceStack commandSourceStack, final @NotNull String[] args) { ++ return Collections.emptyList(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/CommandRegistrationFlag.java b/src/main/java/io/papermc/paper/command/brigadier/CommandRegistrationFlag.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7e24babf746de474c8deec4b147e22031e8dadb2 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/CommandRegistrationFlag.java +@@ -0,0 +1,14 @@ ++package io.papermc.paper.command.brigadier; ++ ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * A {@link CommandRegistrationFlag} is used in {@link Commands} registration for internal purposes. ++ *

++ * A command library may use this to achieve more specific customization on how their commands are registered. ++ * @apiNote Stability of these flags is not promised! This api is not intended for public use. ++ */ ++@ApiStatus.Internal ++public enum CommandRegistrationFlag { ++ FLATTEN_ALIASES ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java b/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..54288dbe7185b875a74184f002ee4de4405e91b1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java +@@ -0,0 +1,50 @@ ++package io.papermc.paper.command.brigadier; ++ ++import org.bukkit.Location; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Entity; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * The command source type for Brigadier commands registered using Paper API. ++ *

++ * While the general use case for CommandSourceStack is similar to that of {@link CommandSender}, it provides access to ++ * important additional context for the command execution. ++ * Specifically, commands such as {@literal /execute} may alter the location or executor of the source stack before ++ * passing it to another command. ++ *

The {@link CommandSender} returned by {@link #getSender()} may be a "no-op" ++ * instance of {@link CommandSender} in cases where the server either doesn't ++ * exist yet, or no specific sender is available. Methods on such a {@link CommandSender} ++ * will either have no effect or throw an {@link UnsupportedOperationException}.

++ */ ++@ApiStatus.NonExtendable ++@ApiStatus.Experimental ++public interface CommandSourceStack { ++ ++ /** ++ * Gets the location that this command is being executed at. ++ * ++ * @return a cloned location instance. ++ */ ++ @NotNull Location getLocation(); ++ ++ /** ++ * Gets the command sender that executed this command. ++ * The sender of a command source stack is the one that initiated/triggered the execution of a command. ++ * It differs to {@link #getExecutor()} as the executor can be changed by a command, e.g. {@literal /execute}. ++ * ++ * @return the command sender instance ++ */ ++ @NotNull CommandSender getSender(); ++ ++ /** ++ * Gets the entity that executes this command. ++ * May not always be {@link #getSender()} as the executor of a command can be changed to a different entity ++ * than the one that triggered the command. ++ * ++ * @return entity that executes this command ++ */ ++ @Nullable Entity getExecutor(); ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/Commands.java b/src/main/java/io/papermc/paper/command/brigadier/Commands.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ce60b618de10da7638f5aefa974aebe02600465c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/Commands.java +@@ -0,0 +1,266 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.arguments.ArgumentType; ++import com.mojang.brigadier.builder.LiteralArgumentBuilder; ++import com.mojang.brigadier.builder.RequiredArgumentBuilder; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.bootstrap.PluginBootstrap; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; ++import io.papermc.paper.plugin.lifecycle.event.registrar.Registrar; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Set; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.jetbrains.annotations.Unmodifiable; ++ ++/** ++ * The registrar for custom commands. Supports Brigadier commands and {@link BasicCommand}. ++ *

++ * An example of a command being registered is below ++ *

{@code
++ * class YourPluginClass extends JavaPlugin {
++ *
++ *     @Override
++ *     public void onEnable() {
++ *         LifecycleEventManager manager = this.getLifecycleManager();
++ *         manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> {
++ *             final Commands commands = event.registrar();
++ *             commands.register(
++ *                 Commands.literal("new-command")
++ *                     .executes(ctx -> {
++ *                         ctx.getSource().getSender().sendPlainMessage("some message");
++ *                         return Command.SINGLE_SUCCESS;
++ *                     })
++ *                     .build(),
++ *                 "some bukkit help description string",
++ *                 List.of("an-alias")
++ *             );
++ *         });
++ *     }
++ * }
++ * }
++ *

++ * You can also register commands in {@link PluginBootstrap} by getting the {@link LifecycleEventManager} from ++ * {@link BootstrapContext}. ++ * Commands registered in the {@link PluginBootstrap} will be available for datapack's ++ * command function parsing. ++ * Note that commands registered via {@link PluginBootstrap} with the same literals as a vanilla command will override ++ * that command within all loaded datapacks. ++ *

++ *

The {@code register} methods that do not have {@link PluginMeta} as a parameter will ++ * implicitly use the {@link PluginMeta} for the plugin that the {@link io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler} ++ * was registered with.

++ * ++ * @see io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents#COMMANDS ++ */ ++@ApiStatus.Experimental ++@ApiStatus.NonExtendable ++public interface Commands extends Registrar { ++ ++ /** ++ * Utility to create a literal command node builder with the correct generic. ++ * ++ * @param literal literal name ++ * @return a new builder instance ++ */ ++ static @NotNull LiteralArgumentBuilder literal(final @NotNull String literal) { ++ return LiteralArgumentBuilder.literal(literal); ++ } ++ ++ /** ++ * Utility to create a required argument builder with the correct generic. ++ * ++ * @param name the name of the argument ++ * @param argumentType the type of the argument ++ * @param the generic type of the argument value ++ * @return a new required argument builder ++ */ ++ static @NotNull RequiredArgumentBuilder argument(final @NotNull String name, final @NotNull ArgumentType argumentType) { ++ return RequiredArgumentBuilder.argument(name, argumentType); ++ } ++ ++ /** ++ * Gets the underlying {@link CommandDispatcher}. ++ * ++ *

Note: This is a delicate API that must be used with care to ensure a consistent user experience.

++ * ++ *

When registering commands, it should be preferred to use the {@link #register(PluginMeta, LiteralCommandNode, String, Collection) register methods} ++ * over directly registering to the dispatcher wherever possible. ++ * {@link #register(PluginMeta, LiteralCommandNode, String, Collection) Register methods} automatically handle ++ * command namespacing, command help, plugin association with commands, and more.

++ * ++ *

Example use cases for this method may include: ++ *

    ++ *
  • Implementing integration between an external command framework and Paper (although {@link #register(PluginMeta, LiteralCommandNode, String, Collection) register methods} should still be preferred where possible)
  • ++ *
  • Registering new child nodes to an existing plugin command (for example an "addon" plugin to another plugin may want to do this)
  • ++ *
  • Retrieving existing command nodes to build redirects
  • ++ *
++ * ++ * @return the dispatcher instance ++ */ ++ @ApiStatus.Experimental ++ @NotNull CommandDispatcher getDispatcher(); ++ ++ /** ++ * Registers a command for the current plugin context. ++ * ++ *

Commands have certain overriding behavior: ++ *

    ++ *
  • Aliases will not override already existing commands (excluding namespaced ones)
  • ++ *
  • The main command/namespaced label will override already existing commands
  • ++ *
++ * ++ * @param node the built literal command node ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ default @Unmodifiable @NotNull Set register(final @NotNull LiteralCommandNode node) { ++ return this.register(node, null, Collections.emptyList()); ++ } ++ ++ /** ++ * Registers a command for the current plugin context. ++ * ++ *

Commands have certain overriding behavior: ++ *

    ++ *
  • Aliases will not override already existing commands (excluding namespaced ones)
  • ++ *
  • The main command/namespaced label will override already existing commands
  • ++ *
++ * ++ * @param node the built literal command node ++ * @param description the help description for the root literal node ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ default @Unmodifiable @NotNull Set register(final @NotNull LiteralCommandNode node, final @Nullable String description) { ++ return this.register(node, description, Collections.emptyList()); ++ } ++ ++ /** ++ * Registers a command for the current plugin context. ++ * ++ *

Commands have certain overriding behavior: ++ *

    ++ *
  • Aliases will not override already existing commands (excluding namespaced ones)
  • ++ *
  • The main command/namespaced label will override already existing commands
  • ++ *
++ * ++ * @param node the built literal command node ++ * @param aliases a collection of aliases to register the literal node's command to ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ default @Unmodifiable @NotNull Set register(final @NotNull LiteralCommandNode node, final @NotNull Collection aliases) { ++ return this.register(node, null, aliases); ++ } ++ ++ /** ++ * Registers a command for the current plugin context. ++ * ++ *

Commands have certain overriding behavior: ++ *

    ++ *
  • Aliases will not override already existing commands (excluding namespaced ones)
  • ++ *
  • The main command/namespaced label will override already existing commands
  • ++ *
++ * ++ * @param node the built literal command node ++ * @param description the help description for the root literal node ++ * @param aliases a collection of aliases to register the literal node's command to ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ @Unmodifiable @NotNull Set register(@NotNull LiteralCommandNode node, @Nullable String description, @NotNull Collection aliases); ++ ++ /** ++ * Registers a command for a plugin. ++ * ++ *

Commands have certain overriding behavior: ++ *

    ++ *
  • Aliases will not override already existing commands (excluding namespaced ones)
  • ++ *
  • The main command/namespaced label will override already existing commands
  • ++ *
++ * ++ * @param pluginMeta the owning plugin's meta ++ * @param node the built literal command node ++ * @param description the help description for the root literal node ++ * @param aliases a collection of aliases to register the literal node's command to ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ @Unmodifiable @NotNull Set register(@NotNull PluginMeta pluginMeta, @NotNull LiteralCommandNode node, @Nullable String description, @NotNull Collection aliases); ++ ++ /** ++ * This allows configuring the registration of your command, which is not intended for public use. ++ * See {@link Commands#register(PluginMeta, LiteralCommandNode, String, Collection)} for more information. ++ * ++ * @param pluginMeta the owning plugin's meta ++ * @param node the built literal command node ++ * @param description the help description for the root literal node ++ * @param aliases a collection of aliases to register the literal node's command to ++ * @param flags a collection of registration flags that control registration behaviour. ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ * ++ * @apiNote This method is not guaranteed to be stable as it is not intended for public use. ++ * See {@link CommandRegistrationFlag} for a more indepth explanation of this method's use-case. ++ */ ++ @ApiStatus.Internal ++ @Unmodifiable @NotNull Set registerWithFlags(@NotNull PluginMeta pluginMeta, @NotNull LiteralCommandNode node, @Nullable String description, @NotNull Collection aliases, @NotNull Set flags); ++ ++ /** ++ * Registers a command under the same logic as {@link Commands#register(LiteralCommandNode, String, Collection)}. ++ * ++ * @param label the label of the to-be-registered command ++ * @param basicCommand the basic command instance to register ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ default @Unmodifiable @NotNull Set register(final @NotNull String label, final @NotNull BasicCommand basicCommand) { ++ return this.register(label, null, Collections.emptyList(), basicCommand); ++ } ++ ++ /** ++ * Registers a command under the same logic as {@link Commands#register(LiteralCommandNode, String, Collection)}. ++ * ++ * @param label the label of the to-be-registered command ++ * @param description the help description for the root literal node ++ * @param basicCommand the basic command instance to register ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ default @Unmodifiable @NotNull Set register(final @NotNull String label, final @Nullable String description, final @NotNull BasicCommand basicCommand) { ++ return this.register(label, description, Collections.emptyList(), basicCommand); ++ } ++ ++ /** ++ * Registers a command under the same logic as {@link Commands#register(LiteralCommandNode, String, Collection)}. ++ * ++ * @param label the label of the to-be-registered command ++ * @param aliases a collection of aliases to register the basic command under. ++ * @param basicCommand the basic command instance to register ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ default @Unmodifiable @NotNull Set register(final @NotNull String label, final @NotNull Collection aliases, final @NotNull BasicCommand basicCommand) { ++ return this.register(label, null, aliases, basicCommand); ++ } ++ ++ /** ++ * Registers a command under the same logic as {@link Commands#register(LiteralCommandNode, String, Collection)}. ++ * ++ * @param label the label of the to-be-registered command ++ * @param description the help description for the root literal node ++ * @param aliases a collection of aliases to register the basic command under. ++ * @param basicCommand the basic command instance to register ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ @Unmodifiable @NotNull Set register(@NotNull String label, @Nullable String description, @NotNull Collection aliases, @NotNull BasicCommand basicCommand); ++ ++ /** ++ * Registers a command under the same logic as {@link Commands#register(PluginMeta, LiteralCommandNode, String, Collection)}. ++ * ++ * @param pluginMeta the owning plugin's meta ++ * @param label the label of the to-be-registered command ++ * @param description the help description for the root literal node ++ * @param aliases a collection of aliases to register the basic command under. ++ * @param basicCommand the basic command instance to register ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ @Unmodifiable @NotNull Set register(@NotNull PluginMeta pluginMeta, @NotNull String label, @Nullable String description, @NotNull Collection aliases, @NotNull BasicCommand basicCommand); ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializer.java b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..57061a3dd738416c2045e641b6080dc3f096de1a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializer.java +@@ -0,0 +1,24 @@ ++package io.papermc.paper.command.brigadier; ++ ++import com.mojang.brigadier.Message; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.serializer.ComponentSerializer; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * A component serializer for converting between {@link Message} and {@link Component}. ++ */ ++@ApiStatus.Experimental ++@ApiStatus.NonExtendable ++public interface MessageComponentSerializer extends ComponentSerializer { ++ ++ /** ++ * A component serializer for converting between {@link Message} and {@link Component}. ++ * ++ * @return serializer instance ++ */ ++ static @NotNull MessageComponentSerializer message() { ++ return MessageComponentSerializerHolder.PROVIDER.orElseThrow(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerHolder.java b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2db12952461c92a64505d6646f6f49f824e83050 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerHolder.java +@@ -0,0 +1,12 @@ ++package io.papermc.paper.command.brigadier; ++ ++import java.util.Optional; ++import java.util.ServiceLoader; ++import org.jetbrains.annotations.ApiStatus; ++ ++@ApiStatus.Internal ++final class MessageComponentSerializerHolder { ++ ++ static final Optional PROVIDER = ServiceLoader.load(MessageComponentSerializer.class) ++ .findFirst(); ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/ArgumentTypes.java b/src/main/java/io/papermc/paper/command/brigadier/argument/ArgumentTypes.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6c5ffca60a499099fa552020d68060c20abc44b1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/ArgumentTypes.java +@@ -0,0 +1,324 @@ ++package io.papermc.paper.command.brigadier.argument; ++ ++import com.mojang.brigadier.arguments.ArgumentType; ++import io.papermc.paper.command.brigadier.argument.predicate.ItemStackPredicate; ++import io.papermc.paper.command.brigadier.argument.range.DoubleRangeProvider; ++import io.papermc.paper.command.brigadier.argument.range.IntegerRangeProvider; ++import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver; ++import io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver; ++import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver; ++import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; ++import io.papermc.paper.entity.LookAnchor; ++import java.util.UUID; ++import net.kyori.adventure.key.Key; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.Style; ++import org.bukkit.GameMode; ++import org.bukkit.HeightMap; ++import org.bukkit.NamespacedKey; ++import org.bukkit.World; ++import org.bukkit.block.BlockState; ++import org.bukkit.block.structure.Mirror; ++import org.bukkit.block.structure.StructureRotation; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.scoreboard.Criteria; ++import org.bukkit.scoreboard.DisplaySlot; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++import static io.papermc.paper.command.brigadier.argument.VanillaArgumentProvider.provider; ++ ++/** ++ * Vanilla Minecraft includes several custom {@link ArgumentType}s that are recognized by the client. ++ * Many of these argument types include client-side completions and validation, and some include command signing context. ++ * ++ *

This class allows creating instances of these types for use in plugin commands, with friendly API result types.

++ * ++ *

{@link CustomArgumentType} is provided for customizing parsing or result types server-side, while sending the vanilla argument type to the client.

++ */ ++@ApiStatus.Experimental ++public final class ArgumentTypes { ++ ++ /** ++ * Represents a selector that can capture any ++ * single entity. ++ * ++ * @return argument that takes one entity ++ */ ++ public static @NotNull ArgumentType entity() { ++ return provider().entity(); ++ } ++ ++ /** ++ * Represents a selector that can capture multiple ++ * entities. ++ * ++ * @return argument that takes multiple entities ++ */ ++ public static @NotNull ArgumentType entities() { ++ return provider().entities(); ++ } ++ ++ /** ++ * Represents a selector that can capture a ++ * singular player entity. ++ * ++ * @return argument that takes one player ++ */ ++ public static @NotNull ArgumentType player() { ++ return provider().player(); ++ } ++ ++ /** ++ * Represents a selector that can capture multiple ++ * player entities. ++ * ++ * @return argument that takes multiple players ++ */ ++ public static @NotNull ArgumentType players() { ++ return provider().players(); ++ } ++ ++ /** ++ * A selector argument that provides a list ++ * of player profiles. ++ * ++ * @return player profile arguments ++ */ ++ public static @NotNull ArgumentType playerProfiles() { ++ return provider().playerProfiles(); ++ } ++ ++ /** ++ * A block position argument. ++ * ++ * @return argument ++ */ ++ public static @NotNull ArgumentType blockPosition() { ++ return provider().blockPosition(); ++ } ++ ++ /** ++ * A blockstate argument which will provide rich parsing for specifying ++ * the specific block variant and then the block entity NBT if applicable. ++ * ++ * @return argument ++ */ ++ public static @NotNull ArgumentType blockState() { ++ return provider().blockState(); ++ } ++ ++ /** ++ * An ItemStack argument which provides rich parsing for ++ * specifying item material and item NBT information. ++ * ++ * @return argument ++ */ ++ public static @NotNull ArgumentType itemStack() { ++ return provider().itemStack(); ++ } ++ ++ /** ++ * An item predicate argument. ++ * ++ * @return argument ++ */ ++ public static @NotNull ArgumentType itemPredicate() { ++ return provider().itemStackPredicate(); ++ } ++ ++ /** ++ * An argument for parsing {@link NamedTextColor}s. ++ * ++ * @return argument ++ */ ++ public static @NotNull ArgumentType namedColor() { ++ return provider().namedColor(); ++ } ++ ++ /** ++ * A component argument. ++ * ++ * @return argument ++ */ ++ public static @NotNull ArgumentType component() { ++ return provider().component(); ++ } ++ ++ /** ++ * A style argument. ++ * ++ * @return argument ++ */ ++ public static @NotNull ArgumentType