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