diff options
Diffstat (limited to 'patches/api/0465-Brigadier-based-command-API.patch')
-rw-r--r-- | patches/api/0465-Brigadier-based-command-API.patch | 2069 |
1 files changed, 2069 insertions, 0 deletions
diff --git a/patches/api/0465-Brigadier-based-command-API.patch b/patches/api/0465-Brigadier-based-command-API.patch new file mode 100644 index 0000000000..88ce5b7aab --- /dev/null +++ b/patches/api/0465-Brigadier-based-command-API.patch @@ -0,0 +1,2069 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <[email protected]> +Date: Mon, 1 Aug 2022 22:50:29 -0400 +Subject: [PATCH] Brigadier based command API + +Co-authored-by: Jake Potrebic <[email protected]> + +diff --git a/build.gradle.kts b/build.gradle.kts +index be8492b23d10332d046150a4ff18c67cc1b0f5d2..6c1cba3e87d2e3cb3eec65345ed7dcc56fd96363 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") +@@ -93,9 +94,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<PublishingExtension> { + publications.create<MavenPublication>("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 <S> 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 <S extends BukkitBrigadierCommandSource> extends Command<S>, Predicate<S>, SuggestionProvider<S> { ++} +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..9e1b70d438c4341ec944503b5bbe6b1f08bc0478 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java +@@ -0,0 +1,73 @@ ++package com.destroystokyo.paper.event.brigadier; ++ ++import com.mojang.brigadier.tree.RootCommandNode; ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++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.jspecify.annotations.NullMarked; ++ ++/** ++ * 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. ++ * ++ * <p>This event may fire on login, world change, and permission rebuilds, by plugin request, and potentially future means.</p> ++ * ++ * <p>This event will fire before {@link org.bukkit.event.player.PlayerCommandSendEvent}, so no filtering has been done by ++ * other plugins yet.</p> ++ * ++ * <p>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.</p> ++ * ++ * <p>Your logic should look like this: ++ * {@code if (event.isAsynchronous() || !event.hasFiredAsync()) { // do stuff }}</p> ++ * ++ * <p>If your logic is not safe to run asynchronously, only react to the synchronous version.</p> ++ * ++ * <p>This is a draft/experimental API and is subject to change.</p> ++ */ ++@NullMarked ++public class AsyncPlayerSendCommandsEvent<S extends CommandSourceStack> extends PlayerEvent { ++ ++ private static final HandlerList HANDLER_LIST = new HandlerList(); ++ private final RootCommandNode<S> node; ++ private final boolean hasFiredAsync; ++ ++ @ApiStatus.Internal ++ public AsyncPlayerSendCommandsEvent(final Player player, final RootCommandNode<S> node, final 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<S> getCommandNode() { ++ return this.node; ++ } ++ ++ /** ++ * Gets if this event has already fired asynchronously. ++ * ++ * @return whether this event has already fired asynchronously ++ */ ++ public boolean hasFiredAsync() { ++ return this.hasFiredAsync; ++ } ++ ++ @Override ++ public HandlerList getHandlers() { ++ return HANDLER_LIST; ++ } ++ ++ public static HandlerList getHandlerList() { ++ return HANDLER_LIST; ++ } ++} +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..faade9d35514687f21a0e8b62fa2e392d4ad238a +--- /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.jspecify.annotations.NullMarked; ++ ++/** ++ * 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. ++ */ ++@NullMarked ++public class AsyncPlayerSendSuggestionsEvent extends PlayerEvent implements Cancellable { ++ ++ private static final HandlerList HANDLER_LIST = new HandlerList(); ++ private boolean cancelled = false; ++ ++ private Suggestions suggestions; ++ private final String buffer; ++ ++ @ApiStatus.Internal ++ public AsyncPlayerSendSuggestionsEvent(final Player player, final Suggestions suggestions, final 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 this.buffer; ++ } ++ ++ /** ++ * Gets the suggestions to be sent to client. ++ * ++ * @return the suggestions ++ */ ++ public Suggestions getSuggestions() { ++ return this.suggestions; ++ } ++ ++ /** ++ * Sets the suggestions to be sent to client. ++ * ++ * @param suggestions suggestions ++ */ ++ public void setSuggestions(final Suggestions suggestions) { ++ this.suggestions = suggestions; ++ } ++ ++ /** ++ * {@inheritDoc} ++ */ ++ @Override ++ public boolean isCancelled() { ++ return this.cancelled; ++ } ++ ++ /** ++ * Cancels sending suggestions to the client. ++ * {@inheritDoc} ++ */ ++ @Override ++ public void setCancelled(final boolean cancel) { ++ this.cancelled = cancel; ++ } ++ ++ @Override ++ public HandlerList getHandlers() { ++ return HANDLER_LIST; ++ } ++ ++ public static HandlerList getHandlerList() { ++ return HANDLER_LIST; ++ } ++} +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..acc2bd2ec56e64b9d4bd8677d99448a97ecb5201 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java +@@ -0,0 +1,171 @@ ++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.Warning; ++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. ++ * ++ * <p>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.</p> ++ * ++ * <p>This is a draft/experimental API and is subject to change.</p> ++ * @deprecated For removal, use the new brigadier api. ++ */ ++@Deprecated(since = "1.20.6") ++@Warning(reason = "This event has been superseded by the Commands API and will be removed in a future release. Listen to LifecycleEvents.COMMANDS instead.", value = true) ++public class CommandRegisteredEvent<S extends com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource> 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<S> brigadierCommand; ++ private final RootCommandNode<S> root; ++ private final ArgumentCommandNode<S, String> defaultArgs; ++ private LiteralCommandNode<S> literal; ++ private boolean rawCommand = false; ++ private boolean cancelled = false; ++ ++ public CommandRegisteredEvent(String commandLabel, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand<S> brigadierCommand, Command command, RootCommandNode<S> root, LiteralCommandNode<S> literal, ArgumentCommandNode<S, String> 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<S> 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<S> 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<S, String> getDefaultArgs() { ++ return this.defaultArgs; ++ } ++ ++ /** ++ * Gets the {@link LiteralCommandNode} to be registered for the {@link Command}. ++ * ++ * @return the {@link LiteralCommandNode} ++ */ ++ public LiteralCommandNode<S> 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<S> 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". ++ * ++ * <p>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.</p> ++ * ++ * <p>On older versions of Paper, this was the default and only behavior of this ++ * event.</p> ++ * ++ * @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}. ++ * ++ * <p>Mostly useful for creating rich suggestion tooltips in combination with other Paper-MojangAPI APIs.</p> ++ * ++ * @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}. ++ * ++ * <p>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()}</p> ++ * ++ * @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..c89d6c4c38e2390cb11ffba182f8741d3726cfd1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java +@@ -0,0 +1,62 @@ ++package io.papermc.paper.command.brigadier; ++ ++import java.util.Collection; ++import java.util.Collections; ++import org.bukkit.command.CommandSender; ++import org.jetbrains.annotations.ApiStatus; ++import org.jspecify.annotations.NullMarked; ++import org.jspecify.annotations.Nullable; ++ ++/** ++ * 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 <greedy_string>}. ++ */ ++@NullMarked ++@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(CommandSourceStack commandSourceStack, 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 Collection<String> suggest(final CommandSourceStack commandSourceStack, final String[] args) { ++ return Collections.emptyList(); ++ } ++ ++ /** ++ * Checks whether a command sender can receive and run the root command. ++ * ++ * @param sender the command sender trying to execute the command ++ * @return whether the command sender fulfills the root command requirement ++ * @see #permission() ++ */ ++ @ApiStatus.OverrideOnly ++ default boolean canUse(final CommandSender sender) { ++ final String permission = this.permission(); ++ return permission == null || sender.hasPermission(permission); ++ } ++ ++ /** ++ * Returns the permission for the root command used in {@link #canUse(CommandSender)} by default. ++ * ++ * @return the permission for the root command used in {@link #canUse(CommandSender)} ++ */ ++ @ApiStatus.OverrideOnly ++ default @Nullable String permission() { ++ return null; ++ } ++} +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. ++ * <p> ++ * 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. ++ */ ++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..ac6f5b754a15e85ce09de4ed4cdee2044b45022c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java +@@ -0,0 +1,51 @@ ++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.jspecify.annotations.NullMarked; ++import org.jspecify.annotations.Nullable; ++ ++/** ++ * The command source type for Brigadier commands registered using Paper API. ++ * <p> ++ * 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. ++ * <p>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}.</p> ++ */ ++@NullMarked ++public interface CommandSourceStack { ++ ++ /** ++ * Gets the location that this command is being executed at. ++ * ++ * @return a cloned location instance. ++ */ ++ 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 ++ */ ++ 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..e32559772a39af781d89de101b3f7483a339e317 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/Commands.java +@@ -0,0 +1,267 @@ ++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.Unmodifiable; ++import org.jspecify.annotations.NullMarked; ++import org.jspecify.annotations.Nullable; ++ ++/** ++ * The registrar for custom commands. Supports Brigadier commands and {@link BasicCommand}. ++ * <p> ++ * An example of a command being registered is below ++ * <pre>{@code ++ * class YourPluginClass extends JavaPlugin { ++ * ++ * @Override ++ * public void onEnable() { ++ * LifecycleEventManager<Plugin> 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") ++ * ); ++ * }); ++ * } ++ * } ++ * }</pre> ++ * <p> ++ * 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. ++ * </p> ++ * <p>The {@code register} methods that <b>do not</b> 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.</p> ++ * ++ * @see io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents#COMMANDS ++ */ ++@NullMarked ++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 LiteralArgumentBuilder<CommandSourceStack> literal(final 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 <T> the generic type of the argument value ++ * @return a new required argument builder ++ */ ++ static <T> RequiredArgumentBuilder<CommandSourceStack, T> argument(final String name, final ArgumentType<T> argumentType) { ++ return RequiredArgumentBuilder.argument(name, argumentType); ++ } ++ ++ /** ++ * Gets the underlying {@link CommandDispatcher}. ++ * ++ * <p><b>Note:</b> This is a delicate API that must be used with care to ensure a consistent user experience.</p> ++ * ++ * <p>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.</p> ++ * ++ * <p>Example use cases for this method <b>may</b> include: ++ * <ul> ++ * <li>Implementing integration between an external command framework and Paper (although {@link #register(PluginMeta, LiteralCommandNode, String, Collection) register methods} should still be preferred where possible)</li> ++ * <li>Registering new child nodes to an existing plugin command (for example an "addon" plugin to another plugin may want to do this)</li> ++ * <li>Retrieving existing command nodes to build redirects</li> ++ * </ul> ++ * ++ * @return the dispatcher instance ++ */ ++ @ApiStatus.Experimental ++ CommandDispatcher<CommandSourceStack> getDispatcher(); ++ ++ /** ++ * Registers a command for the current plugin context. ++ * ++ * <p>Commands have certain overriding behavior: ++ * <ul> ++ * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> ++ * <li>The main command/namespaced label will override already existing commands</li> ++ * </ul> ++ * ++ * @param node the built literal command node ++ * @return successfully registered root command labels (including aliases and namespaced variants) ++ */ ++ default @Unmodifiable Set<String> register(final LiteralCommandNode<CommandSourceStack> node) { ++ return this.register(node, null, Collections.emptyList()); ++ } ++ ++ /** ++ * Registers a command for the current plugin context. ++ * ++ * <p>Commands have certain overriding behavior: ++ * <ul> ++ * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> ++ * <li>The main command/namespaced label will override already existing commands</li> ++ * </ul> ++ * ++ * @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 Set<String> register(final LiteralCommandNode<CommandSourceStack> node, final @Nullable String description) { ++ return this.register(node, description, Collections.emptyList()); ++ } ++ ++ /** ++ * Registers a command for the current plugin context. ++ * ++ * <p>Commands have certain overriding behavior: ++ * <ul> ++ * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> ++ * <li>The main command/namespaced label will override already existing commands</li> ++ * </ul> ++ * ++ * @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 Set<String> register(final LiteralCommandNode<CommandSourceStack> node, final Collection<String> aliases) { ++ return this.register(node, null, aliases); ++ } ++ ++ /** ++ * Registers a command for the current plugin context. ++ * ++ * <p>Commands have certain overriding behavior: ++ * <ul> ++ * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> ++ * <li>The main command/namespaced label will override already existing commands</li> ++ * </ul> ++ * ++ * @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 Set<String> register(LiteralCommandNode<CommandSourceStack> node, @Nullable String description, Collection<String> aliases); ++ ++ /** ++ * Registers a command for a plugin. ++ * ++ * <p>Commands have certain overriding behavior: ++ * <ul> ++ * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> ++ * <li>The main command/namespaced label will override already existing commands</li> ++ * </ul> ++ * ++ * @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 Set<String> register(PluginMeta pluginMeta, LiteralCommandNode<CommandSourceStack> node, @Nullable String description, Collection<String> 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 Set<String> registerWithFlags(PluginMeta pluginMeta, LiteralCommandNode<CommandSourceStack> node, @Nullable String description, Collection<String> aliases, Set<CommandRegistrationFlag> 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 Set<String> register(final String label, final 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 Set<String> register(final String label, final @Nullable String description, final 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 Set<String> register(final String label, final Collection<String> aliases, final 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 Set<String> register(String label, @Nullable String description, Collection<String> aliases, 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 Set<String> register(PluginMeta pluginMeta, String label, @Nullable String description, Collection<String> aliases, 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..19f3dc12426be09613a13b5889f77627a81305f4 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializer.java +@@ -0,0 +1,25 @@ ++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.jspecify.annotations.NullMarked; ++ ++/** ++ * A component serializer for converting between {@link Message} and {@link Component}. ++ */ ++@NullMarked ++public interface MessageComponentSerializer extends ComponentSerializer<Component, Component, Message> { ++ ++ /** ++ * A component serializer for converting between {@link Message} and {@link Component}. ++ * ++ * @return serializer instance ++ */ ++ static 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; ++ ++final class MessageComponentSerializerHolder { ++ ++ static final Optional<MessageComponentSerializer> 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..9abb9ff33672036bb548c688c5661dc8f237aae2 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/ArgumentTypes.java +@@ -0,0 +1,371 @@ ++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.FinePositionResolver; ++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 io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++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.jspecify.annotations.NullMarked; ++ ++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. ++ * ++ * <p>This class allows creating instances of these types for use in plugin commands, with friendly API result types.</p> ++ * ++ * <p>{@link CustomArgumentType} is provided for customizing parsing or result types server-side, while sending the vanilla argument type to the client.</p> ++ */ ++@NullMarked ++public final class ArgumentTypes { ++ ++ /** ++ * Represents a selector that can capture any ++ * single entity. ++ * ++ * @return argument that takes one entity ++ */ ++ public static ArgumentType<EntitySelectorArgumentResolver> entity() { ++ return provider().entity(); ++ } ++ ++ /** ++ * Represents a selector that can capture multiple ++ * entities. ++ * ++ * @return argument that takes multiple entities ++ */ ++ public static ArgumentType<EntitySelectorArgumentResolver> entities() { ++ return provider().entities(); ++ } ++ ++ /** ++ * Represents a selector that can capture a ++ * singular player entity. ++ * ++ * @return argument that takes one player ++ */ ++ public static ArgumentType<PlayerSelectorArgumentResolver> player() { ++ return provider().player(); ++ } ++ ++ /** ++ * Represents a selector that can capture multiple ++ * player entities. ++ * ++ * @return argument that takes multiple players ++ */ ++ public static ArgumentType<PlayerSelectorArgumentResolver> players() { ++ return provider().players(); ++ } ++ ++ /** ++ * A selector argument that provides a list ++ * of player profiles. ++ * ++ * @return player profile argument ++ */ ++ public static ArgumentType<PlayerProfileListResolver> playerProfiles() { ++ return provider().playerProfiles(); ++ } ++ ++ /** ++ * A block position argument. ++ * ++ * @return block position argument ++ */ ++ public static ArgumentType<BlockPositionResolver> blockPosition() { ++ return provider().blockPosition(); ++ } ++ ++ /** ++ * A fine position argument. ++ * ++ * @return fine position argument ++ * @see #finePosition(boolean) to center whole numbers ++ */ ++ public static ArgumentType<FinePositionResolver> finePosition() { ++ return finePosition(false); ++ } ++ ++ /** ++ * A fine position argument. ++ * ++ * @param centerIntegers if whole numbers should be centered (+0.5) ++ * @return fine position argument ++ */ ++ public static ArgumentType<FinePositionResolver> finePosition(final boolean centerIntegers) { ++ return provider().finePosition(centerIntegers); ++ } ++ ++ /** ++ * 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 ArgumentType<BlockState> blockState() { ++ return provider().blockState(); ++ } ++ ++ /** ++ * An ItemStack argument which provides rich parsing for ++ * specifying item material and item NBT information. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<ItemStack> itemStack() { ++ return provider().itemStack(); ++ } ++ ++ /** ++ * An item predicate argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<ItemStackPredicate> itemPredicate() { ++ return provider().itemStackPredicate(); ++ } ++ ++ /** ++ * An argument for parsing {@link NamedTextColor}s. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<NamedTextColor> namedColor() { ++ return provider().namedColor(); ++ } ++ ++ /** ++ * A component argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<Component> component() { ++ return provider().component(); ++ } ++ ++ /** ++ * A style argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<Style> style() { ++ return provider().style(); ++ } ++ ++ /** ++ * A signed message argument. ++ * This argument can be resolved to retrieve the underlying ++ * signed message. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<SignedMessageResolver> signedMessage() { ++ return provider().signedMessage(); ++ } ++ ++ /** ++ * A scoreboard display slot argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<DisplaySlot> scoreboardDisplaySlot() { ++ return provider().scoreboardDisplaySlot(); ++ } ++ ++ /** ++ * A namespaced key argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<NamespacedKey> namespacedKey() { ++ return provider().namespacedKey(); ++ } ++ ++ /** ++ * A key argument. ++ * ++ * @return argument ++ */ ++ // include both key types as we are slowly moving to use adventure's key ++ public static ArgumentType<Key> key() { ++ return provider().key(); ++ } ++ ++ /** ++ * An inclusive range of integers that may be unbounded on either end. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<IntegerRangeProvider> integerRange() { ++ return provider().integerRange(); ++ } ++ ++ /** ++ * An inclusive range of doubles that may be unbounded on either end. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<DoubleRangeProvider> doubleRange() { ++ return provider().doubleRange(); ++ } ++ ++ /** ++ * A world argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<World> world() { ++ return provider().world(); ++ } ++ ++ /** ++ * A game mode argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<GameMode> gameMode() { ++ return provider().gameMode(); ++ } ++ ++ /** ++ * An argument for getting a heightmap type. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<HeightMap> heightMap() { ++ return provider().heightMap(); ++ } ++ ++ /** ++ * A uuid argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<UUID> uuid() { ++ return provider().uuid(); ++ } ++ ++ /** ++ * An objective criteria argument ++ * ++ * @return argument ++ */ ++ public static ArgumentType<Criteria> objectiveCriteria() { ++ return provider().objectiveCriteria(); ++ } ++ ++ /** ++ * An entity anchor argument. ++ * ++ * @return argument ++ */ ++ public static ArgumentType<LookAnchor> entityAnchor() { ++ return provider().entityAnchor(); ++ } ++ ++ /** ++ * A time argument, returning the number of ticks. ++ * <p>Examples: ++ * <ul> ++ * <li> "1d" ++ * <li> "5s" ++ * <li> "2" ++ * <li> "6t" ++ * </ul> ++ * ++ * @return argument ++ */ ++ public static ArgumentType<Integer> time() { ++ return time(0); ++ } ++ ++ /** ++ * A time argument, returning the number of ticks. ++ * <p>Examples: ++ * <ul> ++ * <li> "1d" ++ * <li> "5s" ++ * <li> "2" ++ * <li> "6t" ++ * </ul> ++ * ++ * @param mintime The minimum time required for this argument. ++ * @return argument ++ */ ++ public static ArgumentType<Integer> time(final int mintime) { ++ return provider().time(mintime); ++ } ++ ++ /** ++ * A template mirror argument ++ * ++ * @return argument ++ * @see Mirror ++ */ ++ public static ArgumentType<Mirror> templateMirror() { ++ return provider().templateMirror(); ++ } ++ ++ /** ++ * A template rotation argument. ++ * ++ * @return argument ++ * @see StructureRotation ++ */ ++ public static ArgumentType<StructureRotation> templateRotation() { ++ return provider().templateRotation(); ++ } ++ ++ /** ++ * An argument for a resource in a {@link org.bukkit.Registry}. ++ * ++ * @param registryKey the registry's key ++ * @return argument ++ * @param <T> the registry value type ++ */ ++ public static <T> ArgumentType<T> resource(final RegistryKey<T> registryKey) { ++ return provider().resource(registryKey); ++ } ++ ++ /** ++ * An argument for a typed key for a {@link org.bukkit.Registry}. ++ * ++ * @param registryKey the registry's key ++ * @return argument ++ * @param <T> the registry value type ++ * @see RegistryArgumentExtractor#getTypedKey(com.mojang.brigadier.context.CommandContext, RegistryKey, String) ++ */ ++ public static <T> ArgumentType<TypedKey<T>> resourceKey(final RegistryKey<T> registryKey) { ++ return provider().resourceKey(registryKey); ++ } ++ ++ private ArgumentTypes() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java b/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..91d40ef0bdbdee3609e33577782c5cce29deda6a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java +@@ -0,0 +1,107 @@ ++package io.papermc.paper.command.brigadier.argument; ++ ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.arguments.ArgumentType; ++import com.mojang.brigadier.context.CommandContext; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import com.mojang.brigadier.suggestion.Suggestions; ++import com.mojang.brigadier.suggestion.SuggestionsBuilder; ++import java.util.Collection; ++import java.util.concurrent.CompletableFuture; ++import org.jetbrains.annotations.ApiStatus; ++import org.jspecify.annotations.NullMarked; ++ ++/** ++ * An argument type that wraps around a native-to-vanilla argument type. ++ * This argument receives special handling in that the native argument type will ++ * be sent to the client for possible client-side completions and syntax validation. ++ * <p> ++ * When implementing this class, you have to create your own parsing logic from a ++ * {@link StringReader}. If only want to convert from the native type ({@code N}) to the custom ++ * type ({@code T}), implement {@link Converted} instead. ++ * ++ * @param <T> custom type ++ * @param <N> type with an argument native to vanilla Minecraft (from {@link ArgumentTypes}) ++ */ ++@NullMarked ++public interface CustomArgumentType<T, N> extends ArgumentType<T> { ++ ++ /** ++ * Parses the argument into the custom type ({@code T}). Keep in mind ++ * that this parsing will be done on the server. This means that if ++ * you throw a {@link CommandSyntaxException} during parsing, this ++ * will only show up to the user after the user has executed the command ++ * not while they are still entering it. ++ * ++ * @param reader string reader input ++ * @return parsed value ++ * @throws CommandSyntaxException if an error occurs while parsing ++ */ ++ @Override ++ T parse(final StringReader reader) throws CommandSyntaxException; ++ ++ /** ++ * Gets the native type that this argument uses, ++ * the type that is sent to the client. ++ * ++ * @return native argument type ++ */ ++ ArgumentType<N> getNativeType(); ++ ++ /** ++ * Cannot be controlled by the server. ++ * Returned in cases where there are multiple arguments in the same node. ++ * This helps differentiate and tell the player what the possible inputs are. ++ * ++ * @return client set examples ++ */ ++ @Override ++ @ApiStatus.NonExtendable ++ default Collection<String> getExamples() { ++ return this.getNativeType().getExamples(); ++ } ++ ++ /** ++ * Provides a list of suggestions to show to the client. ++ * ++ * @param context command context ++ * @param builder suggestion builder ++ * @return suggestions ++ * @param <S> context type ++ */ ++ @Override ++ default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) { ++ return ArgumentType.super.listSuggestions(context, builder); ++ } ++ ++ /** ++ * An argument type that wraps around a native-to-vanilla argument type. ++ * This argument receives special handling in that the native argument type will ++ * be sent to the client for possible client-side completions and syntax validation. ++ * <p> ++ * The parsed native type will be converted via {@link #convert(Object)}. ++ * Implement {@link CustomArgumentType} if you want to handle parsing the type manually. ++ * ++ * @param <T> custom type ++ * @param <N> type with an argument native to vanilla Minecraft (from {@link ArgumentTypes}) ++ */ ++ @ApiStatus.Experimental ++ interface Converted<T, N> extends CustomArgumentType<T, N> { ++ ++ @ApiStatus.NonExtendable ++ @Override ++ default T parse(final StringReader reader) throws CommandSyntaxException { ++ return this.convert(this.getNativeType().parse(reader)); ++ } ++ ++ /** ++ * Converts the value from the native type to the custom argument type. ++ * ++ * @param nativeType native argument provided value ++ * @return converted value ++ * @throws CommandSyntaxException if an exception occurs while parsing ++ */ ++ T convert(N nativeType) throws CommandSyntaxException; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/RegistryArgumentExtractor.java b/src/main/java/io/papermc/paper/command/brigadier/argument/RegistryArgumentExtractor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c6123342df9610a97752030955a43df18e8d0cbd +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/RegistryArgumentExtractor.java +@@ -0,0 +1,36 @@ ++package io.papermc.paper.command.brigadier.argument; ++ ++import com.mojang.brigadier.context.CommandContext; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import org.jspecify.annotations.NullMarked; ++ ++/** ++ * Utilities for extracting registry-related arguments from a {@link CommandContext}. ++ */ ++@NullMarked ++public final class RegistryArgumentExtractor { ++ ++ /** ++ * Gets a typed key argument from a command context. ++ * ++ * @param context the command context ++ * @param registryKey the registry key for the typed key ++ * @param name the argument name ++ * @return the typed key argument ++ * @param <T> the value type ++ * @param <S> the sender type ++ * @throws IllegalArgumentException if the registry key doesn't match the typed key ++ */ ++ @SuppressWarnings("unchecked") ++ public static <T, S> TypedKey<T> getTypedKey(final CommandContext<S> context, final RegistryKey<T> registryKey, final String name) { ++ final TypedKey<T> typedKey = context.getArgument(name, TypedKey.class); ++ if (typedKey.registryKey().equals(registryKey)) { ++ return typedKey; ++ } ++ throw new IllegalArgumentException(registryKey + " is not the correct registry for " + typedKey); ++ } ++ ++ private RegistryArgumentExtractor() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2f2c1729c5d9f1a6b6171c0ed5326b9b631f3c20 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolver.java +@@ -0,0 +1,42 @@ ++package io.papermc.paper.command.brigadier.argument; ++ ++import com.mojang.brigadier.context.CommandContext; ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import java.util.concurrent.CompletableFuture; ++import net.kyori.adventure.chat.SignedMessage; ++import org.jetbrains.annotations.ApiStatus; ++import org.jspecify.annotations.NullMarked; ++ ++/** ++ * A resolver for a {@link SignedMessage} ++ * ++ * @see ArgumentTypes#signedMessage() ++ */ ++@NullMarked ++public interface SignedMessageResolver { ++ ++ /** ++ * Gets the string content of the message ++ * ++ * @return string content ++ */ ++ String content(); ++ ++ /** ++ * Resolves this signed message. This will the {@link CommandContext} ++ * and signed arguments sent by the client. ++ * <p> ++ * In the case that signed message information isn't provided, a "system" ++ * signed message will be returned instead. ++ * ++ * @param argumentName argument name ++ * @param context the command context ++ * @return a completable future for the {@link SignedMessage} ++ * @throws CommandSyntaxException syntax exception ++ */ ++ CompletableFuture<SignedMessage> resolveSignedMessage(String argumentName, CommandContext<CommandSourceStack> context) throws CommandSyntaxException; ++ ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProvider.java b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4f640bd3e536fb79db54dcedd5807e7de402acef +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProvider.java +@@ -0,0 +1,106 @@ ++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.FinePositionResolver; ++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 io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import java.util.Optional; ++import java.util.ServiceLoader; ++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.jspecify.annotations.NullMarked; ++ ++@NullMarked ++interface VanillaArgumentProvider { ++ ++ Optional<VanillaArgumentProvider> PROVIDER = ServiceLoader.load(VanillaArgumentProvider.class) ++ .findFirst(); ++ ++ static VanillaArgumentProvider provider() { ++ return PROVIDER.orElseThrow(); ++ } ++ ++ ArgumentType<EntitySelectorArgumentResolver> entity(); ++ ++ ArgumentType<PlayerSelectorArgumentResolver> player(); ++ ++ ArgumentType<EntitySelectorArgumentResolver> entities(); ++ ++ ArgumentType<PlayerSelectorArgumentResolver> players(); ++ ++ ArgumentType<PlayerProfileListResolver> playerProfiles(); ++ ++ ArgumentType<BlockPositionResolver> blockPosition(); ++ ++ ArgumentType<FinePositionResolver> finePosition(boolean centerIntegers); ++ ++ ArgumentType<BlockState> blockState(); ++ ++ ArgumentType<ItemStack> itemStack(); ++ ++ ArgumentType<ItemStackPredicate> itemStackPredicate(); ++ ++ ArgumentType<NamedTextColor> namedColor(); ++ ++ ArgumentType<Component> component(); ++ ++ ArgumentType<Style> style(); ++ ++ ArgumentType<SignedMessageResolver> signedMessage(); ++ ++ ArgumentType<DisplaySlot> scoreboardDisplaySlot(); ++ ++ ArgumentType<NamespacedKey> namespacedKey(); ++ ++ // include both key types as we are slowly moving to use adventure's key ++ ArgumentType<Key> key(); ++ ++ ArgumentType<IntegerRangeProvider> integerRange(); ++ ++ ArgumentType<DoubleRangeProvider> doubleRange(); ++ ++ ArgumentType<World> world(); ++ ++ ArgumentType<GameMode> gameMode(); ++ ++ ArgumentType<HeightMap> heightMap(); ++ ++ ArgumentType<UUID> uuid(); ++ ++ ArgumentType<Criteria> objectiveCriteria(); ++ ++ ArgumentType<LookAnchor> entityAnchor(); ++ ++ ArgumentType<Integer> time(int minTicks); ++ ++ ArgumentType<Mirror> templateMirror(); ++ ++ ArgumentType<StructureRotation> templateRotation(); ++ ++ <T> ArgumentType<TypedKey<T>> resourceKey(RegistryKey<T> registryKey); ++ ++ <T> ArgumentType<T> resource(RegistryKey<T> registryKey); ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/predicate/ItemStackPredicate.java b/src/main/java/io/papermc/paper/command/brigadier/argument/predicate/ItemStackPredicate.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ba0cfb3c53f6a5a29b1719ed271a8f13d5f52f24 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/predicate/ItemStackPredicate.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.command.brigadier.argument.predicate; ++ ++import java.util.function.Predicate; ++import org.bukkit.inventory.ItemStack; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * A predicate for ItemStack. ++ * ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#itemPredicate() ++ */ ++public interface ItemStackPredicate extends Predicate<ItemStack> { ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/range/DoubleRangeProvider.java b/src/main/java/io/papermc/paper/command/brigadier/argument/range/DoubleRangeProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..82c978ba42a787fd0cdc936e42c8e12ffa4ff8bf +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/range/DoubleRangeProvider.java +@@ -0,0 +1,14 @@ ++package io.papermc.paper.command.brigadier.argument.range; ++ ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * A provider for a {@link com.google.common.collect.Range} of doubles. ++ * ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#doubleRange() ++ */ ++public non-sealed interface DoubleRangeProvider extends RangeProvider<Double> { ++ ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/range/IntegerRangeProvider.java b/src/main/java/io/papermc/paper/command/brigadier/argument/range/IntegerRangeProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..06ffff68d2652ef8eb40aa723803c24ecd013721 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/range/IntegerRangeProvider.java +@@ -0,0 +1,14 @@ ++package io.papermc.paper.command.brigadier.argument.range; ++ ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * A provider for a {@link com.google.common.collect.Range} of integers. ++ * ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#integerRange() ++ */ ++public non-sealed interface IntegerRangeProvider extends RangeProvider<Integer> { ++ ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/range/RangeProvider.java b/src/main/java/io/papermc/paper/command/brigadier/argument/range/RangeProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..af1c01ab0d2146225c54a7766788007314f59328 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/range/RangeProvider.java +@@ -0,0 +1,22 @@ ++package io.papermc.paper.command.brigadier.argument.range; ++ ++import com.google.common.collect.Range; ++import org.jetbrains.annotations.ApiStatus; ++import org.jspecify.annotations.NullMarked; ++ ++/** ++ * A provider for a range of numbers ++ * ++ * @param <T> ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes ++ */ ++@NullMarked ++public sealed interface RangeProvider<T extends Comparable<?>> permits DoubleRangeProvider, IntegerRangeProvider { ++ ++ /** ++ * Provides the given range. ++ * @return range ++ */ ++ Range<T> range(); ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/ArgumentResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/ArgumentResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..60439269d8b1535c779ae8bd008c8f28cc7e4133 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/ArgumentResolver.java +@@ -0,0 +1,27 @@ ++package io.papermc.paper.command.brigadier.argument.resolvers; ++ ++import com.mojang.brigadier.exceptions.CommandSyntaxException; ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import org.jetbrains.annotations.ApiStatus; ++import org.jspecify.annotations.NullMarked; ++ ++/** ++ * An {@link ArgumentResolver} is capable of resolving ++ * an argument value using a {@link CommandSourceStack}. ++ * ++ * @param <T> resolved type ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes ++ */ ++@NullMarked ++public interface ArgumentResolver<T> { ++ ++ /** ++ * Resolves the argument with the given ++ * command source stack. ++ * @param sourceStack source stack ++ * @return resolved ++ */ ++ T resolve(CommandSourceStack sourceStack) throws CommandSyntaxException; ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/BlockPositionResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/BlockPositionResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..908f40dbf3e52bdfc8577a8916884e9fa4557a7c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/BlockPositionResolver.java +@@ -0,0 +1,16 @@ ++package io.papermc.paper.command.brigadier.argument.resolvers; ++ ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.math.BlockPosition; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * An {@link ArgumentResolver} that's capable of resolving ++ * a block position argument value using a {@link CommandSourceStack}. ++ * ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#blockPosition() ++ */ ++public interface BlockPositionResolver extends ArgumentResolver<BlockPosition> { ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/FinePositionResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/FinePositionResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e2fc26016b8d68fd0d69c8ca962f61fe65471b24 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/FinePositionResolver.java +@@ -0,0 +1,17 @@ ++package io.papermc.paper.command.brigadier.argument.resolvers; ++ ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.math.FinePosition; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * An {@link ArgumentResolver} that's capable of resolving ++ * a fine position argument value using a {@link CommandSourceStack}. ++ * ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#finePosition() ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#finePosition(boolean) ++ */ ++public interface FinePositionResolver extends ArgumentResolver<FinePosition> { ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/PlayerProfileListResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/PlayerProfileListResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..89024e67fd81a9cd8a9d1ef5bb78d1c8bcb4fcc5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/PlayerProfileListResolver.java +@@ -0,0 +1,17 @@ ++package io.papermc.paper.command.brigadier.argument.resolvers; ++ ++import com.destroystokyo.paper.profile.PlayerProfile; ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import java.util.Collection; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * An {@link ArgumentResolver} that's capable of resolving ++ * argument value using a {@link CommandSourceStack}. ++ * ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#playerProfiles() ++ */ ++public interface PlayerProfileListResolver extends ArgumentResolver<Collection<PlayerProfile>> { ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/EntitySelectorArgumentResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/EntitySelectorArgumentResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..15d05c28040180a00b16cf05c8b059ce66793fa8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/EntitySelectorArgumentResolver.java +@@ -0,0 +1,19 @@ ++package io.papermc.paper.command.brigadier.argument.resolvers.selector; ++ ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.command.brigadier.argument.resolvers.ArgumentResolver; ++import java.util.List; ++import org.bukkit.entity.Entity; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * An {@link ArgumentResolver} that's capable of resolving ++ * an entity selector argument value using a {@link CommandSourceStack}. ++ * ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#entity() ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#entities() ++ */ ++public interface EntitySelectorArgumentResolver extends SelectorArgumentResolver<List<Entity>> { ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/PlayerSelectorArgumentResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/PlayerSelectorArgumentResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a973555b7a013df7f9700841f41220c8afa0301e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/PlayerSelectorArgumentResolver.java +@@ -0,0 +1,19 @@ ++package io.papermc.paper.command.brigadier.argument.resolvers.selector; ++ ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.command.brigadier.argument.resolvers.ArgumentResolver; ++import java.util.List; ++import org.bukkit.entity.Player; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * An {@link ArgumentResolver} that's capable of resolving ++ * a player selector argument value using a {@link CommandSourceStack}. ++ * ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#player() ++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#players() ++ */ ++public interface PlayerSelectorArgumentResolver extends SelectorArgumentResolver<List<Player>> { ++} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/SelectorArgumentResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/SelectorArgumentResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..906ce6eff30ebd9ec3010ce03b471418843e6588 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/SelectorArgumentResolver.java +@@ -0,0 +1,17 @@ ++package io.papermc.paper.command.brigadier.argument.resolvers.selector; ++ ++import io.papermc.paper.command.brigadier.CommandSourceStack; ++import io.papermc.paper.command.brigadier.argument.resolvers.ArgumentResolver; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * An {@link ArgumentResolver} that's capable of resolving ++ * a selector argument value using a {@link CommandSourceStack}. ++ * ++ * @param <T> resolved type ++ * @see <a href="https://minecraft.wiki/w/Target_selectors">Target Selectors</a> ++ */ ++public interface SelectorArgumentResolver<T> extends ArgumentResolver<T> { ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java +index f70814de0d6c40b2c1c9921b8abdd1162e1d3995..ab6b262cf0d2d17962ed012b2ea7b8f1db8bc576 100644 +--- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java +@@ -1,9 +1,11 @@ + package io.papermc.paper.plugin.lifecycle.event.types; + ++import io.papermc.paper.command.brigadier.Commands; + import io.papermc.paper.plugin.bootstrap.BootstrapContext; + import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; + import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; + import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; + import org.bukkit.plugin.Plugin; + import org.jetbrains.annotations.ApiStatus; + import org.jspecify.annotations.NullMarked; +@@ -17,6 +19,13 @@ import org.jspecify.annotations.NullMarked; + @NullMarked + public final class LifecycleEvents { + ++ /** ++ * This event is for registering commands to the server's brigadier command system. You can register a handler for this event in ++ * {@link org.bukkit.plugin.java.JavaPlugin#onEnable()} or {@link io.papermc.paper.plugin.bootstrap.PluginBootstrap#bootstrap(BootstrapContext)}. ++ * @see Commands an example of a command being registered ++ */ ++ public static final LifecycleEventType.Prioritizable<LifecycleEventOwner, ReloadableRegistrarEvent<Commands>> COMMANDS = prioritized("commands", LifecycleEventOwner.class); ++ + //<editor-fold desc="helper methods" defaultstate="collapsed"> + @ApiStatus.Internal + static <E extends LifecycleEvent> LifecycleEventType.Monitorable<Plugin, E> plugin(final String name) { +diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java +index 03d2643d166824458c88a49f20270e93b14f3988..0a26fffe9b1e5080b5639767a03af11006108b4a 100644 +--- a/src/main/java/org/bukkit/command/Command.java ++++ b/src/main/java/org/bukkit/command/Command.java +@@ -520,4 +520,9 @@ public abstract class Command { + public String toString() { + return getClass().getName() + '(' + name + ')'; + } ++ ++ // Paper start ++ @org.jetbrains.annotations.ApiStatus.Internal ++ public boolean canBeOverriden() { return false; } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/command/FormattedCommandAlias.java b/src/main/java/org/bukkit/command/FormattedCommandAlias.java +index 9d4f553c04784cca63901a56a7aea62a5cae1d72..abe256e1e45ce28036da4aa1586715bc8a1a3414 100644 +--- a/src/main/java/org/bukkit/command/FormattedCommandAlias.java ++++ b/src/main/java/org/bukkit/command/FormattedCommandAlias.java +@@ -117,7 +117,7 @@ public class FormattedCommandAlias extends Command { + index = formatString.indexOf('$', index); + } + +- return formatString; ++ return formatString.trim(); // Paper - Causes an extra space at the end, breaks with brig commands + } + + @NotNull +diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java +index b5f9cd2bd191f8b071c6c95706ddbef97d3c244e..5df19bd701c67506689fc7f49d91f99ebfbc83f0 100644 +--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java ++++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java +@@ -23,10 +23,14 @@ import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + + public class SimpleCommandMap implements CommandMap { +- protected final Map<String, Command> knownCommands = new HashMap<String, Command>(); ++ protected final Map<String, Command> knownCommands; // Paper + private final Server server; + +- public SimpleCommandMap(@NotNull final Server server) { ++ // Paper start ++ @org.jetbrains.annotations.ApiStatus.Internal ++ public SimpleCommandMap(@NotNull final Server server, Map<String, Command> backing) { ++ this.knownCommands = backing; ++ // Paper end + this.server = server; + setDefaultCommands(); + } +@@ -103,7 +107,10 @@ public class SimpleCommandMap implements CommandMap { + */ + private synchronized boolean register(@NotNull String label, @NotNull Command command, boolean isAlias, @NotNull String fallbackPrefix) { + knownCommands.put(fallbackPrefix + ":" + label, command); +- if ((command instanceof BukkitCommand || isAlias) && knownCommands.containsKey(label)) { ++ // Paper start ++ Command known = knownCommands.get(label); ++ if ((command instanceof BukkitCommand || isAlias) && (known != null && !known.canBeOverriden())) { ++ // Paper end + // Request is for an alias/fallback command and it conflicts with + // a existing command or previous alias ignore it + // Note: This will mean it gets removed from the commands list of active aliases +@@ -115,7 +122,9 @@ public class SimpleCommandMap implements CommandMap { + // If the command exists but is an alias we overwrite it, otherwise we return + Command conflict = knownCommands.get(label); + if (conflict != null && conflict.getLabel().equals(label)) { ++ if (!conflict.canBeOverriden()) { // Paper + return false; ++ } // Paper + } + + if (!isAlias) { +diff --git a/src/main/java/org/bukkit/command/defaults/ReloadCommand.java b/src/main/java/org/bukkit/command/defaults/ReloadCommand.java +index 3ec32b46264cfff857b50129b5e0fa5584943ec6..bdfe68b386b5ca2878475e548d3c9a3808fce848 100644 +--- a/src/main/java/org/bukkit/command/defaults/ReloadCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/ReloadCommand.java +@@ -18,6 +18,9 @@ public class ReloadCommand extends BukkitCommand { + this.setAliases(Arrays.asList("rl")); + } + ++ @org.jetbrains.annotations.ApiStatus.Internal // Paper ++ public static final String RELOADING_DISABLED_MESSAGE = "A lifecycle event handler has been registered which makes reloading plugins not possible"; // Paper ++ + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { // Paper + if (!testPermission(sender)) return true; +@@ -51,7 +54,16 @@ public class ReloadCommand extends BukkitCommand { + + Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues when using some plugins."); + Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); +- Bukkit.reload(); ++ // Paper start - lifecycle events ++ try { ++ Bukkit.reload(); ++ } catch (final IllegalStateException ex) { ++ if (ex.getMessage().equals(RELOADING_DISABLED_MESSAGE)) { ++ Command.broadcastCommandMessage(sender, ChatColor.RED + RELOADING_DISABLED_MESSAGE); ++ return true; ++ } ++ } ++ // Paper end - lifecycle events + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Reload complete."); + + return true; +diff --git a/src/main/java/org/bukkit/event/server/TabCompleteEvent.java b/src/main/java/org/bukkit/event/server/TabCompleteEvent.java +index 6465e290c090d82986352d5ab7ba5dc65bd3dc17..c71c122ccc4775d030688f7b8df0b4feb49136f4 100644 +--- a/src/main/java/org/bukkit/event/server/TabCompleteEvent.java ++++ b/src/main/java/org/bukkit/event/server/TabCompleteEvent.java +@@ -18,6 +18,8 @@ import org.jetbrains.annotations.NotNull; + * themselves. Plugins wishing to remove commands from tab completion are + * advised to ensure the client does not have permission for the relevant + * commands, or use {@link PlayerCommandSendEvent}. ++ * @apiNote Only called for bukkit API commands {@link org.bukkit.command.Command} and ++ * {@link org.bukkit.command.CommandExecutor} and not for brigadier commands ({@link io.papermc.paper.command.brigadier.Commands}). + */ + public class TabCompleteEvent extends Event implements Cancellable { + |