Paper/patches/server/0956-Brigadier-based-command-API.patch
2024-10-31 22:30:18 +01:00

2820 lines
131 KiB
Diff

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 fe9f638db3525893beed565ef9b7ac2fc76318bd..b1571c162fd8ab7239a7f4aafea5187feb694761 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) {
@@ -478,13 +497,7 @@ public class Commands {
private void sendAsync(ServerPlayer player) {
// 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);
@@ -518,6 +531,7 @@ public class Commands {
}
private void fillUsableCommands(CommandNode<CommandSourceStack> tree, CommandNode<SharedSuggestionProvider> result, CommandSourceStack source, Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> resultNodes) {
+ resultNodes.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains( ":" )); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
Iterator iterator = tree.getChildren().iterator();
while (iterator.hasNext()) {
@@ -531,6 +545,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 95786d2e1a8ac0fdbe8d449b3f100ac0512ee21e..b13b3991292ab96542ba390f3e8e3ff0d7529c44 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 c4ffa8519b520e0793af90e149518951d7ffb65b..688916c8fef40d4c81379ad38609a97993b4b702 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -2430,30 +2430,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 6136037d3d096300d93b9710dd854224b30e0738..694eacb7d3ffd28fe7684139554113e58be1ebfa 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;
@@ -407,6 +407,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());
@@ -605,48 +611,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) {
@@ -1033,17 +1002,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;
}
@@ -1052,7 +1035,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
@@ -1107,8 +1090,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);
@@ -1148,6 +1132,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 a35e2d2f53e8308d51e5a07b34c56d05a707bc14..945878e989f136ac516eb1c539c0626547c465fb 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
@@ -23,14 +23,26 @@ import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.craftbukkit.entity.CraftMinecartCommand;
import org.bukkit.entity.minecart.CommandMinecart;
-public final class VanillaCommandWrapper extends BukkitCommand {
+public class VanillaCommandWrapper extends BukkitCommand { // Paper
- private final Commands dispatcher;
+ //private final Commands dispatcher; // Paper
public final CommandNode<CommandSourceStack> vanillaCommand;
+ // Paper start
+ public VanillaCommandWrapper(String name, String description, String usageMessage, List<String> aliases, CommandNode<CommandSourceStack> vanillaCommand) {
+ super(name, description, usageMessage, aliases);
+ //this.dispatcher = dispatcher; // Paper
+ this.vanillaCommand = vanillaCommand;
+ }
+
+ Commands commands() {
+ return net.minecraft.server.MinecraftServer.getServer().getCommands();
+ }
+
+ // Paper end
public VanillaCommandWrapper(Commands dispatcher, CommandNode<CommandSourceStack> vanillaCommand) {
super(vanillaCommand.getName(), "A Mojang provided command.", vanillaCommand.getUsageText(), Collections.EMPTY_LIST);
- this.dispatcher = dispatcher;
+ // this.dispatcher = dispatcher; // Paper
this.vanillaCommand = vanillaCommand;
this.setPermission(VanillaCommandWrapper.getPermission(vanillaCommand));
}
@@ -40,7 +52,7 @@ public final class VanillaCommandWrapper extends BukkitCommand {
if (!this.testPermission(sender)) return true;
CommandSourceStack icommandlistener = VanillaCommandWrapper.getListener(sender);
- this.dispatcher.performPrefixedCommand(icommandlistener, this.toDispatcher(args, this.getName()), this.toDispatcher(args, commandLabel));
+ this.commands().performPrefixedCommand(icommandlistener, this.toDispatcher(args, this.getName()), this.toDispatcher(args, commandLabel)); // Paper
return true;
}
@@ -51,10 +63,10 @@ public final class VanillaCommandWrapper extends BukkitCommand {
Preconditions.checkArgument(alias != null, "Alias cannot be null");
CommandSourceStack icommandlistener = VanillaCommandWrapper.getListener(sender);
- ParseResults<CommandSourceStack> parsed = this.dispatcher.getDispatcher().parse(this.toDispatcher(args, this.getName()), icommandlistener);
+ ParseResults<CommandSourceStack> parsed = this.commands().getDispatcher().parse(this.toDispatcher(args, this.getName()), icommandlistener); // Paper
List<String> results = new ArrayList<>();
- this.dispatcher.getDispatcher().getCompletionSuggestions(parsed).thenAccept((suggestions) -> {
+ this.commands().getDispatcher().getCompletionSuggestions(parsed).thenAccept((suggestions) -> { // Paper
suggestions.getList().forEach((s) -> results.add(s.getText()));
});
@@ -111,4 +123,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