aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOwen <[email protected]>2024-05-11 16:30:30 -0400
committerGitHub <[email protected]>2024-05-11 16:30:30 -0400
commitb98d20a8ac9c21789d532652df86638a202093c7 (patch)
treecb3418675f0cb5c7ccd995d2c298fcf13c3729b0
parent447f9a1e16f2273c656f4dc95f1ea838319d0fff (diff)
downloadPaper-b98d20a8ac9c21789d532652df86638a202093c7.tar.gz
Paper-b98d20a8ac9c21789d532652df86638a202093c7.zip
Brigadier Command Support (#8235)
Adds the ability for plugins to register their own brigadier commands --------- Co-authored-by: Jake Potrebic <[email protected]> Co-authored-by: Jason Penilla <[email protected]> Co-authored-by: Bjarne Koll <[email protected]>
-rw-r--r--Paper-MojangAPI/build.gradle.kts36
-rw-r--r--Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java14
-rw-r--r--Paper-MojangAPI/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java21
-rw-r--r--Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java72
-rw-r--r--Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java84
-rw-r--r--Paper-MojangAPI/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java168
-rw-r--r--Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java42
-rw-r--r--Paper-MojangAPI/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProvider.java30
-rw-r--r--build.gradle.kts3
-rw-r--r--patches/api/0480-Brigadier-based-command-API.patch1900
-rw-r--r--patches/server/0020-Plugin-remapping.patch18
-rw-r--r--patches/server/0092-Configurable-Player-Collision.patch4
-rw-r--r--patches/server/0231-Add-Early-Warning-Feature-to-WatchDog.patch6
-rw-r--r--patches/server/0282-Brigadier-Mojang-API.patch12
-rw-r--r--patches/server/0362-Implement-Mob-Goal-API.patch6
-rw-r--r--patches/server/0612-Vanilla-command-permission-fixes.patch12
-rw-r--r--patches/server/0722-Add-support-for-Proxy-Protocol.patch4
-rw-r--r--patches/server/1011-Use-Velocity-compression-and-cipher-natives.patch4
-rw-r--r--patches/server/1038-Add-experimental-improved-give-command.patch8
-rw-r--r--patches/server/1050-Brigadier-based-command-API.patch2770
-rw-r--r--settings.gradle.kts2
-rw-r--r--test-plugin/build.gradle.kts1
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java3
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java1
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java166
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ComponentCommandExceptionType.java25
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ExampleAdminCommand.java154
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamType.java9
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamTypeArgument.java47
-rw-r--r--test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/MaterialArgumentType.java88
30 files changed, 5195 insertions, 515 deletions
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<PublishingExtension> {
- publications.create<MavenPublication>("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 <S> command source type
- */
-public interface BukkitBrigadierCommand <S extends BukkitBrigadierCommandSource> extends Command<S>, Predicate<S>, SuggestionProvider<S> {
-}
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.
- *
- * <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>
- */
-public class AsyncPlayerSendCommandsEvent <S extends BukkitBrigadierCommandSource> extends PlayerEvent {
-
- private static final HandlerList handlers = new HandlerList();
- private final RootCommandNode<S> node;
- private final boolean hasFiredAsync;
-
- public AsyncPlayerSendCommandsEvent(Player player, RootCommandNode<S> 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<S> 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.
- *
- * <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>
- */
-public class CommandRegisteredEvent<S extends BukkitBrigadierCommandSource> extends ServerEvent implements Cancellable {
-
- private static final HandlerList handlers = new HandlerList();
- private final String commandLabel;
- private final Command command;
- private final 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, 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/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}.
- *
- * <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 PaperBrigadierProvider.instance().message(componentLike);
- }
-
- /**
- * 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 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 <[email protected]>
+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<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..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.
++ *
++ * <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>
++ */
++public class AsyncPlayerSendCommandsEvent <S extends com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource> extends PlayerEvent {
++
++ private static final HandlerList handlers = new HandlerList();
++ private final RootCommandNode<S> node;
++ private final boolean hasFiredAsync;
++
++ @ApiStatus.Internal
++ public AsyncPlayerSendCommandsEvent(@NotNull Player player, @NotNull RootCommandNode<S> 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<S> 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.
++ *
++ * <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")
++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..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 <greedy_string>}.
++ */
++@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<String> 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.
++ * <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..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.
++ * <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>
++ */
++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}.
++ * <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
++ */
++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<CommandSourceStack> 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 <T> the generic type of the argument value
++ * @return a new required argument builder
++ */
++ static <T> @NotNull RequiredArgumentBuilder<CommandSourceStack, T> argument(final @NotNull String name, final @NotNull 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
++ @NotNull 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 @NotNull Set<String> register(final @NotNull 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 @NotNull Set<String> register(final @NotNull 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 @NotNull Set<String> register(final @NotNull LiteralCommandNode<CommandSourceStack> node, final @NotNull 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 @NotNull Set<String> register(@NotNull LiteralCommandNode<CommandSourceStack> node, @Nullable String description, @NotNull 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 @NotNull Set<String> register(@NotNull PluginMeta pluginMeta, @NotNull LiteralCommandNode<CommandSourceStack> node, @Nullable String description, @NotNull 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 @NotNull Set<String> registerWithFlags(@NotNull PluginMeta pluginMeta, @NotNull LiteralCommandNode<CommandSourceStack> node, @Nullable String description, @NotNull Collection<String> aliases, @NotNull 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 @NotNull Set<String> 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<String> 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<String> register(final @NotNull String label, final @NotNull Collection<String> 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<String> register(@NotNull String label, @Nullable String description, @NotNull Collection<String> 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<String> register(@NotNull PluginMeta pluginMeta, @NotNull String label, @Nullable String description, @NotNull Collection<String> 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}.
++ */
++public interface MessageComponentSerializer extends ComponentSerializer<Component, Component, Message> {
++
++ /**
++ * 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;
++
++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..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.
++ *
++ * <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>
++ */
++public final class ArgumentTypes {
++
++ /**
++ * Represents a selector that can capture any
++ * single entity.
++ *
++ * @return argument that takes one entity
++ */
++ public static @NotNull ArgumentType<EntitySelectorArgumentResolver> entity() {
++ return provider().entity();
++ }
++
++ /**
++ * Represents a selector that can capture multiple
++ * entities.
++ *
++ * @return argument that takes multiple entities
++ */
++ public static @NotNull ArgumentType<EntitySelectorArgumentResolver> entities() {
++ return provider().entities();
++ }
++
++ /**
++ * Represents a selector that can capture a
++ * singular player entity.
++ *
++ * @return argument that takes one player
++ */
++ public static @NotNull ArgumentType<PlayerSelectorArgumentResolver> player() {
++ return provider().player();
++ }
++
++ /**
++ * Represents a selector that can capture multiple
++ * player entities.
++ *
++ * @return argument that takes multiple players
++ */
++ public static @NotNull ArgumentType<PlayerSelectorArgumentResolver> players() {
++ return provider().players();
++ }
++
++ /**
++ * A selector argument that provides a list
++ * of player profiles.
++ *
++ * @return player profile arguments
++ */
++ public static @NotNull ArgumentType<PlayerProfileListResolver> playerProfiles() {
++ return provider().playerProfiles();
++ }
++
++ /**
++ * A block position argument.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<BlockPositionResolver> 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> 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> itemStack() {
++ return provider().itemStack();
++ }
++
++ /**
++ * An item predicate argument.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<ItemStackPredicate> itemPredicate() {
++ return provider().itemStackPredicate();
++ }
++
++ /**
++ * An argument for parsing {@link NamedTextColor}s.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<NamedTextColor> namedColor() {
++ return provider().namedColor();
++ }
++
++ /**
++ * A component argument.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<Component> component() {
++ return provider().component();
++ }
++
++ /**
++ * A style argument.
++ *
++ * @return argument
++ */
++ public static @NotNull 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 @NotNull ArgumentType<SignedMessageResolver> signedMessage() {
++ return provider().signedMessage();
++ }
++
++ /**
++ * A scoreboard display slot argument.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<DisplaySlot> scoreboardDisplaySlot() {
++ return provider().scoreboardDisplaySlot();
++ }
++
++ /**
++ * A namespaced key argument.
++ *
++ * @return argument
++ */
++ public static @NotNull 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 @NotNull ArgumentType<Key> key() {
++ return provider().key();
++ }
++
++ /**
++ * An inclusive range of integers that may be unbounded on either end.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<IntegerRangeProvider> integerRange() {
++ return provider().integerRange();
++ }
++
++ /**
++ * An inclusive range of doubles that may be unbounded on either end.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<DoubleRangeProvider> doubleRange() {
++ return provider().doubleRange();
++ }
++
++ /**
++ * A world argument.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<World> world() {
++ return provider().world();
++ }
++
++ /**
++ * A game mode argument.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<GameMode> gameMode() {
++ return provider().gameMode();
++ }
++
++ /**
++ * A argument for getting a heightmap type.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<HeightMap> heightMap() {
++ return provider().heightMap();
++ }
++
++ /**
++ * A uuid argument.
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<UUID> uuid() {
++ return provider().uuid();
++ }
++
++ /**
++ * An objective criteria argument
++ *
++ * @return argument
++ */
++ public static @NotNull ArgumentType<Criteria> objectiveCriteria() {
++ return provider().objectiveCriteria();
++ }
++
++ /**
++ * An entity anchor argument.
++ *
++ * @return argument
++ */
++ public static @NotNull 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 @NotNull 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 @NotNull ArgumentType<Integer> time(final int mintime) {
++ return provider().time(mintime);
++ }
++
++ /**
++ * A template mirror argument
++ *
++ * @return argument
++ * @see Mirror
++ */
++ public static @NotNull ArgumentType<Mirror> templateMirror() {
++ return provider().templateMirror();
++ }
++
++ /**
++ * A template rotation argument.
++ *
++ * @return argument
++ * @see StructureRotation
++ */
++ public static @NotNull ArgumentType<StructureRotation> templateRotation() {
++ return provider().templateRotation();
++ }
++
++ 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..02acac7f9186677d19c0a62095cc3012bc112961
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java
+@@ -0,0 +1,106 @@
++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.jetbrains.annotations.NotNull;
++
++/**
++ * 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})
++ */
++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
++ @NotNull T parse(final @NotNull StringReader reader) throws CommandSyntaxException;
++
++ /**
++ * Gets the native type that this argument uses,
++ * the type that is sent to the client.
++ *
++ * @return native argument type
++ */
++ @NotNull 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 @NotNull 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> @NotNull CompletableFuture<Suggestions> listSuggestions(final @NotNull CommandContext<S> context, final @NotNull 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 @NotNull T parse(final @NotNull 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
++ */
++ @NotNull T convert(@NotNull N nativeType) throws CommandSyntaxException;
++ }
++}
+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..159b691e7a1a7066f3e706e80d75ca8f87a3a964
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolver.java
+@@ -0,0 +1,41 @@
++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.jetbrains.annotations.NotNull;
++
++/**
++ * A resolver for a {@link SignedMessage}
++ *
++ * @see ArgumentTypes#signedMessage()
++ */
++public interface SignedMessageResolver {
++
++ /**
++ * Gets the string content of the message
++ *
++ * @return string content
++ */
++ @NotNull 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
++ */
++ @NotNull CompletableFuture<SignedMessage> resolveSignedMessage(@NotNull String argumentName, @NotNull 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..da9afa07f919ab139645f06e23b308783d01357a
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProvider.java
+@@ -0,0 +1,98 @@
++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.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.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.ApiStatus;
++
++@DefaultQualifier(NonNull.class)
++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<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();
++}
+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..651dbf15c155f8d8fef25785300d44752c388b37
+--- /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.jetbrains.annotations.NotNull;
++
++/**
++ * A provider for a range of numbers
++ *
++ * @param <T>
++ * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes
++ */
++public sealed interface RangeProvider<T extends Comparable<?>> permits DoubleRangeProvider, IntegerRangeProvider {
++
++ /**
++ * Provides the given range.
++ * @return range
++ */
++ @NotNull
++ 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..dea24d91999f78b77fe85221130d87a54edf004a
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/ArgumentResolver.java
+@@ -0,0 +1,26 @@
++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.jetbrains.annotations.NotNull;
++
++/**
++ * 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
++ */
++public interface ArgumentResolver<T> {
++
++ /**
++ * Resolves the argument with the given
++ * command source stack.
++ * @param sourceStack source stack
++ * @return resolved
++ */
++ @NotNull T resolve(@NotNull 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/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 304f978e40e1759bb19704cc5cec399500905195..1fab48593c567fe05b085ac6e12dc22556cf0b92 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;
+
+@@ -15,6 +17,13 @@ import org.jetbrains.annotations.ApiStatus;
+ @ApiStatus.Experimental
+ 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
+ private 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 b3a2c274f05156fd603bcc7a68ab41265f2eaf44..c7cdc2ad8a2c43e8c0fcaa1761d3b81726c5ebcb 100644
+--- a/src/main/java/org/bukkit/command/Command.java
++++ b/src/main/java/org/bukkit/command/Command.java
+@@ -512,4 +512,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 ac9a28922f8a556944a4c3649d74c32c622f0cb0..c3a9cf65db73ed534bf20996c7f05b5eb0aaebe1 100644
+--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
++++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
+@@ -22,10 +22,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();
+ }
+@@ -102,7 +106,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
+@@ -114,7 +121,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/patches/server/0020-Plugin-remapping.patch b/patches/server/0020-Plugin-remapping.patch
index 251d549a9b..0c33852103 100644
--- a/patches/server/0020-Plugin-remapping.patch
+++ b/patches/server/0020-Plugin-remapping.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Plugin remapping
Co-authored-by: Nassim Jahnke <[email protected]>
diff --git a/build.gradle.kts b/build.gradle.kts
-index 65fb16941fa7e3a9b300696fb6bd2b562bca48cd..5ffd1d7c130e01a4a7516b361e48bfaf41d4f321 100644
+index 66cdd81e4b65ce00973f86763cea566e43053722..2868eb8f9e577ce839d7ecf5ce8fed5bad957dbe 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -46,6 +46,7 @@ dependencies {
@@ -1553,17 +1553,17 @@ index 0000000000000000000000000000000000000000..badff5d6ae6dd8d209c82bc7e8afe370
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index c25d80a1d5aa0f3cc2cbf1e9b94154c759aab36e..c836204a04ca050988057dcc92c7a1fbcc02ef34 100644
+index c25d80a1d5aa0f3cc2cbf1e9b94154c759aab36e..cbbd9aaeb0d87aae72edc7fb5aa10920834de8bf 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -637,6 +637,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+@@ -636,6 +636,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ }
this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
- this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
+ this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
this.connection.acceptConnections();
}
-
@@ -909,6 +910,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.server.disablePlugins();
}
@@ -1904,14 +1904,14 @@ index 0000000000000000000000000000000000000000..73b20a92f330311e3fef8f03b51a0985
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index 45160b93a24dc74f6368441e2a4fe659ceaf5bf5..6573e72d041714ccc2bf0e3c8734bc212caf534e 100644
+index 45160b93a24dc74f6368441e2a4fe659ceaf5bf5..48be9bd462abba1f82200fe3425c36bf8ec91beb 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -966,6 +966,7 @@ public final class CraftServer implements Server {
+@@ -965,6 +965,7 @@ public final class CraftServer implements Server {
+ this.loadPlugins();
this.enablePlugins(PluginLoadOrder.STARTUP);
this.enablePlugins(PluginLoadOrder.POSTWORLD);
- this.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD));
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
+ this.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD));
}
- @Override
diff --git a/patches/server/0092-Configurable-Player-Collision.patch b/patches/server/0092-Configurable-Player-Collision.patch
index 17ddfb3ccf..6fe211a509 100644
--- a/patches/server/0092-Configurable-Player-Collision.patch
+++ b/patches/server/0092-Configurable-Player-Collision.patch
@@ -18,7 +18,7 @@ index 9a1a961eabd4362c171da78c6be82c867f3696a4..1d0c473442b5c72245c356054440323e
ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.playerPrefix);
ComponentSerialization.TRUSTED_STREAM_CODEC.encode(buf, this.playerSuffix);
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 4dc8fcd8e118a1c2f5fac0fc291b5555abeec124..53a344b3ee3813872f5f061aab660bf602b573a5 100644
+index 4712da0fcc454c1747e2677be821959c6f845f5e..6a5f4e8feb3c88662a78575590998efdf529bb51 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -636,6 +636,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -40,8 +40,8 @@ index 4dc8fcd8e118a1c2f5fac0fc291b5555abeec124..53a344b3ee3813872f5f061aab660bf6
+ // Paper end - Configurable player collision
+
this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
- this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
+ this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index abb769cbbed17a82ee86a6c99e61a375045d9937..3fb300026e627313c65ea23b9c0a9f57a97fa310 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
diff --git a/patches/server/0231-Add-Early-Warning-Feature-to-WatchDog.patch b/patches/server/0231-Add-Early-Warning-Feature-to-WatchDog.patch
index bcc6d21efc..4a8f5a4941 100644
--- a/patches/server/0231-Add-Early-Warning-Feature-to-WatchDog.patch
+++ b/patches/server/0231-Add-Early-Warning-Feature-to-WatchDog.patch
@@ -9,7 +9,7 @@ thread dumps at an interval until the point of crash.
This will help diagnose what was going on in that time before the crash.
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 478445e7ed67b17d7aff0e10e9aae0788605a4b9..077b2e1d06a4bb0c2ce10274dc6144ee76608d90 100644
+index 8f5eaecb0bda3c4d69bb077ca727458f3f190f44..5fe925b85a7783e3b972d24e01cfe4a4b878dd38 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1104,6 +1104,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@@ -33,7 +33,7 @@ index 0ffa25a6e41cc56e78c79e0cee45e3b811794129..1b47e228ad7365b31d6ddd8c572d3bc5
com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index cbdfb4168f23bbbd37675da6dc88acb3191d88d6..9c673642b7c35bc3c443bd66fbcd278073eeccc2 100644
+index 77a9c54c77dd12df870eb60d15b381f12392e8d9..9ff04719f0b560c286d97c8ed99b04d3b32900e3 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -926,6 +926,7 @@ public final class CraftServer implements Server {
@@ -46,8 +46,8 @@ index cbdfb4168f23bbbd37675da6dc88acb3191d88d6..9c673642b7c35bc3c443bd66fbcd2780
this.commandsConfiguration = YamlConfiguration.loadConfiguration(this.getCommandsConfigFile());
@@ -1016,6 +1017,7 @@ public final class CraftServer implements Server {
this.enablePlugins(PluginLoadOrder.POSTWORLD);
- this.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD));
if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
+ this.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD));
+ org.spigotmc.WatchdogThread.hasStarted = true; // Paper - Disable watchdog early timeout on reload
}
diff --git a/patches/server/0282-Brigadier-Mojang-API.patch b/patches/server/0282-Brigadier-Mojang-API.patch
index a406a36b20..5e4c8c55ff 100644
--- a/patches/server/0282-Brigadier-Mojang-API.patch
+++ b/patches/server/0282-Brigadier-Mojang-API.patch
@@ -9,18 +9,6 @@ Adds AsyncPlayerSendCommandsEvent
Adds CommandRegisteredEvent
- Allows manipulating the CommandNode to add more children/metadata for the client
-diff --git a/build.gradle.kts b/build.gradle.kts
-index e9498f78cb6c0973a820f093ff7a31bef44ba27f..db2d67c98c62aa90591fea82e8fb07270699d96c 100644
---- a/build.gradle.kts
-+++ b/build.gradle.kts
-@@ -13,6 +13,7 @@ val alsoShade: Configuration by configurations.creating
-
- dependencies {
- implementation(project(":paper-api"))
-+ implementation(project(":paper-mojangapi"))
- // Paper start
- implementation("org.jline:jline-terminal-jansi:3.21.0")
- implementation("net.minecrell:terminalconsoleappender:1.3.0")
diff --git a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java
index 3370731ee064d2693b972a0765c13dd4fd69f66a..09d486a05179b9d878e1c33725b4e614c3544da9 100644
--- a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java
diff --git a/patches/server/0362-Implement-Mob-Goal-API.patch b/patches/server/0362-Implement-Mob-Goal-API.patch
index 56f3af0c73..2e62f69cbb 100644
--- a/patches/server/0362-Implement-Mob-Goal-API.patch
+++ b/patches/server/0362-Implement-Mob-Goal-API.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Implement Mob Goal API
diff --git a/build.gradle.kts b/build.gradle.kts
-index db2d67c98c62aa90591fea82e8fb07270699d96c..7a70c2c52dec44d6b6c7acc7140b2619e56646d0 100644
+index 158779a3590f089c4224b2b128c2e653aef42a94..4f8b8839f4d345f448d30de21ad9f3ad7422f98b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
-@@ -41,6 +41,7 @@ dependencies {
+@@ -40,6 +40,7 @@ dependencies {
runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")
runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18")
@@ -773,7 +773,7 @@ index 6667ecc4b7eded4e20a415cef1e1b1179e6710b8..16f9a98b8a939e5ca7e2dc04f87134a7
LOOK,
JUMP,
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index c62936e7070d794aeac7e5175757aa045a6afcae..82d462fbc8936b70169ca3b55d488e1ef76aec40 100644
+index 821e50c456679f69a9b6ae058f35b601bf3c759b..bd4c982ba919f0196723b4c45be102b47054a70f 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2907,5 +2907,11 @@ public final class CraftServer implements Server {
diff --git a/patches/server/0612-Vanilla-command-permission-fixes.patch b/patches/server/0612-Vanilla-command-permission-fixes.patch
index e7be2c02c2..ee87698d16 100644
--- a/patches/server/0612-Vanilla-command-permission-fixes.patch
+++ b/patches/server/0612-Vanilla-command-permission-fixes.patch
@@ -51,21 +51,19 @@ index 3728b051b9eb9e9e06bc765a9a2fae7f45daf6ff..779fee2f9b819124a01b9f8d2b7ed0d5
}
diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
-index 5e6645e16b185aaa6f719055ddbf670b8741fead..bda9a0b99184adce28bb7851612ed7f4e324826d 100644
+index 5e6645e16b185aaa6f719055ddbf670b8741fead..98d314cd293d462ef109e952f3239e08e14dda59 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
-@@ -86,7 +86,23 @@ public final class VanillaCommandWrapper extends BukkitCommand {
+@@ -86,7 +86,21 @@ public final class VanillaCommandWrapper extends BukkitCommand {
}
public static String getPermission(CommandNode<CommandSourceStack> vanillaCommand) {
- return "minecraft.command." + ((vanillaCommand.getRedirect() == null) ? vanillaCommand.getName() : vanillaCommand.getRedirect().getName());
+ // Paper start - Vanilla command permission fixes
-+ final String commandName;
-+ if (vanillaCommand.getRedirect() == null) {
-+ commandName = vanillaCommand.getName();
-+ } else {
-+ commandName = vanillaCommand.getRedirect().getName();
++ while (vanillaCommand.getRedirect() != null) {
++ vanillaCommand = vanillaCommand.getRedirect();
+ }
++ final String commandName = vanillaCommand.getName();
+ return "minecraft.command." + stripDefaultNamespace(commandName);
+ }
+
diff --git a/patches/server/0722-Add-support-for-Proxy-Protocol.patch b/patches/server/0722-Add-support-for-Proxy-Protocol.patch
index 1ffaba0911..fa64d53a92 100644
--- a/patches/server/0722-Add-support-for-Proxy-Protocol.patch
+++ b/patches/server/0722-Add-support-for-Proxy-Protocol.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Add support for Proxy Protocol
diff --git a/build.gradle.kts b/build.gradle.kts
-index 7a70c2c52dec44d6b6c7acc7140b2619e56646d0..9b9e744d18cf66279f51f950b6ecce31415f9fa8 100644
+index 4f8b8839f4d345f448d30de21ad9f3ad7422f98b..1ed8d1ba75fa3a31469a6e8e3b661328b0debf12 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
-@@ -28,6 +28,7 @@ dependencies {
+@@ -27,6 +27,7 @@ dependencies {
log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins
runtimeOnly(log4jPlugins.output)
alsoShade(log4jPlugins.output)
diff --git a/patches/server/1011-Use-Velocity-compression-and-cipher-natives.patch b/patches/server/1011-Use-Velocity-compression-and-cipher-natives.patch
index 95f9b5544d..713ad85d36 100644
--- a/patches/server/1011-Use-Velocity-compression-and-cipher-natives.patch
+++ b/patches/server/1011-Use-Velocity-compression-and-cipher-natives.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Use Velocity compression and cipher natives
diff --git a/build.gradle.kts b/build.gradle.kts
-index 9b9e744d18cf66279f51f950b6ecce31415f9fa8..5d448d8a7cf6626a11791f30ad52baf41a099272 100644
+index 1ed8d1ba75fa3a31469a6e8e3b661328b0debf12..87bb3fd9b97506f61734ae7f2e6860610ba794e7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
-@@ -37,6 +37,11 @@ dependencies {
+@@ -36,6 +36,11 @@ dependencies {
runtimeOnly("org.xerial:sqlite-jdbc:3.45.3.0")
runtimeOnly("com.mysql:mysql-connector-j:8.3.0")
runtimeOnly("com.lmax:disruptor:3.4.4") // Paper
diff --git a/patches/server/1038-Add-experimental-improved-give-command.patch b/patches/server/1038-Add-experimental-improved-give-command.patch
index 4ff1984b05..fd226dcb27 100644
--- a/patches/server/1038-Add-experimental-improved-give-command.patch
+++ b/patches/server/1038-Add-experimental-improved-give-command.patch
@@ -226,13 +226,13 @@ index 0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a..47355158e5e762540a10dc67b23092a0
private static int giveItem(CommandSourceStack source, ItemInput item, Collection<ServerPlayer> targets, int count) throws CommandSyntaxException {
diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
-index 61115db85b81e627d11a0de21691a2ca69aafe2c..ba2a2ca0c36e61cb3cc00fafc7a5dd9f7050388f 100644
+index 2ee33c55890fa659f6d251e486264c85d9e89802..dd1507f65a7f1d84bc7f236f81a60ac1302a13b8 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
-@@ -98,6 +98,9 @@ public final class VanillaCommandWrapper extends BukkitCommand {
- } else {
- commandName = vanillaCommand.getRedirect().getName();
+@@ -96,6 +96,9 @@ public final class VanillaCommandWrapper extends BukkitCommand {
+ vanillaCommand = vanillaCommand.getRedirect();
}
+ final String commandName = vanillaCommand.getName();
+ if ("pgive".equals(stripDefaultNamespace(commandName))) {
+ return "bukkit.command.paper.pgive";
+ }
diff --git a/patches/server/1050-Brigadier-based-command-API.patch b/patches/server/1050-Brigadier-based-command-API.patch
new file mode 100644
index 0000000000..fb030daa83
--- /dev/null
+++ b/patches/server/1050-Brigadier-based-command-API.patch
@@ -0,0 +1,2770 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Mon, 1 Aug 2022 22:50:34 -0400
+Subject: [PATCH] Brigadier based command API
+
+== AT ==
+public net.minecraft.commands.arguments.blocks.BlockInput tag
+public net.minecraft.commands.arguments.DimensionArgument ERROR_INVALID_VALUE
+public net.minecraft.server.ReloadableServerResources registryLookup
+public net.minecraft.server.ReloadableServerResources
+
+diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java
+index 4b4f812eb13d5f03bcf3f8724d8aa8dbbc724e8b..a4d5d7017e0be79844b996de85a63cad5f8488bc 100644
+--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java
++++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java
+@@ -459,7 +459,7 @@ public class CommandDispatcher<S> {
+ }
+
+ private String getSmartUsage(final CommandNode<S> node, final S source, final boolean optional, final boolean deep) {
+- if (!node.canUse(source)) {
++ if (source != null && !node.canUse(source)) { // Paper
+ return null;
+ }
+
+@@ -473,7 +473,7 @@ public class CommandDispatcher<S> {
+ final String redirect = node.getRedirect() == this.root ? "..." : "-> " + node.getRedirect().getUsageText();
+ return self + CommandDispatcher.ARGUMENT_SEPARATOR + redirect;
+ } else {
+- final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList());
++ final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> source == null || c.canUse(source)).collect(Collectors.toList()); // Paper
+ if (children.size() == 1) {
+ final String usage = this.getSmartUsage(children.iterator().next(), source, childOptional, childOptional);
+ if (usage != null) {
+diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java
+index 1f4963bf4681a771130abc1da179819626ecfc1f..03ce8a2abb6dceaa922dcce7f3adbc228bbde4bc 100644
+--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java
++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java
+@@ -35,6 +35,8 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
+ private final boolean forks;
+ private Command<S> command;
+ public CommandNode<CommandSourceStack> clientNode; // Paper - Brigadier API
++ public CommandNode<io.papermc.paper.command.brigadier.CommandSourceStack> unwrappedCached = null; // Paper - Brigadier Command API
++ public CommandNode<io.papermc.paper.command.brigadier.CommandSourceStack> wrappedCached = null; // Paper - Brigadier Command API
+ // CraftBukkit start
+ public void removeCommand(String name) {
+ this.children.remove(name);
+@@ -203,4 +205,11 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
+ }
+
+ public abstract Collection<String> getExamples();
++ // Paper start - Brigadier Command API
++ public void clearAll() {
++ this.children.clear();
++ this.literals.clear();
++ this.arguments.clear();
++ }
++ // Paper end - Brigadier Command API
+ }
+diff --git a/src/main/java/io/papermc/paper/brigadier/NullCommandSender.java b/src/main/java/io/papermc/paper/brigadier/NullCommandSender.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..367ef7e0769537e8c13c7fd818a1249e15a28a65
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/brigadier/NullCommandSender.java
+@@ -0,0 +1,151 @@
++package io.papermc.paper.brigadier;
++
++import java.util.Set;
++import java.util.UUID;
++import net.kyori.adventure.text.Component;
++import net.md_5.bungee.api.chat.BaseComponent;
++import org.bukkit.Bukkit;
++import org.bukkit.Server;
++import org.bukkit.command.CommandSender;
++import org.bukkit.permissions.PermissibleBase;
++import org.bukkit.permissions.Permission;
++import org.bukkit.permissions.PermissionAttachment;
++import org.bukkit.permissions.PermissionAttachmentInfo;
++import org.bukkit.plugin.Plugin;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++@DefaultQualifier(NonNull.class)
++public final class NullCommandSender implements CommandSender {
++
++ public static final CommandSender INSTANCE = new NullCommandSender();
++
++ private NullCommandSender() {
++ }
++
++ @Override
++ public void sendMessage(final String message) {
++ }
++
++ @Override
++ public void sendMessage(final String... messages) {
++ }
++
++ @Override
++ public void sendMessage(@Nullable final UUID sender, final String message) {
++ }
++
++ @Override
++ public void sendMessage(@Nullable final UUID sender, final String... messages) {
++ }
++
++ @SuppressWarnings("ConstantValue")
++ @Override
++ public Server getServer() {
++ final @Nullable Server server = Bukkit.getServer();
++ if (server == null) {
++ throw new UnsupportedOperationException("The server has not been created yet, you cannot access it at this time from the 'null' CommandSender");
++ }
++ return server;
++ }
++
++ @Override
++ public String getName() {
++ return "";
++ }
++
++ private final Spigot spigot = new Spigot();
++ @Override
++ public Spigot spigot() {
++ return this.spigot;
++ }
++
++ public final class Spigot extends CommandSender.Spigot {
++
++ @Override
++ public void sendMessage(@NotNull final BaseComponent component) {
++ }
++
++ @Override
++ public void sendMessage(@NonNull final @NotNull BaseComponent... components) {
++ }
++
++ @Override
++ public void sendMessage(@Nullable final UUID sender, @NotNull final BaseComponent component) {
++ }
++
++ @Override
++ public void sendMessage(@Nullable final UUID sender, @NonNull final @NotNull BaseComponent... components) {
++ }
++ }
++
++ @Override
++ public Component name() {
++ return Component.empty();
++ }
++
++ @Override
++ public boolean isPermissionSet(final String name) {
++ return false;
++ }
++
++ @Override
++ public boolean isPermissionSet(final Permission perm) {
++ return false;
++ }
++
++ @Override
++ public boolean hasPermission(final String name) {
++ return true;
++ }
++
++ @Override
++ public boolean hasPermission(final Permission perm) {
++ return true;
++ }
++
++ @Override
++ public PermissionAttachment addAttachment(final Plugin plugin, final String name, final boolean value) {
++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
++ }
++
++ @Override
++ public PermissionAttachment addAttachment(final Plugin plugin) {
++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
++ }
++
++ @Override
++ public @Nullable PermissionAttachment addAttachment(final Plugin plugin, final String name, final boolean value, final int ticks) {
++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
++ }
++
++ @Override
++ public @Nullable PermissionAttachment addAttachment(final Plugin plugin, final int ticks) {
++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
++ }
++
++ @Override
++ public void removeAttachment(final PermissionAttachment attachment) {
++ throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
++ }
++
++ @Override
++ public void recalculatePermissions() {
++ }
++
++ @Override
++ public Set<PermissionAttachmentInfo> getEffectivePermissions() {
++ throw new UnsupportedOperationException("Cannot remove attachments from the 'null' CommandSender");
++ }
++
++ @Override
++ public boolean isOp() {
++ return true;
++ }
++
++ @Override
++ public void setOp(final boolean value) {
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java
+deleted file mode 100644
+index dd6012b6a097575b2d1471be5069eccee4537c0a..0000000000000000000000000000000000000000
+--- a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java
++++ /dev/null
+@@ -1,30 +0,0 @@
+-package io.papermc.paper.brigadier;
+-
+-import com.mojang.brigadier.Message;
+-import io.papermc.paper.adventure.PaperAdventure;
+-import net.kyori.adventure.text.Component;
+-import net.kyori.adventure.text.ComponentLike;
+-import net.minecraft.network.chat.ComponentUtils;
+-import org.checkerframework.checker.nullness.qual.NonNull;
+-
+-import static java.util.Objects.requireNonNull;
+-
+-public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider {
+- INSTANCE;
+-
+- PaperBrigadierProviderImpl() {
+- PaperBrigadierProvider.initialize(this);
+- }
+-
+- @Override
+- public @NonNull Message message(final @NonNull ComponentLike componentLike) {
+- requireNonNull(componentLike, "componentLike");
+- return PaperAdventure.asVanilla(componentLike.asComponent());
+- }
+-
+- @Override
+- public @NonNull Component componentFromMessage(final @NonNull Message message) {
+- requireNonNull(message, "message");
+- return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message));
+- }
+-}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/ApiMirrorRootNode.java b/src/main/java/io/papermc/paper/command/brigadier/ApiMirrorRootNode.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2b1cf02f32506187ed0783ae4732ae0f0bdbfc0e
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/ApiMirrorRootNode.java
+@@ -0,0 +1,252 @@
++package io.papermc.paper.command.brigadier;
++
++import com.google.common.collect.Collections2;
++import com.mojang.brigadier.CommandDispatcher;
++import com.mojang.brigadier.arguments.ArgumentType;
++import com.mojang.brigadier.arguments.BoolArgumentType;
++import com.mojang.brigadier.arguments.DoubleArgumentType;
++import com.mojang.brigadier.arguments.FloatArgumentType;
++import com.mojang.brigadier.arguments.IntegerArgumentType;
++import com.mojang.brigadier.arguments.LongArgumentType;
++import com.mojang.brigadier.arguments.StringArgumentType;
++import com.mojang.brigadier.context.CommandContext;
++import com.mojang.brigadier.suggestion.SuggestionProvider;
++import com.mojang.brigadier.suggestion.SuggestionsBuilder;
++import com.mojang.brigadier.tree.ArgumentCommandNode;
++import com.mojang.brigadier.tree.CommandNode;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++import com.mojang.brigadier.tree.RootCommandNode;
++import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
++import io.papermc.paper.command.brigadier.argument.VanillaArgumentProviderImpl;
++import io.papermc.paper.command.brigadier.argument.WrappedArgumentCommandNode;
++import java.lang.reflect.Method;
++import java.util.Collection;
++import java.util.Set;
++import net.minecraft.commands.synchronization.ArgumentTypeInfos;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++/**
++ * This root command node is responsible for wrapping around vanilla's dispatcher.
++ * <p>
++ * The reason for this is conversion is we do NOT want there to be NMS types
++ * in the api. This allows us to reconstruct the nodes to be more api friendly, while
++ * we can then unwrap it when needed and convert them to NMS types.
++ * <p>
++ * Command nodes such as vanilla (those without a proper "api node")
++ * will be assigned a {@link ShadowBrigNode}.
++ * This prevents certain parts of it (children) from being accessed by the api.
++ */
++public abstract class ApiMirrorRootNode extends RootCommandNode<CommandSourceStack> {
++
++ /**
++ * Represents argument types that are allowed to exist in the api.
++ * These typically represent primitives that don't need to be wrapped
++ * by NMS.
++ */
++ private static final Set<Class<? extends ArgumentType<?>>> ARGUMENT_WHITELIST = Set.of(
++ BoolArgumentType.class,
++ DoubleArgumentType.class,
++ FloatArgumentType.class,
++ IntegerArgumentType.class,
++ LongArgumentType.class,
++ StringArgumentType.class
++ );
++
++ public static void validatePrimitiveType(ArgumentType<?> type) {
++ if (ARGUMENT_WHITELIST.contains(type.getClass())) {
++ if (!ArgumentTypeInfos.isClassRecognized(type.getClass())) {
++ throw new IllegalArgumentException("This whitelisted primitive argument type is not recognized by the server!");
++ }
++ } else if (!(type instanceof VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType) || !ArgumentTypeInfos.isClassRecognized(nativeWrapperArgumentType.nativeNmsArgumentType().getClass())) {
++ throw new IllegalArgumentException("Custom argument type was passed, this was not a recognized type to send to the client! You must only pass vanilla arguments or primitive brig args in the wrapper!");
++ }
++ }
++
++ public abstract CommandDispatcher<net.minecraft.commands.CommandSourceStack> getDispatcher();
++
++ /**
++ * This logic is responsible for unwrapping an API node to be supported by NMS.
++ * See the method implementation for detailed steps.
++ *
++ * @param maybeWrappedNode api provided node / node to be "wrapped"
++ * @return wrapped node
++ */
++ @SuppressWarnings({"rawtypes", "unchecked"})
++ private @NotNull CommandNode<CommandSourceStack> unwrapNode(final CommandNode<CommandSourceStack> maybeWrappedNode) {
++ /*
++ If the type is a shadow node we can assume that the type that it represents is an already supported NMS node.
++ This is because these are typically minecraft command nodes.
++ */
++ if (maybeWrappedNode instanceof final ShadowBrigNode shadowBrigNode) {
++ return (CommandNode) shadowBrigNode.getHandle();
++ }
++
++ /*
++ This node already has had an unwrapped node created, so we can assume that it's safe to reuse that cached copy.
++ */
++ if (maybeWrappedNode.unwrappedCached != null) {
++ return maybeWrappedNode.unwrappedCached;
++ }
++
++ // convert the pure brig node into one compatible with the nms dispatcher
++ return this.convertFromPureBrigNode(maybeWrappedNode);
++ }
++
++ private @NotNull CommandNode<CommandSourceStack> convertFromPureBrigNode(final CommandNode<CommandSourceStack> pureNode) {
++ /*
++ Logic for converting a node.
++ */
++ final CommandNode<CommandSourceStack> converted;
++ if (pureNode instanceof final LiteralCommandNode<CommandSourceStack> node) {
++ /*
++ Remap the literal node, we only have to account
++ for the redirect in this case.
++ */
++ converted = this.simpleUnwrap(node);
++ } else if (pureNode instanceof final ArgumentCommandNode<CommandSourceStack, ?> pureArgumentNode) {
++ final ArgumentType<?> pureArgumentType = pureArgumentNode.getType();
++ /*
++ Check to see if this argument type is a wrapped type, if so we know that
++ we can unwrap the node to get an NMS type.
++ */
++ if (pureArgumentType instanceof final CustomArgumentType<?, ?> customArgumentType) {
++ final SuggestionProvider<?> suggestionProvider;
++ try {
++ final Method listSuggestions = customArgumentType.getClass().getMethod("listSuggestions", CommandContext.class, SuggestionsBuilder.class);
++ if (listSuggestions.getDeclaringClass() != CustomArgumentType.class) {
++ suggestionProvider = customArgumentType::listSuggestions;
++ } else {
++ suggestionProvider = null;
++ }
++ } catch (final NoSuchMethodException ex) {
++ throw new IllegalStateException("Could not determine if the custom argument type " + customArgumentType + " overrides listSuggestions", ex);
++ }
++
++ converted = this.unwrapArgumentWrapper(pureArgumentNode, customArgumentType, customArgumentType.getNativeType(), suggestionProvider);
++ } else if (pureArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType) {
++ converted = this.unwrapArgumentWrapper(pureArgumentNode, nativeWrapperArgumentType, nativeWrapperArgumentType, null); // "null" for suggestion provider so it uses the argument type's suggestion provider
++
++ /*
++ If it's not a wrapped type, it either has to be a primitive or an already
++ defined NMS type.
++ This method allows us to check if this is recognized by vanilla.
++ */
++ } else if (ArgumentTypeInfos.isClassRecognized(pureArgumentType.getClass())) {
++ // Allow any type of argument, as long as it's recognized by the client (but in most cases, this should be API only types)
++ // Previously we only allowed whitelisted types.
++ converted = this.simpleUnwrap(pureArgumentNode);
++ } else {
++ // Unknown argument type was passed
++ throw new IllegalArgumentException("Custom unknown argument type was passed, should be wrapped inside an CustomArgumentType.");
++ }
++ } else {
++ throw new IllegalArgumentException("Unknown command node passed. Don't know how to unwrap this.");
++ }
++
++ /*
++ Add the children to the node, unwrapping each child in the process.
++ */
++ for (final CommandNode<CommandSourceStack> child : pureNode.getChildren()) {
++ converted.addChild(this.unwrapNode(child));
++ }
++
++ converted.wrappedCached = pureNode;
++ pureNode.unwrappedCached = converted;
++
++ return converted;
++ }
++
++ /**
++ * This logic is responsible for rewrapping a node.
++ * If a node was unwrapped in the past, it should have a wrapped type
++ * stored in its cache.
++ * <p>
++ * However, if it doesn't seem to have a wrapped version we will return
++ * a {@link ShadowBrigNode} instead. This supports being unwrapped/wrapped while
++ * preventing the API from accessing it unsafely.
++ *
++ * @param unwrapped argument node
++ * @return wrapped node
++ */
++ private @Nullable CommandNode<CommandSourceStack> wrapNode(@Nullable final CommandNode<net.minecraft.commands.CommandSourceStack> unwrapped) {
++ if (unwrapped == null) {
++ return null;
++ }
++
++ /*
++ This was most likely created by API and has a wrapped variant,
++ so we can return this safely.
++ */
++ if (unwrapped.wrappedCached != null) {
++ return unwrapped.wrappedCached;
++ }
++
++ /*
++ We don't know the type of this, or where this came from.
++ Return a shadow, where we will allow the api to handle this but have
++ restrictive access.
++ */
++ CommandNode<CommandSourceStack> shadow = new ShadowBrigNode(unwrapped);
++ unwrapped.wrappedCached = shadow;
++ return shadow;
++ }
++
++ /**
++ * Nodes added to this dispatcher must be unwrapped
++ * in order to be added to the NMS dispatcher.
++ *
++ * @param node node to add
++ */
++ @SuppressWarnings({"rawtypes", "unchecked"})
++ @Override
++ public void addChild(CommandNode<CommandSourceStack> node) {
++ CommandNode convertedNode = this.unwrapNode(node);
++ this.getDispatcher().getRoot().addChild(convertedNode);
++ }
++
++ /**
++ * Gets the children for the vanilla dispatcher,
++ * ensuring that all are wrapped.
++ *
++ * @return wrapped children
++ */
++ @Override
++ public Collection<CommandNode<CommandSourceStack>> getChildren() {
++ return Collections2.transform(this.getDispatcher().getRoot().getChildren(), this::wrapNode);
++ }
++
++ @Override
++ public CommandNode<CommandSourceStack> getChild(String name) {
++ return this.wrapNode(this.getDispatcher().getRoot().getChild(name));
++ }
++
++ // These are needed for bukkit... we should NOT allow this
++ @Override
++ public void removeCommand(String name) {
++ this.getDispatcher().getRoot().removeCommand(name);
++ }
++
++ @Override
++ public void clearAll() {
++ this.getDispatcher().getRoot().clearAll();
++ }
++
++ @SuppressWarnings({"rawtypes", "unchecked"})
++ private CommandNode<CommandSourceStack> unwrapArgumentWrapper(final ArgumentCommandNode pureNode, final ArgumentType pureArgumentType, final ArgumentType possiblyWrappedNativeArgumentType, @Nullable SuggestionProvider argumentTypeSuggestionProvider) {
++ validatePrimitiveType(possiblyWrappedNativeArgumentType);
++ final CommandNode redirectNode = pureNode.getRedirect() == null ? null : this.unwrapNode(pureNode.getRedirect());
++ // If there is already a custom suggestion provider, ignore the suggestion provider from the argument type
++ final SuggestionProvider suggestionProvider = pureNode.getCustomSuggestions() != null ? pureNode.getCustomSuggestions() : argumentTypeSuggestionProvider;
++
++ final ArgumentType nativeArgumentType = possiblyWrappedNativeArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType ? nativeWrapperArgumentType.nativeNmsArgumentType() : possiblyWrappedNativeArgumentType;
++ return new WrappedArgumentCommandNode<>(pureNode.getName(), pureArgumentType, nativeArgumentType, pureNode.getCommand(), pureNode.getRequirement(), redirectNode, pureNode.getRedirectModifier(), pureNode.isFork(), suggestionProvider);
++ }
++
++ private CommandNode<CommandSourceStack> simpleUnwrap(final CommandNode<CommandSourceStack> node) {
++ return node.createBuilder()
++ .redirect(node.getRedirect() == null ? null : this.unwrapNode(node.getRedirect()))
++ .build();
++ }
++
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerImpl.java b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0b33c6cf2366568641e6f2fd7f74fb74f6ea0145
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerImpl.java
+@@ -0,0 +1,20 @@
++package io.papermc.paper.command.brigadier;
++
++import com.mojang.brigadier.Message;
++import io.papermc.paper.adventure.PaperAdventure;
++import net.kyori.adventure.text.Component;
++import net.minecraft.network.chat.ComponentUtils;
++import org.jetbrains.annotations.NotNull;
++
++public final class MessageComponentSerializerImpl implements MessageComponentSerializer {
++
++ @Override
++ public @NotNull Component deserialize(@NotNull Message input) {
++ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(input));
++ }
++
++ @Override
++ public @NotNull Message serialize(@NotNull Component component) {
++ return PaperAdventure.asVanilla(component);
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/PaperBrigadier.java b/src/main/java/io/papermc/paper/command/brigadier/PaperBrigadier.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..4acf7c3bcfbe61431bfbfa3c8addb33f671eb498
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/PaperBrigadier.java
+@@ -0,0 +1,73 @@
++package io.papermc.paper.command.brigadier;
++
++import com.mojang.brigadier.CommandDispatcher;
++import com.mojang.brigadier.tree.CommandNode;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++import io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap;
++import io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode;
++import net.minecraft.commands.CommandSource;
++import net.minecraft.commands.Commands;
++import net.minecraft.network.chat.CommonComponents;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.world.phys.Vec2;
++import net.minecraft.world.phys.Vec3;
++import org.bukkit.Bukkit;
++import org.bukkit.Server;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandMap;
++import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
++
++import java.util.Map;
++
++public final class PaperBrigadier {
++
++ @SuppressWarnings("DataFlowIssue")
++ static final net.minecraft.commands.CommandSourceStack DUMMY = new net.minecraft.commands.CommandSourceStack(
++ CommandSource.NULL,
++ Vec3.ZERO,
++ Vec2.ZERO,
++ null,
++ 4,
++ "",
++ CommonComponents.EMPTY,
++ null,
++ null
++ );
++
++ @SuppressWarnings({"unchecked", "rawtypes"})
++ public static Command wrapNode(CommandNode node) {
++ if (!(node instanceof LiteralCommandNode)) {
++ throw new IllegalArgumentException("Unsure how to wrap a " + node);
++ }
++
++ if (!(node instanceof PluginCommandNode pluginCommandNode)) {
++ return new VanillaCommandWrapper(null, node);
++ }
++ CommandNode<CommandSourceStack> argumentCommandNode = node;
++ if (argumentCommandNode.getRedirect() != null) {
++ argumentCommandNode = argumentCommandNode.getRedirect();
++ }
++
++ Map<CommandNode<CommandSourceStack>, String> map = PaperCommands.INSTANCE.getDispatcherInternal().getSmartUsage(argumentCommandNode, DUMMY);
++ String usage = map.isEmpty() ? pluginCommandNode.getUsageText() : pluginCommandNode.getUsageText() + " " + String.join("\n" + pluginCommandNode.getUsageText() + " ", map.values());
++ return new PluginVanillaCommandWrapper(pluginCommandNode.getName(), pluginCommandNode.getDescription(), usage, pluginCommandNode.getAliases(), node, pluginCommandNode.getPlugin());
++ }
++
++ /*
++ Previously, Bukkit used one command dispatcher and ignored minecraft's reloading logic.
++
++ In order to allow for legacy commands to be properly added, we will iterate through previous bukkit commands
++ in the old dispatcher and re-register them.
++ */
++ @SuppressWarnings({"unchecked", "rawtypes"})
++ public static void moveBukkitCommands(Commands before, Commands after) {
++ CommandDispatcher erasedDispatcher = before.getDispatcher();
++
++ for (Object node : erasedDispatcher.getRoot().getChildren()) {
++ if (node instanceof CommandNode<?> commandNode && commandNode.getCommand() instanceof BukkitCommandNode.BukkitBrigCommand) {
++ after.getDispatcher().getRoot().removeCommand(((CommandNode<?>) node).getName()); // Remove already existing commands
++ after.getDispatcher().getRoot().addChild((CommandNode<net.minecraft.commands.CommandSourceStack>) node);
++ }
++ }
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java b/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1b1642f306771f029e6214a2e2ebebb6ae6abc3e
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java
+@@ -0,0 +1,63 @@
++package io.papermc.paper.command.brigadier;
++
++import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource;
++import net.minecraft.world.level.Level;
++import net.minecraft.world.phys.Vec2;
++import net.minecraft.world.phys.Vec3;
++import org.bukkit.Location;
++import org.bukkit.command.CommandSender;
++import org.bukkit.entity.Entity;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBrigadierCommandSource {
++
++ net.minecraft.commands.CommandSourceStack getHandle();
++
++ @Override
++ default @NotNull Location getLocation() {
++ Vec2 rot = this.getHandle().getRotation();
++ Vec3 pos = this.getHandle().getPosition();
++ Level level = this.getHandle().getLevel();
++
++ return new Location(level.getWorld(), pos.x, pos.y, pos.z, rot.y, rot.x);
++ }
++
++ @Override
++ @NotNull
++ default CommandSender getSender() {
++ return this.getHandle().getBukkitSender();
++ }
++
++ @Override
++ @Nullable
++ default Entity getExecutor() {
++ net.minecraft.world.entity.Entity nmsEntity = this.getHandle().getEntity();
++ if (nmsEntity == null) {
++ return null;
++ }
++
++ return nmsEntity.getBukkitEntity();
++ }
++
++ // OLD METHODS
++ @Override
++ default org.bukkit.entity.Entity getBukkitEntity() {
++ return this.getExecutor();
++ }
++
++ @Override
++ default org.bukkit.World getBukkitWorld() {
++ return this.getLocation().getWorld();
++ }
++
++ @Override
++ default org.bukkit.Location getBukkitLocation() {
++ return this.getLocation();
++ }
++
++ @Override
++ default CommandSender getBukkitSender() {
++ return this.getSender();
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java b/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..27509813a90980be1dfc7bde27d0eba60adfc820
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java
+@@ -0,0 +1,193 @@
++package io.papermc.paper.command.brigadier;
++
++import com.google.common.base.Preconditions;
++import com.mojang.brigadier.CommandDispatcher;
++import com.mojang.brigadier.arguments.StringArgumentType;
++import com.mojang.brigadier.builder.LiteralArgumentBuilder;
++import com.mojang.brigadier.suggestion.SuggestionsBuilder;
++import com.mojang.brigadier.tree.CommandNode;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++import io.papermc.paper.plugin.configuration.PluginMeta;
++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
++import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar;
++import java.util.ArrayList;
++import java.util.Collection;
++import java.util.Collections;
++import java.util.HashSet;
++import java.util.List;
++import java.util.Locale;
++import java.util.Set;
++import net.minecraft.commands.CommandBuildContext;
++import org.apache.commons.lang3.StringUtils;
++import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Unmodifiable;
++
++import static java.util.Objects.requireNonNull;
++
++@DefaultQualifier(NonNull.class)
++public class PaperCommands implements Commands, PaperRegistrar<LifecycleEventOwner> {
++
++ public static final PaperCommands INSTANCE = new PaperCommands();
++
++ private @Nullable LifecycleEventOwner currentContext;
++ private @MonotonicNonNull CommandDispatcher<CommandSourceStack> dispatcher;
++ private @MonotonicNonNull CommandBuildContext buildContext;
++ private boolean invalid = false;
++
++ @Override
++ public void setCurrentContext(final @Nullable LifecycleEventOwner context) {
++ this.currentContext = context;
++ }
++
++ public void setDispatcher(final net.minecraft.commands.Commands commands, final CommandBuildContext commandBuildContext) {
++ this.invalid = false;
++ this.dispatcher = new CommandDispatcher<>(new ApiMirrorRootNode() {
++ @Override
++ public CommandDispatcher<net.minecraft.commands.CommandSourceStack> getDispatcher() {
++ return commands.getDispatcher();
++ }
++ });
++ this.buildContext = commandBuildContext;
++ }
++
++ public void setValid() {
++ this.invalid = false;
++ }
++
++ @Override
++ public void invalidate() {
++ this.invalid = true;
++ }
++
++ // use this method internally as it bypasses the valid check
++ public CommandDispatcher<CommandSourceStack> getDispatcherInternal() {
++ Preconditions.checkState(this.dispatcher != null, "the dispatcher hasn't been set yet");
++ return this.dispatcher;
++ }
++
++ public CommandBuildContext getBuildContext() {
++ Preconditions.checkState(this.buildContext != null, "the build context hasn't been set yet");
++ return this.buildContext;
++ }
++
++ @Override
++ public CommandDispatcher<CommandSourceStack> getDispatcher() {
++ Preconditions.checkState(!this.invalid && this.dispatcher != null, "cannot access the dispatcher in this context");
++ return this.dispatcher;
++ }
++
++ @Override
++ public @Unmodifiable Set<String> register(final LiteralCommandNode<CommandSourceStack> node, final @Nullable String description, final Collection<String> aliases) {
++ return this.register(requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(), node, description, aliases);
++ }
++
++ @Override
++ public @Unmodifiable Set<String> register(final PluginMeta pluginMeta, final LiteralCommandNode<CommandSourceStack> node, final @Nullable String description, final Collection<String> aliases) {
++ return this.registerWithFlags(pluginMeta, node, description, aliases, Set.of());
++ }
++
++ @Override
++ public @Unmodifiable Set<String> registerWithFlags(@NotNull final PluginMeta pluginMeta, @NotNull final LiteralCommandNode<CommandSourceStack> node, @org.jetbrains.annotations.Nullable final String description, @NotNull final Collection<String> aliases, @NotNull final Set<CommandRegistrationFlag> flags) {
++ final boolean hasFlattenRedirectFlag = flags.contains(CommandRegistrationFlag.FLATTEN_ALIASES);
++ final String identifier = pluginMeta.getName().toLowerCase(Locale.ROOT);
++ final String literal = node.getLiteral();
++ final PluginCommandNode pluginLiteral = new PluginCommandNode(identifier + ":" + literal, pluginMeta, node, description); // Treat the keyed version of the command as the root
++
++ final Set<String> registeredLabels = new HashSet<>(aliases.size() * 2 + 2);
++
++ if (this.registerIntoDispatcher(pluginLiteral, true)) {
++ registeredLabels.add(pluginLiteral.getLiteral());
++ }
++ if (this.registerRedirect(literal, pluginMeta, pluginLiteral, description, true, hasFlattenRedirectFlag)) { // Plugin commands should override vanilla commands
++ registeredLabels.add(literal);
++ }
++
++ // Add aliases
++ final List<String> registeredAliases = new ArrayList<>(aliases.size() * 2);
++ for (final String alias : aliases) {
++ if (this.registerRedirect(alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) {
++ registeredAliases.add(alias);
++ }
++ if (this.registerRedirect(identifier + ":" + alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) {
++ registeredAliases.add(identifier + ":" + alias);
++ }
++ }
++
++ if (!registeredAliases.isEmpty()) {
++ pluginLiteral.setAliases(registeredAliases);
++ }
++
++ registeredLabels.addAll(registeredAliases);
++ return registeredLabels.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(registeredLabels);
++ }
++
++ private boolean registerRedirect(final String aliasLiteral, final PluginMeta plugin, final PluginCommandNode redirectTo, final @Nullable String description, final boolean override, boolean hasFlattenRedirectFlag) {
++ final LiteralCommandNode<CommandSourceStack> redirect;
++ if (redirectTo.getChildren().isEmpty() || hasFlattenRedirectFlag) {
++ redirect = Commands.literal(aliasLiteral)
++ .executes(redirectTo.getCommand())
++ .requires(redirectTo.getRequirement())
++ .build();
++
++ for (final CommandNode<CommandSourceStack> child : redirectTo.getChildren()) {
++ redirect.addChild(child);
++ }
++ } else {
++ redirect = Commands.literal(aliasLiteral)
++ .executes(redirectTo.getCommand())
++ .redirect(redirectTo)
++ .requires(redirectTo.getRequirement())
++ .build();
++ }
++
++ return this.registerIntoDispatcher(new PluginCommandNode(aliasLiteral, plugin, redirect, description), override);
++ }
++
++ private boolean registerIntoDispatcher(final PluginCommandNode node, final boolean override) {
++ final boolean hasChild = this.getDispatcher().getRoot().getChild(node.getLiteral()) != null;
++ if (!hasChild || override) { // Avoid merging behavior. Maybe something to look into in the future
++ if (override) {
++ this.getDispatcher().getRoot().removeCommand(node.getLiteral());
++ }
++ this.getDispatcher().getRoot().addChild(node);
++ return true;
++ }
++
++ return false;
++ }
++
++ @Override
++ public @Unmodifiable Set<String> register(final String label, final @Nullable String description, final Collection<String> aliases, final BasicCommand basicCommand) {
++ return this.register(requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(), label, description, aliases, basicCommand);
++ }
++
++ @Override
++ public @Unmodifiable Set<String> register(final PluginMeta pluginMeta, final String label, final @Nullable String description, final Collection<String> aliases, final BasicCommand basicCommand) {
++ final LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal(label)
++ .then(
++ Commands.argument("args", StringArgumentType.greedyString())
++ .suggests((context, suggestionsBuilder) -> {
++ final String[] args = StringUtils.split(suggestionsBuilder.getRemaining());
++ final SuggestionsBuilder offsetSuggestionsBuilder = suggestionsBuilder.createOffset(suggestionsBuilder.getInput().lastIndexOf(' ') + 1);;
++
++ final Collection<String> suggestions = basicCommand.suggest(context.getSource(), args);
++ suggestions.forEach(offsetSuggestionsBuilder::suggest);
++ return offsetSuggestionsBuilder.buildFuture();
++ })
++ .executes((stack) -> {
++ basicCommand.execute(stack.getSource(), StringUtils.split(stack.getArgument("args", String.class), ' '));
++ return com.mojang.brigadier.Command.SINGLE_SUCCESS;
++ })
++ )
++ .executes((stack) -> {
++ basicCommand.execute(stack.getSource(), new String[0]);
++ return com.mojang.brigadier.Command.SINGLE_SUCCESS;
++ });
++
++ return this.register(pluginMeta, builder.build(), description, aliases);
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/PluginCommandNode.java b/src/main/java/io/papermc/paper/command/brigadier/PluginCommandNode.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..3a9f58873b83f10ba354ae4968c4ab0632662439
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/PluginCommandNode.java
+@@ -0,0 +1,50 @@
++package io.papermc.paper.command.brigadier;
++
++import com.mojang.brigadier.tree.CommandNode;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++import java.util.Collections;
++import java.util.List;
++import java.util.Objects;
++import io.papermc.paper.plugin.configuration.PluginMeta;
++import org.bukkit.Bukkit;
++import org.bukkit.plugin.Plugin;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++public class PluginCommandNode extends LiteralCommandNode<CommandSourceStack> {
++
++ private final PluginMeta plugin;
++ private final String description;
++ private List<String> aliases = Collections.emptyList();
++
++ public PluginCommandNode(final @NotNull String literal, final @NotNull PluginMeta plugin, final @NotNull LiteralCommandNode<CommandSourceStack> rootLiteral, final @Nullable String description) {
++ super(
++ literal, rootLiteral.getCommand(), rootLiteral.getRequirement(),
++ rootLiteral.getRedirect(), rootLiteral.getRedirectModifier(), rootLiteral.isFork()
++ );
++ this.plugin = plugin;
++ this.description = description;
++
++ for (CommandNode<CommandSourceStack> argument : rootLiteral.getChildren()) {
++ this.addChild(argument);
++ }
++ }
++
++ @NotNull
++ public Plugin getPlugin() {
++ return Objects.requireNonNull(Bukkit.getPluginManager().getPlugin(this.plugin.getName()));
++ }
++
++ @NotNull
++ public String getDescription() {
++ return this.description;
++ }
++
++ public void setAliases(List<String> aliases) {
++ this.aliases = aliases;
++ }
++
++ public List<String> getAliases() {
++ return this.aliases;
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/PluginVanillaCommandWrapper.java b/src/main/java/io/papermc/paper/command/brigadier/PluginVanillaCommandWrapper.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..cf8359af60601d7917e77fd06a00b64992a85953
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/PluginVanillaCommandWrapper.java
+@@ -0,0 +1,46 @@
++package io.papermc.paper.command.brigadier;
++
++import com.mojang.brigadier.tree.CommandNode;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.commands.Commands;
++import org.bukkit.command.Command;
++import org.bukkit.command.PluginIdentifiableCommand;
++import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
++import org.bukkit.plugin.Plugin;
++import org.jetbrains.annotations.NotNull;
++
++import java.util.List;
++
++// Exists to that /help can show the plugin
++public class PluginVanillaCommandWrapper extends VanillaCommandWrapper implements PluginIdentifiableCommand {
++
++ private final Plugin plugin;
++ private final List<String> alises;
++
++ public PluginVanillaCommandWrapper(String name, String description, String usageMessage, List<String> aliases, CommandNode<CommandSourceStack> vanillaCommand, Plugin plugin) {
++ super(name, description, usageMessage, aliases, vanillaCommand);
++ this.plugin = plugin;
++ this.alises = aliases;
++ }
++
++ @Override
++ public @NotNull List<String> getAliases() {
++ return this.alises;
++ }
++
++ @Override
++ public @NotNull Command setAliases(@NotNull List<String> aliases) {
++ return this;
++ }
++
++ @Override
++ public @NotNull Plugin getPlugin() {
++ return this.plugin;
++ }
++
++ // Show in help menu!
++ @Override
++ public boolean isRegistered() {
++ return true;
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/ShadowBrigNode.java b/src/main/java/io/papermc/paper/command/brigadier/ShadowBrigNode.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..895addef908e09d527e4bc9210599e8827c53807
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/ShadowBrigNode.java
+@@ -0,0 +1,35 @@
++package io.papermc.paper.command.brigadier;
++
++import com.mojang.brigadier.tree.CommandNode;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++
++import java.util.Collection;
++
++public class ShadowBrigNode extends LiteralCommandNode<CommandSourceStack> {
++
++ private final CommandNode<net.minecraft.commands.CommandSourceStack> handle;
++
++ public ShadowBrigNode(CommandNode<net.minecraft.commands.CommandSourceStack> node) {
++ super(node.getName(), context -> 0, (s) -> false, node.getRedirect() == null ? null : new ShadowBrigNode(node.getRedirect()), null, node.isFork());
++ this.handle = node;
++ }
++
++ @Override
++ public Collection<CommandNode<CommandSourceStack>> getChildren() {
++ throw new UnsupportedOperationException("Cannot retrieve children from this node.");
++ }
++
++ @Override
++ public CommandNode<CommandSourceStack> getChild(String name) {
++ throw new UnsupportedOperationException("Cannot retrieve children from this node.");
++ }
++
++ @Override
++ public void addChild(CommandNode<CommandSourceStack> node) {
++ throw new UnsupportedOperationException("Cannot modify children for this node.");
++ }
++
++ public CommandNode<net.minecraft.commands.CommandSourceStack> getHandle() {
++ return this.handle;
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolverImpl.java b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolverImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..72966584089d3fee9778f572727c9b7f4a4d4302
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolverImpl.java
+@@ -0,0 +1,30 @@
++package io.papermc.paper.command.brigadier.argument;
++
++import com.mojang.brigadier.context.CommandContext;
++import com.mojang.brigadier.exceptions.CommandSyntaxException;
++import net.kyori.adventure.chat.SignedMessage;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.commands.arguments.MessageArgument;
++import org.jetbrains.annotations.NotNull;
++
++import java.util.concurrent.CompletableFuture;
++
++public record SignedMessageResolverImpl(MessageArgument.Message message) implements SignedMessageResolver {
++
++ @Override
++ public String content() {
++ return this.message.text();
++ }
++
++ @Override
++ public @NotNull CompletableFuture<SignedMessage> resolveSignedMessage(final String argumentName, final CommandContext erased) throws CommandSyntaxException {
++ final CommandContext<CommandSourceStack> type = erased;
++ final CompletableFuture<SignedMessage> future = new CompletableFuture<>();
++
++ final MessageArgument.Message response = type.getArgument(argumentName, SignedMessageResolverImpl.class).message;
++ MessageArgument.resolveChatMessage(response, type, argumentName, (message) -> {
++ future.complete(message.adventureView());
++ });
++ return future;
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..5f0b46cb2b3f53a81a949fa64690133baa0a304b
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java
+@@ -0,0 +1,321 @@
++package io.papermc.paper.command.brigadier.argument;
++
++import com.destroystokyo.paper.profile.CraftPlayerProfile;
++import com.google.common.collect.Collections2;
++import com.google.common.collect.Lists;
++import com.google.common.collect.Range;
++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 io.papermc.paper.adventure.PaperAdventure;
++import io.papermc.paper.command.brigadier.PaperCommands;
++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.range.RangeProvider;
++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 io.papermc.paper.math.Position;
++import java.util.Collection;
++import java.util.Collections;
++import java.util.List;
++import java.util.UUID;
++import java.util.concurrent.CompletableFuture;
++import java.util.function.Function;
++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 net.minecraft.advancements.critereon.MinMaxBounds;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.commands.arguments.ColorArgument;
++import net.minecraft.commands.arguments.ComponentArgument;
++import net.minecraft.commands.arguments.DimensionArgument;
++import net.minecraft.commands.arguments.EntityAnchorArgument;
++import net.minecraft.commands.arguments.EntityArgument;
++import net.minecraft.commands.arguments.GameModeArgument;
++import net.minecraft.commands.arguments.GameProfileArgument;
++import net.minecraft.commands.arguments.HeightmapTypeArgument;
++import net.minecraft.commands.arguments.MessageArgument;
++import net.minecraft.commands.arguments.ObjectiveCriteriaArgument;
++import net.minecraft.commands.arguments.RangeArgument;
++import net.minecraft.commands.arguments.ResourceLocationArgument;
++import net.minecraft.commands.arguments.ScoreboardSlotArgument;
++import net.minecraft.commands.arguments.StyleArgument;
++import net.minecraft.commands.arguments.TemplateMirrorArgument;
++import net.minecraft.commands.arguments.TemplateRotationArgument;
++import net.minecraft.commands.arguments.TimeArgument;
++import net.minecraft.commands.arguments.UuidArgument;
++import net.minecraft.commands.arguments.blocks.BlockStateArgument;
++import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
++import net.minecraft.commands.arguments.item.ItemArgument;
++import net.minecraft.commands.arguments.item.ItemPredicateArgument;
++import net.minecraft.core.BlockPos;
++import net.minecraft.core.registries.Registries;
++import net.minecraft.resources.ResourceKey;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.level.Level;
++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.craftbukkit.CraftHeightMap;
++import org.bukkit.craftbukkit.CraftRegistry;
++import org.bukkit.craftbukkit.block.CraftBlockStates;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.scoreboard.CraftCriteria;
++import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.inventory.ItemStack;
++import org.bukkit.scoreboard.Criteria;
++import org.bukkit.scoreboard.DisplaySlot;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++import static java.util.Objects.requireNonNull;
++
++@DefaultQualifier(NonNull.class)
++public class VanillaArgumentProviderImpl implements VanillaArgumentProvider {
++
++ @Override
++ public ArgumentType<EntitySelectorArgumentResolver> entity() {
++ return this.wrap(EntityArgument.entity(), (result) -> sourceStack -> {
++ return List.of(result.findSingleEntity((CommandSourceStack) sourceStack).getBukkitEntity());
++ });
++ }
++
++ @Override
++ public ArgumentType<EntitySelectorArgumentResolver> entities() {
++ return this.wrap(EntityArgument.entities(), (result) -> sourceStack -> {
++ return Lists.transform(result.findEntities((CommandSourceStack) sourceStack), net.minecraft.world.entity.Entity::getBukkitEntity);
++ });
++ }
++
++ @Override
++ public ArgumentType<PlayerSelectorArgumentResolver> player() {
++ return this.wrap(EntityArgument.player(), (result) -> sourceStack -> {
++ return List.of(result.findSinglePlayer((CommandSourceStack) sourceStack).getBukkitEntity());
++ });
++ }
++
++ @Override
++ public ArgumentType<PlayerSelectorArgumentResolver> players() {
++ return this.wrap(EntityArgument.players(), (result) -> sourceStack -> {
++ return Lists.transform(result.findPlayers((CommandSourceStack) sourceStack), ServerPlayer::getBukkitEntity);
++ });
++ }
++
++ @Override
++ public ArgumentType<PlayerProfileListResolver> playerProfiles() {
++ return this.wrap(GameProfileArgument.gameProfile(), result -> {
++ if (result instanceof GameProfileArgument.SelectorResult) {
++ return sourceStack -> Collections.unmodifiableCollection(Collections2.transform(result.getNames((CommandSourceStack) sourceStack), CraftPlayerProfile::new));
++ } else {
++ return sourceStack -> Collections.unmodifiableCollection(Collections2.transform(result.getNames((CommandSourceStack) sourceStack), CraftPlayerProfile::new));
++ }
++ });
++ }
++
++ @Override
++ public ArgumentType<BlockPositionResolver> blockPosition() {
++ return this.wrap(BlockPosArgument.blockPos(), (result) -> sourceStack -> {
++ final BlockPos pos = result.getBlockPos((CommandSourceStack) sourceStack);
++
++ return Position.block(pos.getX(), pos.getY(), pos.getZ());
++ });
++ }
++
++ @Override
++ public ArgumentType<BlockState> blockState() {
++ return this.wrap(BlockStateArgument.block(PaperCommands.INSTANCE.getBuildContext()), (result) -> {
++ return CraftBlockStates.getBlockState(CraftRegistry.getMinecraftRegistry(), BlockPos.ZERO, result.getState(), result.tag);
++ });
++ }
++
++ @Override
++ public ArgumentType<ItemStack> itemStack() {
++ return this.wrap(ItemArgument.item(PaperCommands.INSTANCE.getBuildContext()), (result) -> {
++ return CraftItemStack.asBukkitCopy(result.createItemStack(1, true));
++ });
++ }
++
++ @Override
++ public ArgumentType<ItemStackPredicate> itemStackPredicate() {
++ return this.wrap(ItemPredicateArgument.itemPredicate(PaperCommands.INSTANCE.getBuildContext()), type -> itemStack -> type.test(CraftItemStack.asNMSCopy(itemStack)));
++ }
++
++ @Override
++ public ArgumentType<NamedTextColor> namedColor() {
++ return this.wrap(ColorArgument.color(), result ->
++ requireNonNull(
++ NamedTextColor.namedColor(
++ requireNonNull(result.getColor(), () -> result + " didn't have a color")
++ ),
++ () -> result.getColor() + " didn't map to an adventure named color"
++ )
++ );
++ }
++
++ @Override
++ public ArgumentType<Component> component() {
++ return this.wrap(ComponentArgument.textComponent(PaperCommands.INSTANCE.getBuildContext()), PaperAdventure::asAdventure);
++ }
++
++ @Override
++ public ArgumentType<Style> style() {
++ return this.wrap(StyleArgument.style(PaperCommands.INSTANCE.getBuildContext()), PaperAdventure::asAdventure);
++ }
++
++ @Override
++ public ArgumentType<SignedMessageResolver> signedMessage() {
++ return this.wrap(MessageArgument.message(), SignedMessageResolverImpl::new);
++ }
++
++ @Override
++ public ArgumentType<DisplaySlot> scoreboardDisplaySlot() {
++ return this.wrap(ScoreboardSlotArgument.displaySlot(), CraftScoreboardTranslations::toBukkitSlot);
++ }
++
++ @Override
++ public ArgumentType<NamespacedKey> namespacedKey() {
++ return this.wrap(ResourceLocationArgument.id(), CraftNamespacedKey::fromMinecraft);
++ }
++
++ @Override
++ public ArgumentType<Key> key() {
++ return this.wrap(ResourceLocationArgument.id(), CraftNamespacedKey::fromMinecraft);
++ }
++
++ @Override
++ public ArgumentType<IntegerRangeProvider> integerRange() {
++ return this.wrap(RangeArgument.intRange(), type -> VanillaArgumentProviderImpl.convertToRange(type, integerRange -> () -> integerRange));
++ }
++
++ @Override
++ public ArgumentType<DoubleRangeProvider> doubleRange() {
++ return this.wrap(RangeArgument.floatRange(), type -> VanillaArgumentProviderImpl.convertToRange(type, doubleRange -> () -> doubleRange));
++ }
++
++ private static <C extends Number & Comparable<C>, T extends RangeProvider<C>> T convertToRange(final MinMaxBounds<C> bounds, final Function<Range<C>, T> converter) {
++ if (bounds.isAny()) {
++ return converter.apply(Range.all());
++ } else if (bounds.min().isPresent() && bounds.max().isPresent()) {
++ return converter.apply(Range.closed(bounds.min().get(), bounds.max().get()));
++ } else if (bounds.max().isPresent()) {
++ return converter.apply(Range.atMost(bounds.max().get()));
++ } else if (bounds.min().isPresent()) {
++ return converter.apply(Range.atLeast(bounds.min().get()));
++ }
++ throw new IllegalStateException("This is a bug: " + bounds);
++ }
++
++ @Override
++ public ArgumentType<World> world() {
++ return this.wrap(DimensionArgument.dimension(), dimensionLocation -> {
++ // based on DimensionArgument#getDimension
++ final ResourceKey<Level> resourceKey = ResourceKey.create(Registries.DIMENSION, dimensionLocation);
++ @Nullable final ServerLevel serverLevel = MinecraftServer.getServer().getLevel(resourceKey);
++ if (serverLevel == null) {
++ throw DimensionArgument.ERROR_INVALID_VALUE.create(dimensionLocation);
++ } else {
++ return serverLevel.getWorld();
++ }
++ });
++ }
++
++ @Override
++ public ArgumentType<GameMode> gameMode() {
++ return this.wrap(GameModeArgument.gameMode(), type -> requireNonNull(GameMode.getByValue(type.getId())));
++ }
++
++ @Override
++ public ArgumentType<HeightMap> heightMap() {
++ return this.wrap(HeightmapTypeArgument.heightmap(), CraftHeightMap::fromNMS);
++ }
++
++ @Override
++ public ArgumentType<UUID> uuid() {
++ return this.wrap(UuidArgument.uuid());
++ }
++
++ @Override
++ public ArgumentType<Criteria> objectiveCriteria() {
++ return this.wrap(ObjectiveCriteriaArgument.criteria(), CraftCriteria::getFromNMS);
++ }
++
++ @Override
++ public ArgumentType<LookAnchor> entityAnchor() {
++ return this.wrap(EntityAnchorArgument.anchor(), type -> LookAnchor.valueOf(type.name()));
++ }
++
++ @Override
++ public ArgumentType<Integer> time(final int minTicks) {
++ return this.wrap(TimeArgument.time(minTicks));
++ }
++
++ @Override
++ public ArgumentType<Mirror> templateMirror() {
++ return this.wrap(TemplateMirrorArgument.templateMirror(), mirror -> Mirror.valueOf(mirror.name()));
++ }
++
++ @Override
++ public ArgumentType<StructureRotation> templateRotation() {
++ return this.wrap(TemplateRotationArgument.templateRotation(), mirror -> StructureRotation.valueOf(mirror.name()));
++ }
++
++ private <T> ArgumentType<T> wrap(final ArgumentType<T> base) {
++ return this.wrap(base, identity -> identity);
++ }
++
++ private <B, C> ArgumentType<C> wrap(final ArgumentType<B> base, final ResultConverter<B, C> converter) {
++ return new NativeWrapperArgumentType<>(base, converter);
++ }
++
++ @FunctionalInterface
++ interface ResultConverter<T, R> {
++
++ R convert(T type) throws CommandSyntaxException;
++ }
++
++ public static final class NativeWrapperArgumentType<M, P> implements ArgumentType<P> {
++
++ private final ArgumentType<M> nmsBase;
++ private final ResultConverter<M, P> converter;
++
++ private NativeWrapperArgumentType(final ArgumentType<M> nmsBase, final ResultConverter<M, P> converter) {
++ this.nmsBase = nmsBase;
++ this.converter = converter;
++ }
++
++ public ArgumentType<M> nativeNmsArgumentType() {
++ return this.nmsBase;
++ }
++
++ @Override
++ public P parse(final StringReader reader) throws CommandSyntaxException {
++ return this.converter.convert(this.nmsBase.parse(reader));
++ }
++
++ @Override
++ public <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
++ return this.nmsBase.listSuggestions(context, builder);
++ }
++
++ @Override
++ public Collection<String> getExamples() {
++ return this.nmsBase.getExamples();
++ }
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/WrappedArgumentCommandNode.java b/src/main/java/io/papermc/paper/command/brigadier/argument/WrappedArgumentCommandNode.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..c59bbd90fdf04db837366218b312e7fb80366707
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/argument/WrappedArgumentCommandNode.java
+@@ -0,0 +1,55 @@
++package io.papermc.paper.command.brigadier.argument;
++
++import com.mojang.brigadier.Command;
++import com.mojang.brigadier.RedirectModifier;
++import com.mojang.brigadier.StringReader;
++import com.mojang.brigadier.arguments.ArgumentType;
++import com.mojang.brigadier.context.CommandContextBuilder;
++import com.mojang.brigadier.context.ParsedArgument;
++import com.mojang.brigadier.exceptions.CommandSyntaxException;
++import com.mojang.brigadier.suggestion.SuggestionProvider;
++import com.mojang.brigadier.tree.ArgumentCommandNode;
++import com.mojang.brigadier.tree.CommandNode;
++import io.papermc.paper.command.brigadier.CommandSourceStack;
++import net.minecraft.commands.synchronization.ArgumentTypeInfos;
++
++import java.util.function.Predicate;
++
++/*
++Basically this converts the argument to a different type when parsing.
++ */
++public class WrappedArgumentCommandNode<NMS, API> extends ArgumentCommandNode<CommandSourceStack, NMS> {
++
++ private final ArgumentType<API> pureArgumentType;
++
++ public WrappedArgumentCommandNode(
++ final String name,
++ final ArgumentType<API> pureArgumentType,
++ final ArgumentType<NMS> nmsNativeType,
++ final Command<CommandSourceStack> command,
++ final Predicate<CommandSourceStack> requirement,
++ final CommandNode<CommandSourceStack> redirect,
++ final RedirectModifier<CommandSourceStack> modifier,
++ final boolean forks,
++ final SuggestionProvider<CommandSourceStack> customSuggestions
++ ) {
++ super(name, nmsNativeType, command, requirement, redirect, modifier, forks, customSuggestions);
++ if (!ArgumentTypeInfos.isClassRecognized(nmsNativeType.getClass())) {
++ // Is this argument an NMS argument?
++ throw new IllegalArgumentException("Unexpected argument type was passed: " + nmsNativeType.getClass() + ". This should be an NMS type!");
++ }
++
++ this.pureArgumentType = pureArgumentType;
++ }
++
++ // See ArgumentCommandNode#parse
++ @Override
++ public void parse(final StringReader reader, final CommandContextBuilder<CommandSourceStack> contextBuilder) throws CommandSyntaxException {
++ final int start = reader.getCursor();
++ final API result = this.pureArgumentType.parse(reader); // Use the api argument parser
++ final ParsedArgument<CommandSourceStack, API> parsed = new ParsedArgument<>(start, reader.getCursor(), result); // Return an API parsed argument instead.
++
++ contextBuilder.withArgument(this.getName(), parsed);
++ contextBuilder.withNode(this, parsed.getRange());
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitBrigForwardingMap.java b/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitBrigForwardingMap.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..f0cc27640bb3db275295a298d608c9d9f88df617
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitBrigForwardingMap.java
+@@ -0,0 +1,332 @@
++package io.papermc.paper.command.brigadier.bukkit;
++
++import com.google.common.collect.Iterators;
++import com.mojang.brigadier.CommandDispatcher;
++import com.mojang.brigadier.tree.CommandNode;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++import io.papermc.paper.command.brigadier.CommandSourceStack;
++import io.papermc.paper.command.brigadier.PaperBrigadier;
++import io.papermc.paper.command.brigadier.PaperCommands;
++import java.util.AbstractCollection;
++import java.util.AbstractSet;
++import java.util.ArrayList;
++import java.util.Collection;
++import java.util.HashMap;
++import java.util.Iterator;
++import java.util.Map;
++import java.util.Objects;
++import java.util.Set;
++import java.util.Spliterator;
++import java.util.function.Consumer;
++import java.util.stream.Stream;
++import org.bukkit.command.Command;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++/*
++This map is supposed to act as a legacy bridge for the command map and the command dispatcher.
++ */
++public class BukkitBrigForwardingMap extends HashMap<String, Command> {
++
++ public static BukkitBrigForwardingMap INSTANCE = new BukkitBrigForwardingMap();
++
++ private final EntrySet entrySet = new EntrySet();
++ private final KeySet keySet = new KeySet();
++ private final Values values = new Values();
++
++ // Previous dispatcher used to get commands to migrate to another dispatcher
++
++ public CommandDispatcher<CommandSourceStack> getDispatcher() {
++ return PaperCommands.INSTANCE.getDispatcherInternal();
++ }
++
++ @Override
++ public int size() {
++ return this.getDispatcher().getRoot().getChildren().size();
++ }
++
++ @Override
++ public boolean isEmpty() {
++ return this.size() != 0;
++ }
++
++ @Override
++ public boolean containsKey(Object key) {
++ if (!(key instanceof String stringKey)) {
++ return false;
++ }
++
++ // Do any children match?
++ return this.getDispatcher().getRoot().getChild(stringKey) != null;
++ }
++
++ @Override
++ public boolean containsValue(@Nullable final Object value) {
++ if (!(value instanceof Command)) {
++ return false;
++ }
++
++ for (CommandNode<CommandSourceStack> child : this.getDispatcher().getRoot().getChildren()) {
++ // If child is a bukkit command node, we can convert it!
++ if (child instanceof BukkitCommandNode bukkitCommandNode) {
++ return bukkitCommandNode.getBukkitCommand().equals(value);
++ }
++ }
++
++ return false;
++ }
++
++ @Override
++ public Command get(Object key) {
++ CommandNode<?> node = this.getDispatcher().getRoot().getChild((String) key);
++ if (node == null) {
++ return null;
++ }
++
++ if (node instanceof BukkitCommandNode bukkitCommandNode) {
++ return bukkitCommandNode.getBukkitCommand();
++ }
++
++ return PaperBrigadier.wrapNode(node);
++ }
++
++ @Nullable
++ @Override
++ public Command put(String key, Command value) {
++ Command old = this.get(key);
++ this.getDispatcher().getRoot().removeCommand(key); // Override previous command
++ this.getDispatcher().getRoot().addChild(BukkitCommandNode.of(key, value));
++ return old;
++ }
++
++ @Override
++ public Command remove(Object key) {
++ if (!(key instanceof String string)) {
++ return null;
++ }
++
++ Command old = this.get(key);
++ if (old != null) {
++ this.getDispatcher().getRoot().removeCommand(string);
++ }
++
++ return old;
++ }
++
++ @Override
++ public boolean remove(Object key, Object value) {
++ Command old = this.get(key);
++ if (Objects.equals(old, value)) {
++ this.remove(key);
++ return true;
++ }
++
++ return false;
++ }
++
++ @Override
++ public void putAll(@NotNull Map<? extends String, ? extends Command> m) {
++ for (Entry<? extends String, ? extends Command> entry : m.entrySet()) {
++ this.put(entry.getKey(), entry.getValue());
++ }
++ }
++
++ @Override
++ public void clear() {
++ this.getDispatcher().getRoot().clearAll();
++ }
++
++ @NotNull
++ @Override
++ public Set<String> keySet() {
++ return this.keySet;
++ }
++
++ @NotNull
++ @Override
++ public Collection<Command> values() {
++ return this.values;
++ }
++
++ @NotNull
++ @Override
++ public Set<Entry<String, Command>> entrySet() {
++ return this.entrySet;
++ }
++
++ final class Values extends AbstractCollection<Command> {
++
++ @Override
++ public Iterator<Command> iterator() {
++ // AVOID CME since commands can modify multiple commands now through alises, which means it may appear in the iterator even if removed.
++ // Oh well!
++ Iterator<CommandNode<CommandSourceStack>> iterator = new ArrayList<>(BukkitBrigForwardingMap.this.getDispatcher().getRoot().getChildren()).iterator();
++
++ return new Iterator<>() {
++
++ private CommandNode<CommandSourceStack> lastFetched;
++
++ @Override
++ public void remove() {
++ if (this.lastFetched == null) {
++ throw new IllegalStateException("next not yet called");
++ }
++
++ BukkitBrigForwardingMap.this.remove(this.lastFetched.getName());
++ iterator.remove();
++ }
++
++ @Override
++ public boolean hasNext() {
++ return iterator.hasNext();
++ }
++
++ @Override
++ public Command next() {
++ CommandNode<CommandSourceStack> next = iterator.next();
++ this.lastFetched = next;
++ if (next instanceof BukkitCommandNode bukkitCommandNode) {
++ return bukkitCommandNode.getBukkitCommand();
++ } else {
++ return PaperBrigadier.wrapNode(next);
++ }
++ }
++ };
++ }
++
++ @Override
++ public int size() {
++ return BukkitBrigForwardingMap.this.getDispatcher().getRoot().getChildren().size();
++ }
++
++ @Override
++ public void clear() {
++ BukkitBrigForwardingMap.this.clear();
++ }
++ }
++
++
++ final class KeySet extends AbstractSet<String> {
++
++ @Override
++ public int size() {
++ return BukkitBrigForwardingMap.this.size();
++ }
++
++ @Override
++ public void clear() {
++ BukkitBrigForwardingMap.this.clear();
++ }
++
++ @Override
++ public Iterator<String> iterator() {
++ return Iterators.transform(BukkitBrigForwardingMap.this.values.iterator(), Command::getName); // Wrap around the values iterator for consistancy
++ }
++
++ @Override
++ public boolean contains(Object o) {
++ return BukkitBrigForwardingMap.this.containsKey(o);
++ }
++
++ @Override
++ public boolean remove(Object o) {
++ return BukkitBrigForwardingMap.this.remove(o) != null;
++ }
++
++ @Override
++ public Spliterator<String> spliterator() {
++ return this.entryStream().spliterator();
++ }
++
++ @Override
++ public void forEach(Consumer<? super String> action) {
++ this.entryStream().forEach(action);
++ }
++
++ private Stream<String> entryStream() {
++ return BukkitBrigForwardingMap.this.getDispatcher().getRoot().getChildren().stream().map(CommandNode::getName);
++ }
++ }
++
++ final class EntrySet extends AbstractSet<Entry<String, Command>> {
++ @Override
++ public int size() {
++ return BukkitBrigForwardingMap.this.size();
++ }
++
++
++ @Override
++ public void clear() {
++ BukkitBrigForwardingMap.this.clear();
++ }
++
++ @Override
++ public Iterator<Entry<String, Command>> iterator() {
++ return this.entryStream().iterator();
++ }
++
++ @Override
++ public boolean contains(Object o) {
++ if (!(o instanceof Map.Entry<?, ?> entry)) {
++ return false;
++ }
++
++ Object key = entry.getKey();
++ Command candidate = get(key);
++ return candidate != null && candidate.equals(entry.getValue());
++ }
++
++ @Override
++ public boolean remove(Object o) {
++ if (o instanceof Map.Entry<?, ?> e) {
++ Object key = e.getKey();
++ Object value = e.getValue();
++ return BukkitBrigForwardingMap.this.remove(key, value);
++ }
++ return false;
++ }
++
++ @Override
++ public Spliterator<Entry<String, Command>> spliterator() {
++ return this.entryStream().spliterator();
++ }
++
++ @Override
++ public void forEach(Consumer<? super Entry<String, Command>> action) {
++ this.entryStream().forEach(action);
++ }
++
++ private Stream<Map.Entry<String, Command>> entryStream() {
++ return BukkitBrigForwardingMap.this.getDispatcher().getRoot().getChildren().stream().map(BukkitBrigForwardingMap.this::nodeToEntry);
++ }
++ }
++
++ private Map.Entry<String, Command> nodeToEntry(CommandNode<?> node) {
++ if (node instanceof BukkitCommandNode bukkitCommandNode) {
++ return this.mutableEntry(bukkitCommandNode.getName(), bukkitCommandNode.getBukkitCommand());
++ } else {
++ Command wrapped = PaperBrigadier.wrapNode(node);
++ return this.mutableEntry(node.getName(), wrapped);
++ }
++ }
++
++ private Map.Entry<String, Command> mutableEntry(String key, Command command) {
++ return new Entry<>() {
++ @Override
++ public String getKey() {
++ return key;
++ }
++
++ @Override
++ public Command getValue() {
++ return command;
++ }
++
++ @Override
++ public Command setValue(Command value) {
++ return BukkitBrigForwardingMap.this.put(key, value);
++ }
++ };
++ }
++
++}
+diff --git a/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitCommandNode.java b/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitCommandNode.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..10a113b057b0a4d27cce3bae975e1108aaa7b517
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitCommandNode.java
+@@ -0,0 +1,135 @@
++package io.papermc.paper.command.brigadier.bukkit;
++
++import co.aikar.timings.Timing;
++import com.mojang.brigadier.arguments.StringArgumentType;
++import com.mojang.brigadier.builder.RequiredArgumentBuilder;
++import com.mojang.brigadier.context.CommandContext;
++import com.mojang.brigadier.exceptions.CommandSyntaxException;
++import com.mojang.brigadier.suggestion.SuggestionProvider;
++import com.mojang.brigadier.suggestion.Suggestions;
++import com.mojang.brigadier.suggestion.SuggestionsBuilder;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++import io.papermc.paper.command.brigadier.CommandSourceStack;
++import net.minecraft.commands.CommandSource;
++import org.bukkit.Bukkit;
++import org.bukkit.ChatColor;
++import org.bukkit.Location;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandException;
++import org.bukkit.command.CommandSender;
++
++import java.util.Arrays;
++import java.util.List;
++import java.util.concurrent.CompletableFuture;
++import java.util.logging.Level;
++
++public class BukkitCommandNode extends LiteralCommandNode<CommandSourceStack> {
++
++ private final Command command;
++
++ private BukkitCommandNode(String literal, Command command, BukkitBrigCommand bukkitBrigCommand) {
++ super(
++ literal, bukkitBrigCommand, source -> {
++ // If the source is null, assume it's true.
++ // As bukkit doesn't really map the command sender well in all cases
++ if (source instanceof net.minecraft.commands.CommandSourceStack commandSourceStack && commandSourceStack.source == CommandSource.NULL) {
++ return true;
++ } else {
++ return command.testPermissionSilent(source.getSender());
++ }
++ },
++ null, null, false
++ );
++ this.command = command;
++ }
++
++ public static BukkitCommandNode of(String name, Command command) {
++ BukkitBrigCommand bukkitBrigCommand = new BukkitBrigCommand(command, name);
++ BukkitCommandNode commandNode = new BukkitCommandNode(name, command, bukkitBrigCommand);
++ commandNode.addChild(
++ RequiredArgumentBuilder.<CommandSourceStack, String>argument("args", StringArgumentType.greedyString())
++ .suggests(new BukkitBrigSuggestionProvider(command, name))
++ .executes(bukkitBrigCommand).build()
++ );
++
++ return commandNode;
++ }
++
++ public Command getBukkitCommand() {
++ return this.command;
++ }
++
++ public static class BukkitBrigCommand implements com.mojang.brigadier.Command<CommandSourceStack> {
++
++ private final org.bukkit.command.Command command;
++ private final String literal;
++
++ BukkitBrigCommand(org.bukkit.command.Command command, String literal) {
++ this.command = command;
++ this.literal = literal;
++ }
++
++ @Override
++ public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
++ CommandSender sender = context.getSource().getSender();
++
++ // Plugins do weird things to workaround normal registration
++ if (this.command.timings == null) {
++ this.command.timings = co.aikar.timings.TimingsManager.getCommandTiming(null, this.command);
++ }
++
++ String content = context.getRange().get(context.getInput());
++ String[] args = org.apache.commons.lang3.StringUtils.split(content, ' '); // fix adjacent spaces (from console/plugins) causing empty array elements
++
++ try (Timing ignored = this.command.timings.startTiming()) {
++ // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false)
++ this.command.execute(sender, this.literal, Arrays.copyOfRange(args, 1, args.length));
++ }
++
++ // return true as command was handled
++ return 1;
++ }
++ }
++
++ static class BukkitBrigSuggestionProvider implements SuggestionProvider<CommandSourceStack> {
++
++ private final org.bukkit.command.Command command;
++ private final String literal;
++
++ BukkitBrigSuggestionProvider(org.bukkit.command.Command command, String literal) {
++ this.command = command;
++ this.literal = literal;
++ }
++
++ @Override
++ public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) throws CommandSyntaxException {
++ // Paper start
++ org.bukkit.command.CommandSender sender = context.getSource().getSender();
++ String[] args = builder.getRemaining().split(" ", -1); // We need the command included -- Set limit to -1, allow for trailing spaces
++
++ List<String> results = null;
++ Location pos = context.getSource().getLocation();
++ try {
++ results = this.command.tabComplete(sender, this.literal, args, pos.clone());
++ } catch (CommandException ex) {
++ sender.sendMessage(ChatColor.RED + "An internal error occurred while attempting to tab-complete this command");
++ Bukkit.getServer().getLogger().log(Level.SEVERE, "Exception when " + sender.getName() + " attempted to tab complete " + builder.getRemaining(), ex);
++ }
++
++ // Paper end
++ if (results == null) {
++ return builder.buildFuture();
++ }
++
++ // Defaults to sub nodes, but we have just one giant args node, so offset accordingly
++ builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1);
++
++ for (String s : results) {
++ builder.suggest(s);
++ }
++
++ return builder.buildFuture();
++ }
++ }
++
++}
+diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
+index f84c9c80e701231e5c33ac3c5573f1093e80f38b..6c072e44a8144de6658b4eb818c996f0eac5805b 100644
+--- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
+@@ -9,6 +9,7 @@ import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEventImpl;
+ import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent;
+ import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType;
+ import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
+ import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent;
+ import java.util.ArrayList;
+ import java.util.List;
+@@ -26,6 +27,7 @@ public class LifecycleEventRunner {
+
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+ private static final Supplier<Set<LifecycleEventType<?, ?, ?>>> BLOCKS_RELOADING = Suppliers.memoize(() -> Set.of( // lazy due to cyclic initialization
++ LifecycleEvents.COMMANDS
+ ));
+ public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner();
+
+diff --git a/src/main/java/net/minecraft/commands/CommandSource.java b/src/main/java/net/minecraft/commands/CommandSource.java
+index 5ba0ef6eda157c4e61d1de99c6b017ceb34430ec..bc5fc57018e347caa5ca453430a45669e086bb22 100644
+--- a/src/main/java/net/minecraft/commands/CommandSource.java
++++ b/src/main/java/net/minecraft/commands/CommandSource.java
+@@ -26,7 +26,7 @@ public interface CommandSource {
+ // CraftBukkit start
+ @Override
+ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
+- throw new UnsupportedOperationException("Not supported yet.");
++ return io.papermc.paper.brigadier.NullCommandSender.INSTANCE; // Paper - expose a no-op CommandSender
+ }
+ // CraftBukkit end
+ };
+diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java
+index e6c7f62ed379a78645933670299e4fcda8540ed1..59d7e8a3d83d3ab7aa28606401bb129ccaeff240 100644
+--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java
++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java
+@@ -45,8 +45,7 @@ import net.minecraft.world.phys.Vec2;
+ import net.minecraft.world.phys.Vec3;
+ import com.mojang.brigadier.tree.CommandNode; // CraftBukkit
+
+-public class CommandSourceStack implements ExecutionCommandSource<CommandSourceStack>, SharedSuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource { // Paper - Brigadier API
+-
++public class CommandSourceStack implements ExecutionCommandSource<CommandSourceStack>, SharedSuggestionProvider, io.papermc.paper.command.brigadier.PaperCommandSourceStack { // Paper - Brigadier API
+ public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player"));
+ public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(Component.translatable("permissions.requires.entity"));
+ public final CommandSource source;
+@@ -170,26 +169,6 @@ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceS
+ return this.textName;
+ }
+
+- // Paper start - Brigadier API
+- @Override
+- public org.bukkit.entity.Entity getBukkitEntity() {
+- return getEntity() != null ? getEntity().getBukkitEntity() : null;
+- }
+-
+- @Override
+- public org.bukkit.World getBukkitWorld() {
+- return getLevel() != null ? getLevel().getWorld() : null;
+- }
+-
+- @Override
+- public org.bukkit.Location getBukkitLocation() {
+- Vec3 pos = getPosition();
+- org.bukkit.World world = getBukkitWorld();
+- Vec2 rot = getRotation();
+- return world != null && pos != null ? new org.bukkit.Location(world, pos.x, pos.y, pos.z, rot != null ? rot.y : 0, rot != null ? rot.x : 0) : null;
+- }
+- // Paper end - Brigadier API
+-
+ @Override
+ public boolean hasPermission(int level) {
+ // CraftBukkit start
+@@ -457,6 +436,12 @@ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceS
+ return this.silent;
+ }
+
++ // Paper start
++ @Override
++ public CommandSourceStack getHandle() {
++ return this;
++ }
++ // Paper end
+ // CraftBukkit start
+ public org.bukkit.command.CommandSender getBukkitSender() {
+ return this.source.getBukkitSender(this);
+diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
+index aa2fca6917fb67fe0e9ba067d11487c3a274f675..2b33c5f5a7c5c87cf975c5237a2c9ba8cc0d2bdf 100644
+--- a/src/main/java/net/minecraft/commands/Commands.java
++++ b/src/main/java/net/minecraft/commands/Commands.java
+@@ -156,7 +156,7 @@ public class Commands {
+ private final com.mojang.brigadier.CommandDispatcher<CommandSourceStack> dispatcher = new com.mojang.brigadier.CommandDispatcher();
+
+ public Commands(Commands.CommandSelection environment, CommandBuildContext commandRegistryAccess) {
+- this(); // CraftBukkit
++ // Paper
+ AdvancementCommands.register(this.dispatcher);
+ AttributeCommand.register(this.dispatcher, commandRegistryAccess);
+ ExecuteCommand.register(this.dispatcher, commandRegistryAccess);
+@@ -265,11 +265,17 @@ public class Commands {
+ }
+ }
+ // Paper end - Vanilla command permission fixes
+- // CraftBukkit start
+- }
+-
+- public Commands() {
+- // CraftBukkkit end
++ // Paper start - Brigadier Command API
++ // Create legacy minecraft namespace commands
++ for (final CommandNode<CommandSourceStack> node : new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren())) {
++ this.dispatcher.register(
++ com.mojang.brigadier.builder.LiteralArgumentBuilder.<CommandSourceStack>literal("minecraft:" + node.getName())
++ .executes(node.getCommand())
++ .redirect(node)
++ );
++ }
++ // Paper end - Brigadier Command API
++ // Paper - remove public constructor, no longer needed
+ this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
+ }
+
+@@ -325,6 +331,11 @@ public class Commands {
+ }
+
+ public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label) { // CraftBukkit
++ // Paper start
++ this.performCommand(parseresults, s, label, false);
++ }
++ public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label, boolean throwCommandError) {
++ // Paper end
+ CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseresults.getContext().getSource();
+
+ commandlistenerwrapper.getServer().getProfiler().push(() -> {
+@@ -339,6 +350,7 @@ public class Commands {
+ });
+ }
+ } catch (Exception exception) {
++ if (throwCommandError) throw exception;
+ MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage());
+
+ if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging
+@@ -477,7 +489,7 @@ public class Commands {
+ Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
+ RootCommandNode vanillaRoot = new RootCommandNode();
+
+- RootCommandNode<CommandSourceStack> vanilla = player.server.vanillaCommandDispatcher.getDispatcher().getRoot();
++ RootCommandNode<CommandSourceStack> vanilla = player.server.getCommands().getDispatcher().getRoot(); // Paper
+ map.put(vanilla, vanillaRoot);
+ this.fillUsableCommands(vanilla, vanillaRoot, player.createCommandSourceStack(), (Map) map);
+
+@@ -515,6 +527,7 @@ public class Commands {
+ }
+
+ private void fillUsableCommands(CommandNode<CommandSourceStack> tree, CommandNode<SharedSuggestionProvider> result, CommandSourceStack source, Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> resultNodes) {
++ resultNodes.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains( ":" )); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
+ Iterator iterator = tree.getChildren().iterator();
+
+ boolean registeredAskServerSuggestionsForTree = false; // Paper - tell clients to ask server for suggestions for EntityArguments
+@@ -529,6 +542,42 @@ public class Commands {
+
+ if (commandnode2.canUse(source)) {
+ ArgumentBuilder argumentbuilder = commandnode2.createBuilder(); // CraftBukkit - decompile error
++ // Paper start
++ /*
++ Because of how commands can be yeeted right left and center due to bad bukkit practices
++ we need to be able to ensure that ALL commands are registered (even redirects).
++
++ What this will do is IF the redirect seems to be "dead" it will create a builder and essentially populate (flatten)
++ all the children from the dead redirect to the node.
++
++ So, if minecraft:msg redirects to msg but the original msg node has been overriden minecraft:msg will now act as msg and will explicilty inherit its children.
++
++ The only way to fix this is to either:
++ - Send EVERYTHING flattened, don't use redirects
++ - Don't allow command nodes to be deleted
++ - Do this :)
++ */
++
++ // Is there an invalid command redirect?
++ if (argumentbuilder.getRedirect() != null && (CommandNode) resultNodes.get(argumentbuilder.getRedirect()) == null) {
++ // Create the argument builder with the same values as the specified node, but with a different literal and populated children
++
++ CommandNode<CommandSourceStack> redirect = argumentbuilder.getRedirect();
++ // Diff copied from LiteralCommand#createBuilder
++ final com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> builder = com.mojang.brigadier.builder.LiteralArgumentBuilder.literal(commandnode2.getName());
++ builder.requires(redirect.getRequirement());
++ // builder.forward(redirect.getRedirect(), redirect.getRedirectModifier(), redirect.isFork()); We don't want to migrate the forward, since it's invalid.
++ if (redirect.getCommand() != null) {
++ builder.executes(redirect.getCommand());
++ }
++ // Diff copied from LiteralCommand#createBuilder
++ for (CommandNode<CommandSourceStack> child : redirect.getChildren()) {
++ builder.then(child);
++ }
++
++ argumentbuilder = builder;
++ }
++ // Paper end
+
+ argumentbuilder.requires((icompletionprovider) -> {
+ return true;
+diff --git a/src/main/java/net/minecraft/commands/arguments/MessageArgument.java b/src/main/java/net/minecraft/commands/arguments/MessageArgument.java
+index 982b2bab27e3d55d0ba07060862c0c3183ad91b0..5fa8a3343ffc11e82c20b78a73205fd8a42d3c5d 100644
+--- a/src/main/java/net/minecraft/commands/arguments/MessageArgument.java
++++ b/src/main/java/net/minecraft/commands/arguments/MessageArgument.java
+@@ -39,6 +39,11 @@ public class MessageArgument implements SignedArgument<MessageArgument.Message>
+
+ public static void resolveChatMessage(CommandContext<CommandSourceStack> context, String name, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
+ MessageArgument.Message message = context.getArgument(name, MessageArgument.Message.class);
++ // Paper start
++ resolveChatMessage(message, context, name, callback);
++ }
++ public static void resolveChatMessage(MessageArgument.Message message, CommandContext<CommandSourceStack> context, String name, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
++ // Paper end
+ CommandSourceStack commandSourceStack = context.getSource();
+ Component component = message.resolveComponent(commandSourceStack);
+ CommandSigningContext commandSigningContext = commandSourceStack.getSigningContext();
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index cac41b5cea8c6362946c6bd5e50b449d033ab6a9..39303bb4e336732db0ab19dee0c1f8b609bbb134 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -302,7 +302,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ public static int currentTick; // Paper - improve tick loop
+ public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
+ public int autosavePeriod;
+- public Commands vanillaCommandDispatcher;
++ // Paper - don't store the vanilla dispatcher
+ public boolean forceTicks; // Paper
+ // CraftBukkit end
+ // Spigot start
+@@ -393,7 +393,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ // CraftBukkit start
+ this.options = options;
+ this.worldLoader = worldLoader;
+- this.vanillaCommandDispatcher = worldstem.dataPackResources().commands; // CraftBukkit
+ // Paper start - Handled by TerminalConsoleAppender
+ // Try to see if we're actually running in a terminal, disable jline if not
+ /*
+@@ -681,6 +680,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+
+ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // Paper - reset invalid state for event fire below
++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - call commands event for regular plugins
++ ((org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap()).initializeCommands();
+ this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
+ this.connection.acceptConnections();
+ }
+@@ -2302,9 +2304,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ return new MinecraftServer.ReloadableResources(resourcemanager, datapackresources);
+ });
+ }).thenAcceptAsync((minecraftserver_reloadableresources) -> {
++ io.papermc.paper.command.brigadier.PaperBrigadier.moveBukkitCommands(this.resources.managers().getCommands(), minecraftserver_reloadableresources.managers().commands); // Paper
+ this.resources.close();
+ this.resources = minecraftserver_reloadableresources;
+- this.server.syncCommands(); // SPIGOT-5884: Lost on reload
+ this.packRepository.setSelected(dataPacks);
+ WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(MinecraftServer.getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures());
+
+@@ -2323,8 +2325,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ this.getPlayerList().reloadResources();
+ this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
+ this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
+- org.bukkit.craftbukkit.block.data.CraftBlockData.reloadCache(); // Paper - cache block data strings; they can be defined by datapacks so refresh it here
+- new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper - Add ServerResourcesReloadedEvent; fire after everything has been reloaded
++ org.bukkit.craftbukkit.block.data.CraftBlockData.reloadCache(); // Paper - cache block data strings, they can be defined by datapacks so refresh it here
++ // Paper start - brigadier command API
++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // reset invalid state for event fire below
++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // call commands event for regular plugins
++ final org.bukkit.craftbukkit.help.SimpleHelpMap helpMap = (org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap();
++ helpMap.clear();
++ helpMap.initializeGeneralTopics();
++ helpMap.initializeCommands();
++ this.server.syncCommands(); // Refresh commands after event
++ // Paper end
++ new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper - fire after everything has been reloaded
+ }, this);
+
+ if (this.isSameThread()) {
+diff --git a/src/main/java/net/minecraft/server/ReloadableServerResources.java b/src/main/java/net/minecraft/server/ReloadableServerResources.java
+index 84b4bfe8363adc015821e9cabedfabed98c0336c..6de563b7adea957a7ead1c00c4900060fa5df277 100644
+--- a/src/main/java/net/minecraft/server/ReloadableServerResources.java
++++ b/src/main/java/net/minecraft/server/ReloadableServerResources.java
+@@ -48,6 +48,7 @@ public class ReloadableServerResources {
+ this.recipes = new RecipeManager(this.registryLookup);
+ this.tagManager = new TagManager(dynamicRegistryManager);
+ this.commands = new Commands(environment, CommandBuildContext.simple(this.registryLookup, enabledFeatures));
++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setDispatcher(this.commands, CommandBuildContext.simple(this.registryLookup, enabledFeatures)); // Paper - Brigadier Command API
+ this.advancements = new ServerAdvancementManager(this.registryLookup);
+ this.functionLibrary = new ServerFunctionLibrary(functionPermissionLevel, this.commands.getDispatcher());
+ }
+@@ -91,6 +92,14 @@ public class ReloadableServerResources {
+ ReloadableServerResources reloadableServerResources = new ReloadableServerResources(
+ reloadedDynamicRegistries.compositeAccess(), enabledFeatures, environment, functionPermissionLevel
+ );
++ // Paper start - call commands event for bootstraps
++ //noinspection ConstantValue
++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(
++ io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS,
++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE,
++ io.papermc.paper.plugin.bootstrap.BootstrapContext.class,
++ MinecraftServer.getServer() == null ? io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL : io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD);
++ // Paper end - call commands event
+ return SimpleReloadInstance.create(
+ manager, reloadableServerResources.listeners(), prepareExecutor, applyExecutor, DATA_RELOAD_INITIAL_TASK, LOGGER.isDebugEnabled()
+ )
+diff --git a/src/main/java/net/minecraft/server/ServerFunctionManager.java b/src/main/java/net/minecraft/server/ServerFunctionManager.java
+index a0ec6c3d122ad28d65d37f1b9f82541997b37d37..c6d7ee0d498bd92d4321acd30ade10abea611e42 100644
+--- a/src/main/java/net/minecraft/server/ServerFunctionManager.java
++++ b/src/main/java/net/minecraft/server/ServerFunctionManager.java
+@@ -36,7 +36,7 @@ public class ServerFunctionManager {
+ }
+
+ public CommandDispatcher<CommandSourceStack> getDispatcher() {
+- return this.server.vanillaCommandDispatcher.getDispatcher(); // CraftBukkit
++ return this.server.getCommands().getDispatcher(); // CraftBukkit // Paper - Don't override command dispatcher
+ }
+
+ public void tick() {
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index eb4fc900164d1fb3a78653ae8bc42ea30323f5b7..2eb9c584cc77237f1c82d880a51a3f8b51008d73 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -233,7 +233,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
+ com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
+- io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // Paper - init PaperBrigadierProvider
+
+ this.setPvpAllowed(dedicatedserverproperties.pvp);
+ this.setFlightAllowed(dedicatedserverproperties.allowFlight);
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 5e9202bc7fc649764568b55d66ba0d684118c00c..379d87cdab68e161a71063af5cd47bd08daef119 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -2464,33 +2464,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ }
+ }
+
++ @Deprecated // Paper
+ public void handleCommand(String s) { // Paper - private -> public
+- org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + s); // Paper - Add async catcher
+- co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper
+- if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot
+- this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s);
+-
+- CraftPlayer player = this.getCraftPlayer();
+-
+- PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(player, s, new LazyPlayerSet(this.server));
+- this.cserver.getPluginManager().callEvent(event);
+-
+- if (event.isCancelled()) {
+- co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper
+- return;
+- }
+-
+- try {
+- if (this.cserver.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) {
+- return;
+- }
+- } catch (org.bukkit.command.CommandException ex) {
+- player.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command");
+- java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+- return;
+- } finally {
+- co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper
++ // Paper start - Remove all this old duplicated logic
++ if (s.startsWith("/")) {
++ s = s.substring(1);
+ }
++ /*
++ It should be noted that this represents the "legacy" command execution path.
++ Api can call commands even if there is no additional context provided.
++ This method should ONLY be used if you need to execute a command WITHOUT
++ an actual player's input.
++ */
++ this.performUnsignedChatCommand(s);
++ // Paper end
+ }
+ // CraftBukkit end
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 10622ba2f035c183b709748818b6de1a1a8d9ed0..7c165ed8b1fd8072bbfbed7b4f865b72f677a2a3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -274,11 +274,11 @@ public final class CraftServer implements Server {
+ private final Logger logger = Logger.getLogger("Minecraft");
+ private final ServicesManager servicesManager = new SimpleServicesManager();
+ private final CraftScheduler scheduler = new CraftScheduler();
+- private final CraftCommandMap commandMap = new CraftCommandMap(this);
++ private final CraftCommandMap commandMap; // Paper - Move down
+ private final SimpleHelpMap helpMap = new SimpleHelpMap(this);
+ private final StandardMessenger messenger = new StandardMessenger();
+- private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap);
+- public final io.papermc.paper.plugin.manager.PaperPluginManagerImpl paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); {this.pluginManager.paperPluginManager = this.paperPluginManager;} // Paper
++ private final SimplePluginManager pluginManager; // Paper - Move down
++ public final io.papermc.paper.plugin.manager.PaperPluginManagerImpl paperPluginManager; // Paper
+ private final StructureManager structureManager;
+ protected final DedicatedServer console;
+ protected final DedicatedPlayerList playerList;
+@@ -403,6 +403,12 @@ public final class CraftServer implements Server {
+ this.serverTickManager = new CraftServerTickManager(console.tickRateManager());
+
+ Bukkit.setServer(this);
++ // Paper start
++ this.commandMap = new CraftCommandMap(this);
++ this.pluginManager = new SimplePluginManager(this, commandMap);
++ this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager);
++ this.pluginManager.paperPluginManager = this.paperPluginManager;
++ // Paper end
+
+ CraftRegistry.setMinecraftRegistry(console.registryAccess());
+
+@@ -572,48 +578,11 @@ public final class CraftServer implements Server {
+ }
+
+ private void setVanillaCommands(boolean first) { // Spigot
+- Commands dispatcher = this.console.vanillaCommandDispatcher;
+-
+- // Build a list of all Vanilla commands and create wrappers
+- for (CommandNode<CommandSourceStack> cmd : dispatcher.getDispatcher().getRoot().getChildren()) {
+- // Spigot start
+- VanillaCommandWrapper wrapper = new VanillaCommandWrapper(dispatcher, cmd);
+- if (org.spigotmc.SpigotConfig.replaceCommands.contains( wrapper.getName() ) ) {
+- if (first) {
+- this.commandMap.register("minecraft", wrapper);
+- }
+- } else if (!first) {
+- this.commandMap.register("minecraft", wrapper);
+- }
+- // Spigot end
+- }
++ // Paper - Replace implementation
+ }
+
+ public void syncCommands() {
+- // Clear existing commands
+- Commands dispatcher = this.console.resources.managers().commands = new Commands();
+-
+- // Register all commands, vanilla ones will be using the old dispatcher references
+- for (Map.Entry<String, Command> entry : this.commandMap.getKnownCommands().entrySet()) {
+- String label = entry.getKey();
+- Command command = entry.getValue();
+-
+- if (command instanceof VanillaCommandWrapper) {
+- LiteralCommandNode<CommandSourceStack> node = (LiteralCommandNode<CommandSourceStack>) ((VanillaCommandWrapper) command).vanillaCommand;
+- if (!node.getLiteral().equals(label)) {
+- LiteralCommandNode<CommandSourceStack> clone = new LiteralCommandNode(label, node.getCommand(), node.getRequirement(), node.getRedirect(), node.getRedirectModifier(), node.isFork());
+-
+- for (CommandNode<CommandSourceStack> child : node.getChildren()) {
+- clone.addChild(child);
+- }
+- node = clone;
+- }
+-
+- dispatcher.getDispatcher().getRoot().addChild(node);
+- } else {
+- new BukkitCommandWrapper(this, entry.getValue()).register(dispatcher.getDispatcher(), label);
+- }
+- }
++ Commands dispatcher = this.getHandle().getServer().getCommands(); // Paper - We now register directly to the dispatcher.
+
+ // Refresh commands
+ for (ServerPlayer player : this.getHandle().players) {
+@@ -1000,17 +969,31 @@ public final class CraftServer implements Server {
+ return true;
+ }
+
+- // Spigot start
+- if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) {
+- // Paper start
+- org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(sender, commandLine, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.unknownCommandMessage));
+- this.getPluginManager().callEvent(event);
+- if (event.message() != null) {
+- sender.sendMessage(event.message());
+- }
+- // Paper end
++ return this.dispatchCommand(VanillaCommandWrapper.getListener(sender), commandLine);
++ }
++
++ public boolean dispatchCommand(CommandSourceStack sourceStack, String commandLine) {
++ net.minecraft.commands.Commands commands = this.getHandle().getServer().getCommands();
++ com.mojang.brigadier.CommandDispatcher<CommandSourceStack> dispatcher = commands.getDispatcher();
++ com.mojang.brigadier.ParseResults<CommandSourceStack> results = dispatcher.parse(commandLine, sourceStack);
++
++ CommandSender sender = sourceStack.getBukkitSender();
++ String[] args = org.apache.commons.lang3.StringUtils.split(commandLine, ' '); // Paper - fix adjacent spaces (from console/plugins) causing empty array elements
++ Command target = this.commandMap.getCommand(args[0].toLowerCase(java.util.Locale.ENGLISH));
++
++ try {
++ commands.performCommand(results, commandLine, commandLine, true);
++ } catch (CommandException ex) {
++ this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper
++ //target.timings.stopTiming(); // Spigot // Paper
++ throw ex;
++ } catch (Throwable ex) {
++ //target.timings.stopTiming(); // Spigot // Paper
++ String msg = "Unhandled exception executing '" + commandLine + "' in " + target;
++ this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper
++ throw new CommandException(msg, ex);
+ }
+- // Spigot end
++ // Paper end
+
+ return false;
+ }
+@@ -1019,7 +1002,7 @@ public final class CraftServer implements Server {
+ public void reload() {
+ // Paper start - lifecycle events
+ if (io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.blocksPluginReloading()) {
+- throw new IllegalStateException("A lifecycle event handler has been registered which makes reloading plugins not possible");
++ throw new IllegalStateException(org.bukkit.command.defaults.ReloadCommand.RELOADING_DISABLED_MESSAGE);
+ }
+ // Paper end - lifecycle events
+ org.spigotmc.WatchdogThread.hasStarted = false; // Paper - Disable watchdog early timeout on reload
+@@ -1072,8 +1055,9 @@ public final class CraftServer implements Server {
+ }
+
+ Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
++ this.commandMap.clearCommands(); // Paper - Move command reloading up
+ this.pluginManager.clearPlugins();
+- this.commandMap.clearCommands();
++ // Paper - move up
+ // Paper start
+ for (Plugin plugin : pluginClone) {
+ entityMetadata.removeAll(plugin);
+@@ -1113,6 +1097,12 @@ public final class CraftServer implements Server {
+ this.enablePlugins(PluginLoadOrder.STARTUP);
+ this.enablePlugins(PluginLoadOrder.POSTWORLD);
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
++ // Paper start - brigadier command API
++ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // to clear invalid state for event fire below
++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // call commands event for regular plugins
++ this.helpMap.initializeCommands();
++ this.syncCommands(); // Refresh commands after event
++ // Paper end - brigadier command API
+ this.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD));
+ org.spigotmc.WatchdogThread.hasStarted = true; // Paper - Disable watchdog early timeout on reload
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java
+index 21b6f90cf5bd7087d1a0f512289d971f2c3e1afa..a3c02200da9e793de79a74fe7e0cd72634150f64 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java
+@@ -20,6 +20,7 @@ import org.bukkit.command.CommandException;
+ import org.bukkit.command.CommandSender;
+ import org.bukkit.craftbukkit.CraftServer;
+
++@Deprecated(forRemoval = true) // Paper - Don't use
+ public class BukkitCommandWrapper implements com.mojang.brigadier.Command<CommandSourceStack>, Predicate<CommandSourceStack>, SuggestionProvider<CommandSourceStack>, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand<CommandSourceStack> { // Paper
+
+ private final CraftServer server;
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftCommandMap.java b/src/main/java/org/bukkit/craftbukkit/command/CraftCommandMap.java
+index 4b1ac1fe7ea07f419ae2818251900e7ba434ee16..90ed57a7fbcd0625b64084347460e9864216f610 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/CraftCommandMap.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftCommandMap.java
+@@ -8,7 +8,7 @@ import org.bukkit.command.SimpleCommandMap;
+ public class CraftCommandMap extends SimpleCommandMap {
+
+ public CraftCommandMap(Server server) {
+- super(server);
++ super(server, io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap.INSTANCE);
+ }
+
+ public Map<String, Command> getKnownCommands() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+index dd1507f65a7f1d84bc7f236f81a60ac1302a13b8..5b70f53bc4b27a715b8b7aa13586082adbc4bd16 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+@@ -23,14 +23,26 @@ import org.bukkit.craftbukkit.entity.CraftMinecartCommand;
+ import org.bukkit.entity.Entity;
+ import org.bukkit.entity.minecart.CommandMinecart;
+
+-public final class VanillaCommandWrapper extends BukkitCommand {
++public class VanillaCommandWrapper extends BukkitCommand { // Paper
+
+- private final Commands dispatcher;
++ //private final Commands dispatcher; // Paper
+ public final CommandNode<CommandSourceStack> vanillaCommand;
+
++ // Paper start
++ public VanillaCommandWrapper(String name, String description, String usageMessage, List<String> aliases, CommandNode<CommandSourceStack> vanillaCommand) {
++ super(name, description, usageMessage, aliases);
++ //this.dispatcher = dispatcher; // Paper
++ this.vanillaCommand = vanillaCommand;
++ }
++
++ Commands commands() {
++ return net.minecraft.server.MinecraftServer.getServer().getCommands();
++ }
++
++ // Paper end
+ public VanillaCommandWrapper(Commands dispatcher, CommandNode<CommandSourceStack> vanillaCommand) {
+ super(vanillaCommand.getName(), "A Mojang provided command.", vanillaCommand.getUsageText(), Collections.EMPTY_LIST);
+- this.dispatcher = dispatcher;
++ // this.dispatcher = dispatcher; // Paper
+ this.vanillaCommand = vanillaCommand;
+ this.setPermission(VanillaCommandWrapper.getPermission(vanillaCommand));
+ }
+@@ -40,7 +52,7 @@ public final class VanillaCommandWrapper extends BukkitCommand {
+ if (!this.testPermission(sender)) return true;
+
+ CommandSourceStack icommandlistener = VanillaCommandWrapper.getListener(sender);
+- this.dispatcher.performPrefixedCommand(icommandlistener, this.toDispatcher(args, this.getName()), this.toDispatcher(args, commandLabel));
++ this.commands().performPrefixedCommand(icommandlistener, this.toDispatcher(args, this.getName()), this.toDispatcher(args, commandLabel)); // Paper
+ return true;
+ }
+
+@@ -51,10 +63,10 @@ public final class VanillaCommandWrapper extends BukkitCommand {
+ Preconditions.checkArgument(alias != null, "Alias cannot be null");
+
+ CommandSourceStack icommandlistener = VanillaCommandWrapper.getListener(sender);
+- ParseResults<CommandSourceStack> parsed = this.dispatcher.getDispatcher().parse(this.toDispatcher(args, this.getName()), icommandlistener);
++ ParseResults<CommandSourceStack> parsed = this.commands().getDispatcher().parse(this.toDispatcher(args, this.getName()), icommandlistener); // Paper
+
+ List<String> results = new ArrayList<>();
+- this.dispatcher.getDispatcher().getCompletionSuggestions(parsed).thenAccept((suggestions) -> {
++ this.commands().getDispatcher().getCompletionSuggestions(parsed).thenAccept((suggestions) -> { // Paper
+ suggestions.getList().forEach((s) -> results.add(s.getText()));
+ });
+
+@@ -114,4 +126,15 @@ public final class VanillaCommandWrapper extends BukkitCommand {
+ private String toDispatcher(String[] args, String name) {
+ return name + ((args.length > 0) ? " " + Joiner.on(' ').join(args) : "");
+ }
++ // Paper start
++ @Override
++ public boolean canBeOverriden() {
++ return true;
++ }
++
++ @Override
++ public boolean isRegistered() {
++ return true;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java b/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java
+index 05d3aecd4abaab6a94effcb1ab35c1b82410865f..97141968f36b3ef88bd6e520c2ccc37c97e4adb1 100644
+--- a/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java
++++ b/src/main/java/org/bukkit/craftbukkit/help/SimpleHelpMap.java
+@@ -200,15 +200,18 @@ public class SimpleHelpMap implements HelpMap {
+ }
+
+ private String getCommandPluginName(Command command) {
++ // Paper start - Move up
++ if (command instanceof PluginIdentifiableCommand) {
++ return ((PluginIdentifiableCommand) command).getPlugin().getName();
++ }
++ // Paper end
+ if (command instanceof VanillaCommandWrapper) {
+ return "Minecraft";
+ }
+ if (command instanceof BukkitCommand) {
+ return "Bukkit";
+ }
+- if (command instanceof PluginIdentifiableCommand) {
+- return ((PluginIdentifiableCommand) command).getPlugin().getName();
+- }
++ // Paper - Move PluginIdentifiableCommand instanceof check to allow brig commands
+ return null;
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java
+index 8464531a4ee400834d25c23b1eb723f49be8689e..4a0b1587180381123eb843819cd10630e49c7a02 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java
++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java
+@@ -53,7 +53,13 @@ public final class CraftCriteria implements Criteria {
+ return RenderType.values()[this.criteria.getDefaultRenderType().ordinal()];
+ }
+
+- static CraftCriteria getFromNMS(Objective objective) {
++ // Paper start
++ public static CraftCriteria getFromNMS(ObjectiveCriteria criteria) {
++ return java.util.Objects.requireNonNullElseGet(CraftCriteria.DEFAULTS.get(criteria.getName()), () -> new CraftCriteria(criteria));
++ }
++ // Paper end
++
++ public static CraftCriteria getFromNMS(Objective objective) {
+ return java.util.Objects.requireNonNullElseGet(CraftCriteria.DEFAULTS.get(objective.getCriteria().getName()), () -> new CraftCriteria(objective.getCriteria())); // Paper
+ }
+
+diff --git a/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.CommandBuilderProvider b/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.CommandBuilderProvider
+new file mode 100644
+index 0000000000000000000000000000000000000000..2f0b1f0ed9ca3605cd24a75466973e1a0a745ee5
+--- /dev/null
++++ b/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.CommandBuilderProvider
+@@ -0,0 +1 @@
++io.papermc.paper.command.brigadier.CommandBuilderImpl$ProviderImpl
+diff --git a/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.MessageComponentSerializer b/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.MessageComponentSerializer
+new file mode 100644
+index 0000000000000000000000000000000000000000..2428b577b9bf0eac6947f5d919cbb51f7aca3d50
+--- /dev/null
++++ b/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.MessageComponentSerializer
+@@ -0,0 +1 @@
++io.papermc.paper.command.brigadier.MessageComponentSerializerImpl
+diff --git a/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.argument.VanillaArgumentProvider b/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.argument.VanillaArgumentProvider
+new file mode 100644
+index 0000000000000000000000000000000000000000..b2fdb8351c2abb55283850a929d2a87aa6ecb80f
+--- /dev/null
++++ b/src/main/resources/META-INF/services/io.papermc.paper.command.brigadier.argument.VanillaArgumentProvider
+@@ -0,0 +1 @@
++io.papermc.paper.command.brigadier.argument.VanillaArgumentProviderImpl
+diff --git a/src/test/java/io/papermc/paper/command/brigadier/BukkitCommandConversionTest.java b/src/test/java/io/papermc/paper/command/brigadier/BukkitCommandConversionTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..6475510ea1084a003fb2c8645cb4538b3f48e1c7
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/command/brigadier/BukkitCommandConversionTest.java
+@@ -0,0 +1,113 @@
++package io.papermc.paper.command.brigadier;
++
++import com.mojang.brigadier.CommandDispatcher;
++import com.mojang.brigadier.ResultConsumer;
++import com.mojang.brigadier.context.CommandContext;
++import com.mojang.brigadier.exceptions.CommandSyntaxException;
++import com.mojang.brigadier.suggestion.Suggestions;
++import com.mojang.brigadier.tree.CommandNode;
++import io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.world.flag.FeatureFlags;
++import org.apache.logging.log4j.core.util.Assert;
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.World;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandMap;
++import org.bukkit.command.CommandSender;
++import org.bukkit.command.SimpleCommandMap;
++import org.bukkit.craftbukkit.command.CraftCommandMap;
++import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
++import org.bukkit.entity.Entity;
++import org.bukkit.plugin.PluginManager;
++import org.bukkit.support.AbstractTestingBase;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++import org.junit.jupiter.api.Assertions;
++import org.junit.jupiter.api.Test;
++import org.mockito.Mockito;
++
++import java.util.List;
++import java.util.Map;
++import java.util.logging.Logger;
++
++public class BukkitCommandConversionTest extends AbstractTestingBase {
++
++ private CommandSender getSender() {
++ return Mockito.mock(CommandSender.class);
++ }
++
++ @Test
++ public void test() throws CommandSyntaxException {
++ CommandSender sender = this.getSender();
++ CommandSourceStack object = Mockito.mock(CommandSourceStack.class);
++ Mockito.when(object.getLocation()).thenReturn(new Location(null, 0, 0, 0));;
++
++ CommandDispatcher dispatcher = DATA_PACK.commands.getDispatcher();
++ dispatcher.setConsumer((context, success, result) -> {});
++ CommandMap commandMap = new SimpleCommandMap(Bukkit.getServer(), new BukkitBrigForwardingMap());
++ Map<String, Command> stringCommandMap = commandMap.getKnownCommands();
++ // All commands should be mirrored -- or equal
++ int commandMapSize = stringCommandMap.values().size();
++ ExampleCommand exampleCommand = new ExampleCommand();
++
++ Assertions.assertEquals(commandMapSize, dispatcher.getRoot().getChildren().size());
++
++ // Register a new command
++ commandMap.register("test", exampleCommand);
++ Assertions.assertEquals(commandMapSize + (3 * 2), stringCommandMap.values().size()); // Make sure commands are accounted for, including those with namespaced keys
++
++ // Test Registration
++ for (String alias : exampleCommand.getAliases()) {
++ Assertions.assertEquals(stringCommandMap.get(alias), exampleCommand);
++ Assertions.assertEquals(stringCommandMap.get("test:" + alias), exampleCommand);
++ }
++ // Test command instance equality
++ Assertions.assertEquals(stringCommandMap.get(exampleCommand.getName()), exampleCommand);
++ Assertions.assertEquals(stringCommandMap.get("test:" + exampleCommand.getName()), exampleCommand);
++
++ // Test command map execution
++ commandMap.dispatch(sender, "main-example example");
++ Assertions.assertEquals(exampleCommand.invocations, 1);
++ Assertions.assertEquals(commandMap.tabComplete(sender, "main-example 1 2"), List.of("complete"));
++
++ // Test dispatcher execution
++ dispatcher.execute("main-example example", object);
++ Assertions.assertEquals(exampleCommand.invocations, 2);
++
++ dispatcher.execute("test:example2 example", object);
++ Assertions.assertEquals(exampleCommand.invocations, 3);
++
++ Suggestions suggestions = (Suggestions) dispatcher.getCompletionSuggestions(dispatcher.parse("main-example 1 2", object)).join();
++ Assertions.assertEquals(suggestions.getList().get(0).getText(), "complete");
++
++
++ // Test command map removal
++ commandMap.getKnownCommands().remove("test");
++ Assertions.assertNull(commandMap.getCommand("test"));
++ Assertions.assertNull(dispatcher.getRoot().getChild("test"));
++ }
++
++ private static class ExampleCommand extends Command {
++
++ int invocations;
++
++ protected ExampleCommand() {
++ super("main-example", "This is an example.", "", List.of("example", "example2"));
++ }
++
++ @Override
++ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
++ Assertions.assertEquals(args[0], "example");
++ this.invocations++;
++ return true;
++ }
++
++ @Override
++ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
++ Assertions.assertEquals(args.length, 2);
++ return List.of("complete");
++ }
++ }
++}
+diff --git a/src/test/java/org/bukkit/support/DummyServer.java b/src/test/java/org/bukkit/support/DummyServer.java
+index bd56792fc674c4e3606a3179ebf5a84ef0a4e35c..aded6d8b36008d87a1039e88333fa4b86077b56a 100644
+--- a/src/test/java/org/bukkit/support/DummyServer.java
++++ b/src/test/java/org/bukkit/support/DummyServer.java
+@@ -51,7 +51,7 @@ public final class DummyServer {
+ final Thread currentThread = Thread.currentThread();
+ when(instance.isPrimaryThread()).thenAnswer(ignored -> Thread.currentThread().equals(currentThread));
+
+- final org.bukkit.plugin.PluginManager pluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(instance, new org.bukkit.command.SimpleCommandMap(instance), null);
++ final org.bukkit.plugin.PluginManager pluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(instance, new org.bukkit.command.SimpleCommandMap(instance, new java.util.HashMap<>()), null); // Paper
+ when(instance.getPluginManager()).thenReturn(pluginManager);
+ when(instance.getTag(anyString(), any(org.bukkit.NamespacedKey.class), any())).thenAnswer(ignored -> new io.papermc.paper.util.EmptyTag());
+ // paper end - testing additions
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 706a83d5dd..eb689c22d8 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -33,7 +33,7 @@ if (!file(".git").exists()) {
rootProject.name = "paper"
-for (name in listOf("Paper-API", "Paper-Server", "Paper-MojangAPI")) {
+for (name in listOf("Paper-API", "Paper-Server")) {
val projName = name.lowercase(Locale.ENGLISH)
include(projName)
findProject(":$projName")!!.projectDir = file(name)
diff --git a/test-plugin/build.gradle.kts b/test-plugin/build.gradle.kts
index 3edf55f288..9f7d9da599 100644
--- a/test-plugin/build.gradle.kts
+++ b/test-plugin/build.gradle.kts
@@ -2,7 +2,6 @@ version = "1.0.0-SNAPSHOT"
dependencies {
compileOnly(project(":paper-api"))
- compileOnly(project(":paper-mojangapi"))
}
tasks.processResources {
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
index 4e68423bb7..671c37fa40 100644
--- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
+++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java
@@ -8,5 +8,8 @@ public final class TestPlugin extends JavaPlugin implements Listener {
@Override
public void onEnable() {
this.getServer().getPluginManager().registerEvents(this, this);
+
+ // io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this);
}
+
}
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
index e978b15f97..fe2b287b25 100644
--- a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
+++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java
@@ -8,6 +8,7 @@ public class TestPluginBootstrap implements PluginBootstrap {
@Override
public void bootstrap(@NotNull BootstrapContext context) {
+ // io.papermc.testplugin.brigtests.Registration.registerViaBootstrap(context);
}
}
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java
new file mode 100644
index 0000000000..cd24899f34
--- /dev/null
+++ b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java
@@ -0,0 +1,166 @@
+package io.papermc.testplugin.brigtests;
+
+import com.mojang.brigadier.Command;
+import io.papermc.paper.command.brigadier.BasicCommand;
+import io.papermc.paper.command.brigadier.CommandSourceStack;
+import io.papermc.paper.command.brigadier.Commands;
+import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
+import io.papermc.paper.command.brigadier.argument.range.DoubleRangeProvider;
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
+import io.papermc.testplugin.brigtests.example.ExampleAdminCommand;
+import io.papermc.testplugin.brigtests.example.MaterialArgumentType;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.bukkit.Material;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.defaults.BukkitCommand;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.jetbrains.annotations.NotNull;
+
+public final class Registration {
+
+ private Registration() {
+ }
+
+ public static void registerViaOnEnable(final JavaPlugin plugin) {
+ registerLegacyCommands(plugin);
+ registerViaLifecycleEvents(plugin);
+ }
+
+ private static void registerViaLifecycleEvents(final JavaPlugin plugin) {
+ final LifecycleEventManager<Plugin> lifecycleManager = plugin.getLifecycleManager();
+ lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS, event -> {
+ final Commands commands = event.registrar();
+ // ensure plugin commands override
+ commands.register(Commands.literal("tag")
+ .executes(ctx -> {
+ ctx.getSource().getSender().sendPlainMessage("overriden command");
+ return Command.SINGLE_SUCCESS;
+ })
+ .build(),
+ null,
+ Collections.emptyList()
+ );
+ });
+
+ lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> {
+ final Commands commands = event.registrar();
+ commands.register(plugin.getPluginMeta(), Commands.literal("root_command")
+ .then(Commands.literal("sub_command")
+ .requires(source -> source.getSender().hasPermission("testplugin.test"))
+ .executes(ctx -> {
+ ctx.getSource().getSender().sendPlainMessage("root_command sub_command");
+ return Command.SINGLE_SUCCESS;
+ })).build(),
+ null,
+ Collections.emptyList()
+ );
+
+ commands.register(plugin.getPluginMeta(), "example", "test", Collections.emptyList(), new BasicCommand() {
+ @Override
+ public void execute(@NotNull final CommandSourceStack commandSourceStack, final @NotNull String[] args) {
+ System.out.println(Arrays.toString(args));
+ }
+
+ @Override
+ public @NotNull Collection<String> suggest(final @NotNull CommandSourceStack commandSourceStack, final @NotNull String[] args) {
+ System.out.println(Arrays.toString(args));
+ return List.of("apple", "banana");
+ }
+ });
+
+
+ commands.register(plugin.getPluginMeta(), Commands.literal("water")
+ .requires(source -> {
+ System.out.println("isInWater check");
+ return source.getExecutor().isInWater();
+ })
+ .executes(ctx -> {
+ ctx.getSource().getExecutor().sendMessage("You are in water!");
+ return Command.SINGLE_SUCCESS;
+ }).then(Commands.literal("lava")
+ .requires(source -> {
+ System.out.println("isInLava check");
+ if (source.getExecutor() != null) {
+ return source.getExecutor().isInLava();
+ }
+ return true;
+ })
+ .executes(ctx -> {
+ ctx.getSource().getExecutor().sendMessage("You are in lava!");
+ return Command.SINGLE_SUCCESS;
+ })).build(),
+ null,
+ Collections.emptyList());
+
+
+ ExampleAdminCommand.register(plugin, commands);
+ }).priority(10));
+ }
+
+ private static void registerLegacyCommands(final JavaPlugin plugin) {
+ plugin.getServer().getCommandMap().register("fallback", new BukkitCommand("hi", "cool hi command", "<>", List.of("hialias")) {
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
+ sender.sendMessage("hi");
+ return true;
+ }
+ });
+ plugin.getServer().getCommandMap().register("fallback", new BukkitCommand("cooler-command", "cool hi command", "<>", List.of("cooler-command-alias")) {
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
+ sender.sendMessage("hi");
+ return true;
+ }
+ });
+ plugin.getServer().getCommandMap().getKnownCommands().values().removeIf((command) -> {
+ return command.getName().equals("hi");
+ });
+ }
+
+ public static void registerViaBootstrap(final BootstrapContext context) {
+ final LifecycleEventManager<BootstrapContext> lifecycleManager = context.getLifecycleManager();
+ lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS, event -> {
+ final Commands commands = event.registrar();
+ commands.register(Commands.literal("material")
+ .then(Commands.literal("item")
+ .then(Commands.argument("mat", MaterialArgumentType.item())
+ .executes(ctx -> {
+ ctx.getSource().getSender().sendPlainMessage(ctx.getArgument("mat", Material.class).name());
+ return Command.SINGLE_SUCCESS;
+ })
+ )
+ ).then(Commands.literal("block")
+ .then(Commands.argument("mat", MaterialArgumentType.block())
+ .executes(ctx -> {
+ ctx.getSource().getSender().sendPlainMessage(ctx.getArgument("mat", Material.class).name());
+ return Command.SINGLE_SUCCESS;
+ })
+ )
+ )
+ .build(),
+ null,
+ Collections.emptyList()
+ );
+ });
+
+ lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> {
+ final Commands commands = event.registrar();
+ commands.register(Commands.literal("heya")
+ .then(Commands.argument("range", ArgumentTypes.doubleRange())
+ .executes((ct) -> {
+ ct.getSource().getSender().sendPlainMessage(ct.getArgument("range", DoubleRangeProvider.class).range().toString());
+ return 1;
+ })
+ ).build(),
+ null,
+ Collections.emptyList()
+ );
+ }).priority(10));
+ }
+}
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ComponentCommandExceptionType.java b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ComponentCommandExceptionType.java
new file mode 100644
index 0000000000..7b8d9db790
--- /dev/null
+++ b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ComponentCommandExceptionType.java
@@ -0,0 +1,25 @@
+package io.papermc.testplugin.brigtests.example;
+
+import com.mojang.brigadier.ImmutableStringReader;
+import com.mojang.brigadier.Message;
+import com.mojang.brigadier.exceptions.CommandExceptionType;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import io.papermc.paper.command.brigadier.MessageComponentSerializer;
+import net.kyori.adventure.text.Component;
+
+public class ComponentCommandExceptionType implements CommandExceptionType {
+
+ private final Message message;
+
+ public ComponentCommandExceptionType(final Component message) {
+ this.message = MessageComponentSerializer.message().serialize(message);
+ }
+
+ public CommandSyntaxException create() {
+ return new CommandSyntaxException(this, this.message);
+ }
+
+ public CommandSyntaxException createWithContext(final ImmutableStringReader reader) {
+ return new CommandSyntaxException(this, this.message, reader.getString(), reader.getCursor());
+ }
+}
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ExampleAdminCommand.java b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ExampleAdminCommand.java
new file mode 100644
index 0000000000..83f1ebb93e
--- /dev/null
+++ b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/ExampleAdminCommand.java
@@ -0,0 +1,154 @@
+package io.papermc.testplugin.brigtests.example;
+
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import io.papermc.paper.command.brigadier.CommandSourceStack;
+import io.papermc.paper.command.brigadier.Commands;
+import io.papermc.paper.command.brigadier.argument.SignedMessageResolver;
+import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
+import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver;
+import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver;
+import io.papermc.paper.math.BlockPosition;
+import io.papermc.testplugin.TestPlugin;
+import net.kyori.adventure.chat.ChatType;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Bukkit;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockState;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class ExampleAdminCommand {
+
+ public static void register(JavaPlugin plugin, Commands commands) {
+ final LiteralArgumentBuilder<CommandSourceStack> adminBuilder = Commands.literal("admin")
+ .executes((ct) -> {
+ ct.getSource().getSender().sendPlainMessage("root admin");
+ return 1;
+ })
+ .then(
+ Commands.literal("tp")
+ .then(
+ Commands.argument("player", ArgumentTypes.player()).executes((source) -> {
+ CommandSourceStack sourceStack = source.getSource();
+ Player resolved = source.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(sourceStack).get(0);
+
+ if (resolved == source.getSource().getExecutor()) {
+ source.getSource().getExecutor().sendMessage(Component.text("Can't teleport to self!"));
+ return 0;
+ }
+ Entity entity = source.getSource().getExecutor();
+ if (entity != null) {
+ entity.teleport(resolved);
+ }
+
+ return 1;
+ })
+ )
+ )
+ .then(
+ Commands.literal("tp-self")
+ .executes((cmd) -> {
+ if (cmd.getSource().getSender() instanceof Player player) {
+ player.teleport(cmd.getSource().getLocation());
+ }
+
+ return com.mojang.brigadier.Command.SINGLE_SUCCESS;
+ })
+ )
+ .then(
+ Commands.literal("broadcast")
+ .then(
+ Commands.argument("message", ArgumentTypes.component()).executes((source) -> {
+ Component message = source.getArgument("message", Component.class);
+ Bukkit.broadcast(message);
+ return 1;
+ })
+ )
+ )
+ .then(
+ Commands.literal("ice_cream").then(
+ Commands.argument("type", new IceCreamTypeArgument()).executes((context) -> {
+ IceCreamType argumentResponse = context.getArgument("type", IceCreamType.class); // Gets the raw argument
+ context.getSource().getSender().sendMessage(Component.text("You like: " + argumentResponse));
+ return 1;
+ })
+ )
+ )
+ .then(
+ Commands.literal("execute")
+ .redirect(commands.getDispatcher().getRoot().getChild("execute"))
+ )
+ .then(
+ Commands.literal("signed_message").then(
+ Commands.argument("msg", ArgumentTypes.signedMessage()).executes((context) -> {
+ SignedMessageResolver argumentResponse = context.getArgument("msg", SignedMessageResolver.class); // Gets the raw argument
+
+ // This is a better way of getting signed messages, includes the concept of "disguised" messages.
+ argumentResponse.resolveSignedMessage("msg", context)
+ .thenAccept((signedMsg) -> {
+ context.getSource().getSender().sendMessage(signedMsg, ChatType.SAY_COMMAND.bind(Component.text("STATIC")));
+ });
+
+ return 1;
+ })
+ )
+ )
+ .then(
+ Commands.literal("setblock").then(
+ Commands.argument("block", ArgumentTypes.blockState())
+ .then(Commands.argument("pos", ArgumentTypes.blockPosition())
+ .executes((context) -> {
+ CommandSourceStack sourceStack = context.getSource();
+ BlockPosition position = context.getArgument("pos", BlockPositionResolver.class).resolve(sourceStack);
+ BlockState state = context.getArgument("block", BlockState.class);
+
+ // TODO: better block state api here? :thinking:
+ Block block = context.getSource().getLocation().getWorld().getBlockAt(position.blockX(), position.blockY(), position.blockZ());
+ block.setType(state.getType());
+ block.setBlockData(state.getBlockData());
+
+ return 1;
+ })
+ )
+ )
+ );
+ commands.register(plugin.getPluginMeta(), adminBuilder.build(), "Cool command showcasing what you can do!", List.of("alias_for_admin_that_you_shouldnt_use", "a"));
+
+
+ Bukkit.getCommandMap().register(
+ "legacy",
+ new Command("legacy_command") {
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
+ return List.of(String.join(" ", args));
+ }
+ }
+ );
+
+ Bukkit.getCommandMap().register(
+ "legacy",
+ new Command("legacy_fail") {
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
+ return false;
+ }
+
+ @Override
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
+ return List.of(String.join(" ", args));
+ }
+ }
+ );
+ }
+}
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamType.java b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamType.java
new file mode 100644
index 0000000000..cf63058fb9
--- /dev/null
+++ b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamType.java
@@ -0,0 +1,9 @@
+package io.papermc.testplugin.brigtests.example;
+
+public enum IceCreamType {
+ VANILLA,
+ CHOCOLATE,
+ BLUE_MOON,
+ STRAWBERRY,
+ WHOLE_MILK
+}
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamTypeArgument.java b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamTypeArgument.java
new file mode 100644
index 0000000000..68df9e65a3
--- /dev/null
+++ b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/IceCreamTypeArgument.java
@@ -0,0 +1,47 @@
+package io.papermc.testplugin.brigtests.example;
+
+import com.mojang.brigadier.Message;
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import io.papermc.paper.command.brigadier.MessageComponentSerializer;
+import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.CompletableFuture;
+
+public class IceCreamTypeArgument implements CustomArgumentType.Converted<IceCreamType, String> {
+
+ @Override
+ public @NotNull IceCreamType convert(String nativeType) throws CommandSyntaxException {
+ try {
+ return IceCreamType.valueOf(nativeType.toUpperCase());
+ } catch (Exception e) {
+ Message message = MessageComponentSerializer.message().serialize(Component.text("Invalid species %s!".formatted(nativeType), NamedTextColor.RED));
+
+ throw new CommandSyntaxException(new SimpleCommandExceptionType(message), message);
+ }
+ }
+
+ @Override
+ public @NotNull ArgumentType<String> getNativeType() {
+ return StringArgumentType.word();
+ }
+
+ @Override
+ public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
+ for (IceCreamType species : IceCreamType.values()) {
+ builder.suggest(species.name(), MessageComponentSerializer.message().serialize(Component.text("COOL! TOOLTIP!", NamedTextColor.GREEN)));
+ }
+
+ return CompletableFuture.completedFuture(
+ builder.build()
+ );
+ }
+}
diff --git a/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/MaterialArgumentType.java b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/MaterialArgumentType.java
new file mode 100644
index 0000000000..381be0e65b
--- /dev/null
+++ b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/example/MaterialArgumentType.java
@@ -0,0 +1,88 @@
+package io.papermc.testplugin.brigtests.example;
+
+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 io.papermc.paper.command.brigadier.argument.CustomArgumentType;
+import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.bukkit.Keyed;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.jetbrains.annotations.NotNull;
+
+import static net.kyori.adventure.text.Component.translatable;
+
+public class MaterialArgumentType implements CustomArgumentType.Converted<Material, NamespacedKey> {
+
+ private static final ComponentCommandExceptionType ERROR_INVALID = new ComponentCommandExceptionType(translatable("argument.id.invalid"));
+
+ private final Predicate<Material> check;
+
+ private MaterialArgumentType(Predicate<Material> check) {
+ this.check = check;
+ }
+
+ public static MaterialArgumentType item() {
+ return new MaterialArgumentType(Material::isItem);
+ }
+
+ public static MaterialArgumentType block() {
+ return new MaterialArgumentType(Material::isBlock);
+ }
+
+ @Override
+ public @NotNull Material convert(final @NotNull NamespacedKey nativeType) throws CommandSyntaxException {
+ final Material material = Registry.MATERIAL.get(nativeType);
+ if (material == null) {
+ throw ERROR_INVALID.create();
+ }
+ if (!this.check.test(material)) {
+ throw ERROR_INVALID.create();
+ }
+ return material;
+ }
+
+ static boolean matchesSubStr(String remaining, String candidate) {
+ for(int i = 0; !candidate.startsWith(remaining, i); ++i) {
+ i = candidate.indexOf('_', i);
+ if (i < 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public @NotNull ArgumentType<NamespacedKey> getNativeType() {
+ return ArgumentTypes.namespacedKey();
+ }
+
+ @Override
+ public @NotNull <S> CompletableFuture<Suggestions> listSuggestions(final @NotNull CommandContext<S> context, final @NotNull SuggestionsBuilder builder) {
+ final Stream<Material> stream = StreamSupport.stream(Registry.MATERIAL.spliterator(), false);
+ final String remaining = builder.getRemaining();
+ boolean containsColon = remaining.indexOf(':') > -1;
+ stream.filter(this.check)
+ .map(Keyed::key)
+ .forEach(key -> {
+ final String keyAsString = key.asString();
+ if (containsColon) {
+ if (matchesSubStr(remaining, keyAsString)) {
+ builder.suggest(keyAsString);
+ }
+ } else if (matchesSubStr(remaining, key.namespace()) || "minecraft".equals(key.namespace()) && matchesSubStr(remaining, key.value())) {
+ builder.suggest(keyAsString);
+ }
+ });
+ return builder.buildFuture();
+ }
+
+}