From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
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

Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>

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..23525592d880f340745a28c956fa38d3e4057231
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/brigadier/ApiMirrorRootNode.java
@@ -0,0 +1,253 @@
+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.");
+        }
+
+        // Store unwrapped node before unwrapping children to avoid infinite recursion in cyclic redirects.
+        converted.wrappedCached = pureNode;
+        pureNode.unwrappedCached = converted;
+
+        /*
+        Add the children to the node, unwrapping each child in the process.
+         */
+        for (final CommandNode<CommandSourceStack> child : pureNode.getChildren()) {
+            converted.addChild(this.unwrapNode(child));
+        }
+
+        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..95d3b42cbe2184b0a04d941f27f7a6e643ef59be
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java
@@ -0,0 +1,204 @@
+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.command.brigadier.bukkit.BukkitCommandNode;
+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.ArrayUtils;
+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, boolean override) {
+        final @Nullable CommandNode<CommandSourceStack> existingChild = this.getDispatcher().getRoot().getChild(node.getLiteral());
+        if (existingChild != null && !(existingChild instanceof PluginCommandNode) && !(existingChild instanceof BukkitCommandNode)) {
+            override = true; // override vanilla commands
+        }
+        if (existingChild == null || 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)
+            .requires(stack -> basicCommand.canUse(stack.getSender()))
+            .then(
+                Commands.argument("args", StringArgumentType.greedyString())
+                    .suggests((context, suggestionsBuilder) -> {
+                        String[] args = StringUtils.split(suggestionsBuilder.getRemaining());
+                        if (suggestionsBuilder.getRemaining().endsWith(" ")) {
+                            // if there is trailing whitespace, we should add an empty argument to signify
+                            // that there may be more, but no characters have been typed yet
+                            args = ArrayUtils.add(args, "");
+                        }
+                        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..07a23be2cd7b4592108dee0ae223e71b1d00cec9
--- /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 java.util.concurrent.CompletableFuture;
+import net.kyori.adventure.chat.SignedMessage;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.arguments.MessageArgument;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public record SignedMessageResolverImpl(MessageArgument.Message message) implements SignedMessageResolver {
+
+    @Override
+    public String content() {
+        return this.message.text();
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    @Override
+    public CompletableFuture<SignedMessage> resolveSignedMessage(final String argumentName, final CommandContext erased) throws CommandSyntaxException {
+        final CompletableFuture<SignedMessage> future = new CompletableFuture<>();
+
+        final MessageArgument.Message response = ((CommandContext<CommandSourceStack>) erased).getArgument(argumentName, SignedMessageResolverImpl.class).message;
+        MessageArgument.resolveChatMessage(response, erased, 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..38fb7d13abfcb55fe4a132b9b27e0c91f8c3d891
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java
@@ -0,0 +1,366 @@
+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.FinePositionResolver;
+import io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver;
+import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver;
+import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver;
+import io.papermc.paper.entity.LookAnchor;
+import io.papermc.paper.registry.PaperRegistries;
+import io.papermc.paper.registry.RegistryAccess;
+import io.papermc.paper.registry.RegistryKey;
+import io.papermc.paper.registry.TypedKey;
+import io.papermc.paper.util.MCUtil;
+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.ResourceArgument;
+import net.minecraft.commands.arguments.ResourceKeyArgument;
+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.coordinates.Vec3Argument;
+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 net.minecraft.world.phys.Vec3;
+import org.bukkit.GameMode;
+import org.bukkit.HeightMap;
+import org.bukkit.Keyed;
+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 MCUtil.toPosition(pos);
+        });
+    }
+
+    @Override
+    public ArgumentType<FinePositionResolver> finePosition(final boolean centerIntegers) {
+        return this.wrap(Vec3Argument.vec3(centerIntegers), (result) -> sourceStack -> {
+            final Vec3 vec3 = result.getPosition((CommandSourceStack) sourceStack);
+
+            return MCUtil.toPosition(vec3);
+        });
+    }
+
+    @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);
+            final @Nullable 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()));
+    }
+
+    @Override
+    public <T> ArgumentType<TypedKey<T>> resourceKey(final RegistryKey<T> registryKey) {
+        return this.wrap(
+            ResourceKeyArgument.key(PaperRegistries.registryToNms(registryKey)),
+            nmsRegistryKey -> TypedKey.create(registryKey, CraftNamespacedKey.fromMinecraft(nmsRegistryKey.location()))
+        );
+    }
+
+    @Override
+    public <T> ArgumentType<T> resource(final RegistryKey<T> registryKey) {
+        return this.resourceRaw(registryKey);
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes", "UnnecessaryLocalVariable"})
+    private <T, K extends Keyed> ArgumentType<T> resourceRaw(final RegistryKey registryKeyRaw) { // TODO remove Keyed
+        final RegistryKey<K> registryKey = registryKeyRaw;
+        return (ArgumentType<T>) this.wrap(
+            ResourceArgument.resource(PaperCommands.INSTANCE.getBuildContext(), PaperRegistries.registryToNms(registryKey)),
+            resource -> requireNonNull(
+                RegistryAccess.registryAccess()
+                    .getRegistry(registryKey)
+                    .get(CraftNamespacedKey.fromMinecraft(resource.key().location()))
+            )
+        );
+    }
+
+    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..5eef7ae5197bd395fbd6800530ffe34d147651ff
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitBrigForwardingMap.java
@@ -0,0 +1,338 @@
+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 io.papermc.paper.command.brigadier.CommandSourceStack;
+import io.papermc.paper.command.brigadier.PaperBrigadier;
+import io.papermc.paper.command.brigadier.PaperCommands;
+import io.papermc.paper.command.brigadier.PluginVanillaCommandWrapper;
+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);
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    @Nullable
+    @Override
+    public Command put(String key, Command value) {
+        Command old = this.get(key);
+        this.getDispatcher().getRoot().removeCommand(key); // Override previous command
+        if (value instanceof PluginVanillaCommandWrapper wrapper && wrapper.getName().equals(key)) {
+            // Don't break when some plugin tries to remove and add back a plugin command registered with modern API...
+            this.getDispatcher().getRoot().addChild((CommandNode) wrapper.vanillaCommand);
+        } else {
+            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..1814cd072aaca3e72249f0509a9c3b3cb154eaba
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitCommandNode.java
@@ -0,0 +1,138 @@
+package io.papermc.paper.command.brigadier.bukkit;
+
+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 java.util.ArrayList;
+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;
+import org.bukkit.entity.Player;
+import org.bukkit.event.server.TabCompleteEvent;
+
+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();
+
+            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
+
+            // 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);
+            }
+
+            if (sender instanceof final Player player) {
+                TabCompleteEvent tabEvent = new org.bukkit.event.server.TabCompleteEvent(player, builder.getInput(), results != null ? results : new ArrayList<>(), true, pos); // Paper - AsyncTabCompleteEvent
+                if (!tabEvent.callEvent()) {
+                    results = null;
+                } else {
+                    results = tabEvent.getCompletions();
+                }
+            }
+            // 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/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 fc0c60b22844ed010aede2fa125b9fa440d3de80..3549ffea451b932602efb113844ba21a7bc72371 100644
--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java
+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java
@@ -47,8 +47,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;
@@ -172,26 +171,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
@@ -464,6 +443,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 cfa328f06c1b972c8328ff40580b485c02e0c270..62704cbfe3a232809b3e62d1f1d8beb3bea6496e 100644
--- a/src/main/java/net/minecraft/commands/Commands.java
+++ b/src/main/java/net/minecraft/commands/Commands.java
@@ -159,7 +159,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);
@@ -268,11 +268,24 @@ 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())) {
+            // The brigadier dispatcher is not able to resolve nested redirects.
+            // E.g. registering the alias minecraft:tp cannot redirect to tp, as tp itself redirects to teleport.
+            // Instead, target the first none redirecting node.
+            CommandNode<CommandSourceStack> flattenedAliasTarget = node;
+            while (flattenedAliasTarget.getRedirect() != null) flattenedAliasTarget = flattenedAliasTarget.getRedirect();
+
+            this.dispatcher.register(
+                com.mojang.brigadier.builder.LiteralArgumentBuilder.<CommandSourceStack>literal("minecraft:" + node.getName())
+                    .executes(flattenedAliasTarget.getCommand())
+                    .requires(flattenedAliasTarget.getRequirement())
+                    .redirect(flattenedAliasTarget)
+            );
+        }
+        // Paper end - Brigadier Command API
+        // Paper - remove public constructor, no longer needed
         this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
     }
 
@@ -328,6 +341,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();
 
         Profiler.get().push(() -> {
@@ -342,10 +360,11 @@ public class Commands {
                 });
             }
         } catch (Exception exception) {
+            if (throwCommandError) throw exception;
             MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage());
 
+            Commands.LOGGER.error("Command exception: /{}", s, exception); // Paper - always show execution exception in console log
             if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging
-                Commands.LOGGER.error("Command exception: /{}", s, exception);
                 StackTraceElement[] astacktraceelement = exception.getStackTrace();
 
                 for (int i = 0; i < Math.min(astacktraceelement.length, 3); ++i) {
@@ -479,13 +498,7 @@ public class Commands {
     private void sendAsync(ServerPlayer player, Collection<CommandNode<CommandSourceStack>> dispatcherRootChildren) {
         // Paper end - Perf: Async command map building
         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();
-        map.put(vanilla, vanillaRoot);
-        this.fillUsableCommands(vanilla, vanillaRoot, player.createCommandSourceStack(), (Map) map);
-
-        // Now build the global commands in a second pass
+        // Paper - brigadier API removes the need to fill the map twice
         RootCommandNode<SharedSuggestionProvider> rootcommandnode = new RootCommandNode();
 
         map.put(this.dispatcher.getRoot(), rootcommandnode);
@@ -520,6 +533,7 @@ public class Commands {
 
     // Paper start - Perf: Async command map building; pass copy of children
     private void fillUsableCommands(Collection<CommandNode<CommandSourceStack>> children, 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 = children.iterator();
         // Paper end - Perf: Async command map building
 
@@ -534,6 +548,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 55484826fc5ddd04ae024e25a0251796d7fa9c28..237e4f7b24908e9ade9a483eb7ae05fa3b7931d8 100644
--- a/src/main/java/net/minecraft/commands/arguments/MessageArgument.java
+++ b/src/main/java/net/minecraft/commands/arguments/MessageArgument.java
@@ -40,6 +40,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 60883d019ceef9e2430e9416c8b321165bba4fe6..c7f84fac5929046d06c38a7624c497dbcda2ff37 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -316,7 +316,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
     private boolean forceTicks;
     // CraftBukkit end
     // Spigot start
@@ -406,7 +406,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
         /*
@@ -691,6 +690,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();
     }
@@ -2216,9 +2218,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());
 
@@ -2232,6 +2234,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
             this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
             this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
             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 - Add ServerResourcesReloadedEvent; fire after everything has been reloaded
         }, this);
 
diff --git a/src/main/java/net/minecraft/server/ReloadableServerResources.java b/src/main/java/net/minecraft/server/ReloadableServerResources.java
index ac9a706dd92f15406299b8fc4ed567ffcc736169..47d5d5fcc8623969c6ab7c148c043bc367f1d6cf 100644
--- a/src/main/java/net/minecraft/server/ReloadableServerResources.java
+++ b/src/main/java/net/minecraft/server/ReloadableServerResources.java
@@ -39,6 +39,7 @@ public class ReloadableServerResources {
         this.postponedTags = pendingTagLoads;
         this.recipes = new RecipeManager(registries);
         this.commands = new Commands(environment, CommandBuildContext.simple(registries, enabledFeatures));
+        io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setDispatcher(this.commands, CommandBuildContext.simple(registries, enabledFeatures)); // Paper - Brigadier Command API
         this.advancements = new ServerAdvancementManager(registries);
         this.functionLibrary = new ServerFunctionLibrary(functionPermissionLevel, this.commands.getDispatcher());
     }
@@ -83,6 +84,14 @@ public class ReloadableServerResources {
                     ReloadableServerResources reloadableServerResources = new ReloadableServerResources(
                         reloadResult.layers(), reloadResult.lookupWithUpdatedTags(), enabledFeatures, environment, pendingTagLoads, 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(
                             resourceManager,
                             reloadableServerResources.listeners(),
diff --git a/src/main/java/net/minecraft/server/ServerFunctionManager.java b/src/main/java/net/minecraft/server/ServerFunctionManager.java
index 02e00819970eda49196641520870fc31d08b1a38..0b348f701b61c7b7ed0190eff8b2d73f3a3d5c74 100644
--- a/src/main/java/net/minecraft/server/ServerFunctionManager.java
+++ b/src/main/java/net/minecraft/server/ServerFunctionManager.java
@@ -37,7 +37,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 ebe6a002d883721d80cbfcc004064e8a57934a56..cce0e570c8217c8e7cc81642d303e1b96f70f4f3 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -234,7 +234,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 a13c199446423dbc807f916157bb7cac33229389..929cd59b8f27d7b708054d80e764a11d6551f060 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -2489,30 +2489,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
         }
     }
 
-    private void handleCommand(String s) {
-        org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + s); // Paper - Add async catcher
-        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()) {
-            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 {
+    @Deprecated // Paper
+    public void handleCommand(String s) { // Paper - private -> public
+        // 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 0d62694e2f9086702eaca7a11799eb90a06ce853..338b60f0254d55ac4a0645ca351d0ce736ce0681 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -275,11 +275,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;
@@ -419,6 +419,12 @@ public final class CraftServer implements Server {
         this.serverLinks = new CraftServerLinks(console);
 
         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());
 
@@ -617,48 +623,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) {
@@ -1045,17 +1014,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;
     }
@@ -1064,7 +1047,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
@@ -1119,8 +1102,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);
@@ -1160,6 +1144,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 ce8683eff5b8ade57a2fcb77027cfe4b26986bc7..68001fbc7abf4c87740ebe8e7e492fa254be0da1 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
@@ -24,14 +24,26 @@ import org.bukkit.craftbukkit.entity.CraftMinecartCommand;
 import org.bukkit.craftbukkit.entity.CraftPlayer;
 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));
     }
@@ -41,7 +53,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;
     }
 
@@ -52,10 +64,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()));
         });
 
@@ -116,4 +128,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..4b419ce023f61d5af9ff7a34e6879de1991cf4df
--- /dev/null
+++ b/src/test/java/io/papermc/paper/command/brigadier/BukkitCommandConversionTest.java
@@ -0,0 +1,102 @@
+package io.papermc.paper.command.brigadier;
+
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestions;
+import io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap;
+import java.util.List;
+import java.util.Map;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandMap;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.SimpleCommandMap;
+import org.bukkit.support.RegistryHelper;
+import org.bukkit.support.environment.AllFeatures;
+import org.bukkit.support.environment.Normal;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+@Normal
+public class BukkitCommandConversionTest {
+
+    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 = RegistryHelper.getDataPack().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/DummyServerHelper.java b/src/test/java/org/bukkit/support/DummyServerHelper.java
index 2fed099bc91a8591a2415493b333f9c18bfe35f6..55c05c16a80c489cdda2fd03c943921d38d978e9 100644
--- a/src/test/java/org/bukkit/support/DummyServerHelper.java
+++ b/src/test/java/org/bukkit/support/DummyServerHelper.java
@@ -87,7 +87,7 @@ public final class DummyServerHelper {
         // Paper start - testing additions
         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);
         when(instance.getPluginManager()).thenReturn(pluginManager);
         // Paper end - testing additions