From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Mon, 1 Aug 2022 22:50:29 -0400 Subject: [PATCH] Brigadier based command API Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com> diff --git a/build.gradle.kts b/build.gradle.kts index 037b94a8b21f8f9408cd833823a1e6e48e22dcd6..8d76f0a8b6b67f0cae0b2fa23f0f79fc9fe97f30 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider { // Paper end - configure mockito agent that is needed in newer java versions 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:33.3.1-jre") api("com.google.code.gson:gson:2.11.0") @@ -106,9 +107,33 @@ sourceSets { } } // Paper end +// Paper start - brigadier API +val outgoingVariants = arrayOf("runtimeElements", "apiElements", "sourcesElements", "javadocElements") +val mainCapability = "${project.group}:${project.name}:${project.version}" +configurations { + val outgoing = outgoingVariants.map { named(it) } + for (config in outgoing) { + config { + attributes { + attribute(io.papermc.paperweight.util.mainCapabilityAttribute, mainCapability) + } + outgoing { + capability(mainCapability) + capability("io.papermc.paper:paper-mojangapi:${project.version}") + capability("com.destroystokyo.paper:paper-mojangapi:${project.version}") + } + } + } +} +// Paper end configure<PublishingExtension> { publications.create<MavenPublication>("maven") { + // Paper start - brigadier API + outgoingVariants.forEach { + suppressPomMetadataWarningsFor(it) + } + // Paper end from(components["java"]) } } diff --git a/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java b/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..03a1078446f84b998cd7fe8d64abecb2e36bab0a --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommand.java @@ -0,0 +1,16 @@ +package com.destroystokyo.paper.brigadier; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.suggestion.SuggestionProvider; + +import java.util.function.Predicate; + +/** + * Brigadier {@link Command}, {@link SuggestionProvider}, and permission checker for Bukkit {@link Command}s. + * + * @param <S> command source type + * @deprecated For removal, see {@link io.papermc.paper.command.brigadier.Commands} on how to use the new Brigadier API. + */ +@Deprecated(forRemoval = true, since = "1.20.6") +public interface BukkitBrigadierCommand <S extends BukkitBrigadierCommandSource> extends Command<S>, Predicate<S>, SuggestionProvider<S> { +} diff --git a/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java b/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java new file mode 100644 index 0000000000000000000000000000000000000000..28b44789e3be586c4b680fff56e5d2ff095f9ac2 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/brigadier/BukkitBrigadierCommandSource.java @@ -0,0 +1,25 @@ +package com.destroystokyo.paper.brigadier; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.Nullable; + +/** + * @deprecated For removal, see {@link io.papermc.paper.command.brigadier.Commands} on how to use the new Brigadier API. + */ +@Deprecated(forRemoval = true) +public interface BukkitBrigadierCommandSource { + + @Nullable + Entity getBukkitEntity(); + + @Nullable + World getBukkitWorld(); + + @Nullable + Location getBukkitLocation(); + + CommandSender getBukkitSender(); +} diff --git a/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..9e1b70d438c4341ec944503b5bbe6b1f08bc0478 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendCommandsEvent.java @@ -0,0 +1,73 @@ +package com.destroystokyo.paper.event.brigadier; + +import com.mojang.brigadier.tree.RootCommandNode; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * Fired any time a Brigadier RootCommandNode is generated for a player to inform the client of commands. + * You may manipulate this CommandNode to change what the client sees. + * + * <p>This event may fire on login, world change, and permission rebuilds, by plugin request, and potentially future means.</p> + * + * <p>This event will fire before {@link org.bukkit.event.player.PlayerCommandSendEvent}, so no filtering has been done by + * other plugins yet.</p> + * + * <p>WARNING: This event will potentially (and most likely) fire twice! Once for Async, and once again for Sync. + * It is important that you check event.isAsynchronous() and event.hasFiredAsync() to ensure you only act once. + * If for some reason we are unable to send this asynchronously in the future, only the sync method will fire.</p> + * + * <p>Your logic should look like this: + * {@code if (event.isAsynchronous() || !event.hasFiredAsync()) { // do stuff }}</p> + * + * <p>If your logic is not safe to run asynchronously, only react to the synchronous version.</p> + * + * <p>This is a draft/experimental API and is subject to change.</p> + */ +@ApiStatus.Experimental +@NullMarked +public class AsyncPlayerSendCommandsEvent<S extends CommandSourceStack> extends PlayerEvent { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + private final RootCommandNode<S> node; + private final boolean hasFiredAsync; + + @ApiStatus.Internal + public AsyncPlayerSendCommandsEvent(final Player player, final RootCommandNode<S> node, final boolean hasFiredAsync) { + super(player, !Bukkit.isPrimaryThread()); + this.node = node; + this.hasFiredAsync = hasFiredAsync; + } + + /** + * Gets the full Root Command Node being sent to the client, which is mutable. + * + * @return the root command node + */ + public RootCommandNode<S> getCommandNode() { + return this.node; + } + + /** + * Gets if this event has already fired asynchronously. + * + * @return whether this event has already fired asynchronously + */ + public boolean hasFiredAsync() { + return this.hasFiredAsync; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..faade9d35514687f21a0e8b62fa2e392d4ad238a --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/brigadier/AsyncPlayerSendSuggestionsEvent.java @@ -0,0 +1,85 @@ +package com.destroystokyo.paper.event.brigadier; + +import com.mojang.brigadier.suggestion.Suggestions; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * Called when sending {@link Suggestions} to the client. Will be called asynchronously if a plugin + * marks the {@link com.destroystokyo.paper.event.server.AsyncTabCompleteEvent} event handled asynchronously, + * otherwise called synchronously. + */ +@NullMarked +public class AsyncPlayerSendSuggestionsEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + private boolean cancelled = false; + + private Suggestions suggestions; + private final String buffer; + + @ApiStatus.Internal + public AsyncPlayerSendSuggestionsEvent(final Player player, final Suggestions suggestions, final String buffer) { + super(player, !Bukkit.isPrimaryThread()); + this.suggestions = suggestions; + this.buffer = buffer; + } + + /** + * Gets the input buffer sent to request these suggestions. + * + * @return the input buffer + */ + public String getBuffer() { + return this.buffer; + } + + /** + * Gets the suggestions to be sent to client. + * + * @return the suggestions + */ + public Suggestions getSuggestions() { + return this.suggestions; + } + + /** + * Sets the suggestions to be sent to client. + * + * @param suggestions suggestions + */ + public void setSuggestions(final Suggestions suggestions) { + this.suggestions = suggestions; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * Cancels sending suggestions to the client. + * {@inheritDoc} + */ + @Override + public void setCancelled(final boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java b/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..acc2bd2ec56e64b9d4bd8677d99448a97ecb5201 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/brigadier/CommandRegisteredEvent.java @@ -0,0 +1,171 @@ +package com.destroystokyo.paper.event.brigadier; + +import com.destroystokyo.paper.brigadier.BukkitBrigadierCommand; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; +import org.bukkit.Warning; +import org.bukkit.command.Command; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.server.ServerEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Fired anytime the server synchronizes Bukkit commands to Brigadier. + * + * <p>Allows a plugin to control the command node structure for its commands. + * This is done at Plugin Enable time after commands have been registered, but may also + * run at a later point in the server lifetime due to plugins, a server reload, etc.</p> + * + * <p>This is a draft/experimental API and is subject to change.</p> + * @deprecated For removal, use the new brigadier api. + */ +@ApiStatus.Experimental +@Deprecated(since = "1.20.6") +@Warning(reason = "This event has been superseded by the Commands API and will be removed in a future release. Listen to LifecycleEvents.COMMANDS instead.", value = true) +public class CommandRegisteredEvent<S extends com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource> extends ServerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final String commandLabel; + private final Command command; + private final com.destroystokyo.paper.brigadier.BukkitBrigadierCommand<S> brigadierCommand; + private final RootCommandNode<S> root; + private final ArgumentCommandNode<S, String> defaultArgs; + private LiteralCommandNode<S> literal; + private boolean rawCommand = false; + private boolean cancelled = false; + + public CommandRegisteredEvent(String commandLabel, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand<S> brigadierCommand, Command command, RootCommandNode<S> root, LiteralCommandNode<S> literal, ArgumentCommandNode<S, String> defaultArgs) { + this.commandLabel = commandLabel; + this.brigadierCommand = brigadierCommand; + this.command = command; + this.root = root; + this.literal = literal; + this.defaultArgs = defaultArgs; + } + + /** + * Gets the command label of the {@link Command} being registered. + * + * @return the command label + */ + public String getCommandLabel() { + return this.commandLabel; + } + + /** + * Gets the {@link BukkitBrigadierCommand} for the {@link Command} being registered. This can be used + * as the {@link com.mojang.brigadier.Command command executor} or + * {@link com.mojang.brigadier.suggestion.SuggestionProvider} of a {@link com.mojang.brigadier.tree.CommandNode} + * to delegate to the {@link Command} being registered. + * + * @return the {@link BukkitBrigadierCommand} + */ + public BukkitBrigadierCommand<S> getBrigadierCommand() { + return this.brigadierCommand; + } + + /** + * Gets the {@link Command} being registered. + * + * @return the {@link Command} + */ + public Command getCommand() { + return this.command; + } + + /** + * Gets the {@link RootCommandNode} which is being registered to. + * + * @return the {@link RootCommandNode} + */ + public RootCommandNode<S> getRoot() { + return this.root; + } + + /** + * Gets the Bukkit APIs default arguments node (greedy string), for if + * you wish to reuse it. + * + * @return default arguments node + */ + public ArgumentCommandNode<S, String> getDefaultArgs() { + return this.defaultArgs; + } + + /** + * Gets the {@link LiteralCommandNode} to be registered for the {@link Command}. + * + * @return the {@link LiteralCommandNode} + */ + public LiteralCommandNode<S> getLiteral() { + return this.literal; + } + + /** + * Sets the {@link LiteralCommandNode} used to register this command. The default literal is mutable, so + * this is primarily if you want to completely replace the object. + * + * @param literal new node + */ + public void setLiteral(LiteralCommandNode<S> literal) { + this.literal = literal; + } + + /** + * Gets whether this command should is treated as "raw". + * + * @see #setRawCommand(boolean) + * @return whether this command is treated as "raw" + */ + public boolean isRawCommand() { + return this.rawCommand; + } + + /** + * Sets whether this command should be treated as "raw". + * + * <p>A "raw" command will only use the node provided by this event for + * sending the command tree to the client. For execution purposes, the default + * greedy string execution of a standard Bukkit {@link Command} is used.</p> + * + * <p>On older versions of Paper, this was the default and only behavior of this + * event.</p> + * + * @param rawCommand whether this command should be treated as "raw" + */ + public void setRawCommand(final boolean rawCommand) { + this.rawCommand = rawCommand; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * Cancels registering this command to Brigadier, but will remain in Bukkit Command Map. Can be used to hide a + * command from all players. + * + * {@inheritDoc} + */ + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java new file mode 100644 index 0000000000000000000000000000000000000000..9df87708206e26167a2c4934deff7fc6f1657106 --- /dev/null +++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadier.java @@ -0,0 +1,47 @@ +package io.papermc.paper.brigadier; + +import com.mojang.brigadier.Message; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TextComponent; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Helper methods to bridge the gaps between Brigadier and Paper-MojangAPI. + * @deprecated for removal. See {@link MessageComponentSerializer} for a direct replacement of functionality found in + * this class. + * As a general entrypoint to brigadier on paper, see {@link io.papermc.paper.command.brigadier.Commands}. + */ +@Deprecated(forRemoval = true, since = "1.20.6") +public final class PaperBrigadier { + private PaperBrigadier() { + throw new RuntimeException("PaperBrigadier is not to be instantiated!"); + } + + /** + * Create a new Brigadier {@link Message} from a {@link ComponentLike}. + * + * <p>Mostly useful for creating rich suggestion tooltips in combination with other Paper-MojangAPI APIs.</p> + * + * @param componentLike The {@link ComponentLike} to use for the {@link Message} contents + * @return A new Brigadier {@link Message} + */ + public static @NonNull Message message(final @NonNull ComponentLike componentLike) { + return MessageComponentSerializer.message().serialize(componentLike.asComponent()); + } + + /** + * Create a new {@link Component} from a Brigadier {@link Message}. + * + * <p>If the {@link Message} was created from a {@link Component}, it will simply be + * converted back, otherwise a new {@link TextComponent} will be created with the + * content of {@link Message#getString()}</p> + * + * @param message The {@link Message} to create a {@link Component} from + * @return The created {@link Component} + */ + public static @NonNull Component componentFromMessage(final @NonNull Message message) { + return MessageComponentSerializer.message().deserialize(message); + } +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java b/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..c89d6c4c38e2390cb11ffba182f8741d3726cfd1 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java @@ -0,0 +1,62 @@ +package io.papermc.paper.command.brigadier; + +import java.util.Collection; +import java.util.Collections; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Implementing this interface allows for easily creating "Bukkit-style" {@code String[] args} commands. + * The implementation handles converting the command to a representation compatible with Brigadier on registration, usually in the form of {@literal /commandlabel <greedy_string>}. + */ +@ApiStatus.Experimental +@NullMarked +@FunctionalInterface +public interface BasicCommand { + + /** + * Executes the command with the given {@link CommandSourceStack} and arguments. + * + * @param commandSourceStack the commandSourceStack of the command + * @param args the arguments of the command ignoring repeated spaces + */ + @ApiStatus.OverrideOnly + void execute(CommandSourceStack commandSourceStack, String[] args); + + /** + * Suggests possible completions for the given command {@link CommandSourceStack} and arguments. + * + * @param commandSourceStack the commandSourceStack of the command + * @param args the arguments of the command including repeated spaces + * @return a collection of suggestions + */ + @ApiStatus.OverrideOnly + default Collection<String> suggest(final CommandSourceStack commandSourceStack, final String[] args) { + return Collections.emptyList(); + } + + /** + * Checks whether a command sender can receive and run the root command. + * + * @param sender the command sender trying to execute the command + * @return whether the command sender fulfills the root command requirement + * @see #permission() + */ + @ApiStatus.OverrideOnly + default boolean canUse(final CommandSender sender) { + final String permission = this.permission(); + return permission == null || sender.hasPermission(permission); + } + + /** + * Returns the permission for the root command used in {@link #canUse(CommandSender)} by default. + * + * @return the permission for the root command used in {@link #canUse(CommandSender)} + */ + @ApiStatus.OverrideOnly + default @Nullable String permission() { + return null; + } +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/CommandRegistrationFlag.java b/src/main/java/io/papermc/paper/command/brigadier/CommandRegistrationFlag.java new file mode 100644 index 0000000000000000000000000000000000000000..7e24babf746de474c8deec4b147e22031e8dadb2 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/CommandRegistrationFlag.java @@ -0,0 +1,14 @@ +package io.papermc.paper.command.brigadier; + +import org.jetbrains.annotations.ApiStatus; + +/** + * A {@link CommandRegistrationFlag} is used in {@link Commands} registration for internal purposes. + * <p> + * A command library may use this to achieve more specific customization on how their commands are registered. + * @apiNote Stability of these flags is not promised! This api is not intended for public use. + */ +@ApiStatus.Internal +public enum CommandRegistrationFlag { + FLATTEN_ALIASES +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java b/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java new file mode 100644 index 0000000000000000000000000000000000000000..ac6f5b754a15e85ce09de4ed4cdee2044b45022c --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java @@ -0,0 +1,51 @@ +package io.papermc.paper.command.brigadier; + +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * The command source type for Brigadier commands registered using Paper API. + * <p> + * While the general use case for CommandSourceStack is similar to that of {@link CommandSender}, it provides access to + * important additional context for the command execution. + * Specifically, commands such as {@literal /execute} may alter the location or executor of the source stack before + * passing it to another command. + * <p>The {@link CommandSender} returned by {@link #getSender()} may be a "no-op" + * instance of {@link CommandSender} in cases where the server either doesn't + * exist yet, or no specific sender is available. Methods on such a {@link CommandSender} + * will either have no effect or throw an {@link UnsupportedOperationException}.</p> + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface CommandSourceStack { + + /** + * Gets the location that this command is being executed at. + * + * @return a cloned location instance. + */ + Location getLocation(); + + /** + * Gets the command sender that executed this command. + * The sender of a command source stack is the one that initiated/triggered the execution of a command. + * It differs to {@link #getExecutor()} as the executor can be changed by a command, e.g. {@literal /execute}. + * + * @return the command sender instance + */ + CommandSender getSender(); + + /** + * Gets the entity that executes this command. + * May not always be {@link #getSender()} as the executor of a command can be changed to a different entity + * than the one that triggered the command. + * + * @return entity that executes this command + */ + @Nullable Entity getExecutor(); +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/Commands.java b/src/main/java/io/papermc/paper/command/brigadier/Commands.java new file mode 100644 index 0000000000000000000000000000000000000000..e32559772a39af781d89de101b3f7483a339e317 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/Commands.java @@ -0,0 +1,267 @@ +package io.papermc.paper.command.brigadier; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.bootstrap.PluginBootstrap; +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import io.papermc.paper.plugin.lifecycle.event.registrar.Registrar; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * The registrar for custom commands. Supports Brigadier commands and {@link BasicCommand}. + * <p> + * An example of a command being registered is below + * <pre>{@code + * class YourPluginClass extends JavaPlugin { + * + * @Override + * public void onEnable() { + * LifecycleEventManager<Plugin> manager = this.getLifecycleManager(); + * manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { + * final Commands commands = event.registrar(); + * commands.register( + * Commands.literal("new-command") + * .executes(ctx -> { + * ctx.getSource().getSender().sendPlainMessage("some message"); + * return Command.SINGLE_SUCCESS; + * }) + * .build(), + * "some bukkit help description string", + * List.of("an-alias") + * ); + * }); + * } + * } + * }</pre> + * <p> + * You can also register commands in {@link PluginBootstrap} by getting the {@link LifecycleEventManager} from + * {@link BootstrapContext}. + * Commands registered in the {@link PluginBootstrap} will be available for datapack's + * command function parsing. + * Note that commands registered via {@link PluginBootstrap} with the same literals as a vanilla command will override + * that command within all loaded datapacks. + * </p> + * <p>The {@code register} methods that <b>do not</b> have {@link PluginMeta} as a parameter will + * implicitly use the {@link PluginMeta} for the plugin that the {@link io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler} + * was registered with.</p> + * + * @see io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents#COMMANDS + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface Commands extends Registrar { + + /** + * Utility to create a literal command node builder with the correct generic. + * + * @param literal literal name + * @return a new builder instance + */ + static LiteralArgumentBuilder<CommandSourceStack> literal(final String literal) { + return LiteralArgumentBuilder.literal(literal); + } + + /** + * Utility to create a required argument builder with the correct generic. + * + * @param name the name of the argument + * @param argumentType the type of the argument + * @param <T> the generic type of the argument value + * @return a new required argument builder + */ + static <T> RequiredArgumentBuilder<CommandSourceStack, T> argument(final String name, final ArgumentType<T> argumentType) { + return RequiredArgumentBuilder.argument(name, argumentType); + } + + /** + * Gets the underlying {@link CommandDispatcher}. + * + * <p><b>Note:</b> This is a delicate API that must be used with care to ensure a consistent user experience.</p> + * + * <p>When registering commands, it should be preferred to use the {@link #register(PluginMeta, LiteralCommandNode, String, Collection) register methods} + * over directly registering to the dispatcher wherever possible. + * {@link #register(PluginMeta, LiteralCommandNode, String, Collection) Register methods} automatically handle + * command namespacing, command help, plugin association with commands, and more.</p> + * + * <p>Example use cases for this method <b>may</b> include: + * <ul> + * <li>Implementing integration between an external command framework and Paper (although {@link #register(PluginMeta, LiteralCommandNode, String, Collection) register methods} should still be preferred where possible)</li> + * <li>Registering new child nodes to an existing plugin command (for example an "addon" plugin to another plugin may want to do this)</li> + * <li>Retrieving existing command nodes to build redirects</li> + * </ul> + * + * @return the dispatcher instance + */ + @ApiStatus.Experimental + CommandDispatcher<CommandSourceStack> getDispatcher(); + + /** + * Registers a command for the current plugin context. + * + * <p>Commands have certain overriding behavior: + * <ul> + * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> + * <li>The main command/namespaced label will override already existing commands</li> + * </ul> + * + * @param node the built literal command node + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + default @Unmodifiable Set<String> register(final LiteralCommandNode<CommandSourceStack> node) { + return this.register(node, null, Collections.emptyList()); + } + + /** + * Registers a command for the current plugin context. + * + * <p>Commands have certain overriding behavior: + * <ul> + * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> + * <li>The main command/namespaced label will override already existing commands</li> + * </ul> + * + * @param node the built literal command node + * @param description the help description for the root literal node + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + default @Unmodifiable Set<String> register(final LiteralCommandNode<CommandSourceStack> node, final @Nullable String description) { + return this.register(node, description, Collections.emptyList()); + } + + /** + * Registers a command for the current plugin context. + * + * <p>Commands have certain overriding behavior: + * <ul> + * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> + * <li>The main command/namespaced label will override already existing commands</li> + * </ul> + * + * @param node the built literal command node + * @param aliases a collection of aliases to register the literal node's command to + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + default @Unmodifiable Set<String> register(final LiteralCommandNode<CommandSourceStack> node, final Collection<String> aliases) { + return this.register(node, null, aliases); + } + + /** + * Registers a command for the current plugin context. + * + * <p>Commands have certain overriding behavior: + * <ul> + * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> + * <li>The main command/namespaced label will override already existing commands</li> + * </ul> + * + * @param node the built literal command node + * @param description the help description for the root literal node + * @param aliases a collection of aliases to register the literal node's command to + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + @Unmodifiable Set<String> register(LiteralCommandNode<CommandSourceStack> node, @Nullable String description, Collection<String> aliases); + + /** + * Registers a command for a plugin. + * + * <p>Commands have certain overriding behavior: + * <ul> + * <li>Aliases will not override already existing commands (excluding namespaced ones)</li> + * <li>The main command/namespaced label will override already existing commands</li> + * </ul> + * + * @param pluginMeta the owning plugin's meta + * @param node the built literal command node + * @param description the help description for the root literal node + * @param aliases a collection of aliases to register the literal node's command to + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + @Unmodifiable Set<String> register(PluginMeta pluginMeta, LiteralCommandNode<CommandSourceStack> node, @Nullable String description, Collection<String> aliases); + + /** + * This allows configuring the registration of your command, which is not intended for public use. + * See {@link Commands#register(PluginMeta, LiteralCommandNode, String, Collection)} for more information. + * + * @param pluginMeta the owning plugin's meta + * @param node the built literal command node + * @param description the help description for the root literal node + * @param aliases a collection of aliases to register the literal node's command to + * @param flags a collection of registration flags that control registration behaviour. + * @return successfully registered root command labels (including aliases and namespaced variants) + * + * @apiNote This method is not guaranteed to be stable as it is not intended for public use. + * See {@link CommandRegistrationFlag} for a more indepth explanation of this method's use-case. + */ + @ApiStatus.Internal + @Unmodifiable Set<String> registerWithFlags(PluginMeta pluginMeta, LiteralCommandNode<CommandSourceStack> node, @Nullable String description, Collection<String> aliases, Set<CommandRegistrationFlag> flags); + + /** + * Registers a command under the same logic as {@link Commands#register(LiteralCommandNode, String, Collection)}. + * + * @param label the label of the to-be-registered command + * @param basicCommand the basic command instance to register + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + default @Unmodifiable Set<String> register(final String label, final BasicCommand basicCommand) { + return this.register(label, null, Collections.emptyList(), basicCommand); + } + + /** + * Registers a command under the same logic as {@link Commands#register(LiteralCommandNode, String, Collection)}. + * + * @param label the label of the to-be-registered command + * @param description the help description for the root literal node + * @param basicCommand the basic command instance to register + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + default @Unmodifiable Set<String> register(final String label, final @Nullable String description, final BasicCommand basicCommand) { + return this.register(label, description, Collections.emptyList(), basicCommand); + } + + /** + * Registers a command under the same logic as {@link Commands#register(LiteralCommandNode, String, Collection)}. + * + * @param label the label of the to-be-registered command + * @param aliases a collection of aliases to register the basic command under. + * @param basicCommand the basic command instance to register + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + default @Unmodifiable Set<String> register(final String label, final Collection<String> aliases, final BasicCommand basicCommand) { + return this.register(label, null, aliases, basicCommand); + } + + /** + * Registers a command under the same logic as {@link Commands#register(LiteralCommandNode, String, Collection)}. + * + * @param label the label of the to-be-registered command + * @param description the help description for the root literal node + * @param aliases a collection of aliases to register the basic command under. + * @param basicCommand the basic command instance to register + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + @Unmodifiable Set<String> register(String label, @Nullable String description, Collection<String> aliases, BasicCommand basicCommand); + + /** + * Registers a command under the same logic as {@link Commands#register(PluginMeta, LiteralCommandNode, String, Collection)}. + * + * @param pluginMeta the owning plugin's meta + * @param label the label of the to-be-registered command + * @param description the help description for the root literal node + * @param aliases a collection of aliases to register the basic command under. + * @param basicCommand the basic command instance to register + * @return successfully registered root command labels (including aliases and namespaced variants) + */ + @Unmodifiable Set<String> register(PluginMeta pluginMeta, String label, @Nullable String description, Collection<String> aliases, BasicCommand basicCommand); +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializer.java b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..19f3dc12426be09613a13b5889f77627a81305f4 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializer.java @@ -0,0 +1,25 @@ +package io.papermc.paper.command.brigadier; + +import com.mojang.brigadier.Message; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * A component serializer for converting between {@link Message} and {@link Component}. + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface MessageComponentSerializer extends ComponentSerializer<Component, Component, Message> { + + /** + * A component serializer for converting between {@link Message} and {@link Component}. + * + * @return serializer instance + */ + static MessageComponentSerializer message() { + return MessageComponentSerializerHolder.PROVIDER.orElseThrow(); + } +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerHolder.java b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..2db12952461c92a64505d6646f6f49f824e83050 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerHolder.java @@ -0,0 +1,12 @@ +package io.papermc.paper.command.brigadier; + +import java.util.Optional; +import java.util.ServiceLoader; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +final class MessageComponentSerializerHolder { + + static final Optional<MessageComponentSerializer> PROVIDER = ServiceLoader.load(MessageComponentSerializer.class) + .findFirst(); +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/ArgumentTypes.java b/src/main/java/io/papermc/paper/command/brigadier/argument/ArgumentTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..9abb9ff33672036bb548c688c5661dc8f237aae2 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/ArgumentTypes.java @@ -0,0 +1,371 @@ +package io.papermc.paper.command.brigadier.argument; + +import com.mojang.brigadier.arguments.ArgumentType; +import io.papermc.paper.command.brigadier.argument.predicate.ItemStackPredicate; +import io.papermc.paper.command.brigadier.argument.range.DoubleRangeProvider; +import io.papermc.paper.command.brigadier.argument.range.IntegerRangeProvider; +import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver; +import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver; +import io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; +import io.papermc.paper.entity.LookAnchor; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import java.util.UUID; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import org.bukkit.GameMode; +import org.bukkit.HeightMap; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.block.BlockState; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scoreboard.Criteria; +import org.bukkit.scoreboard.DisplaySlot; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +import static io.papermc.paper.command.brigadier.argument.VanillaArgumentProvider.provider; + +/** + * Vanilla Minecraft includes several custom {@link ArgumentType}s that are recognized by the client. + * Many of these argument types include client-side completions and validation, and some include command signing context. + * + * <p>This class allows creating instances of these types for use in plugin commands, with friendly API result types.</p> + * + * <p>{@link CustomArgumentType} is provided for customizing parsing or result types server-side, while sending the vanilla argument type to the client.</p> + */ +@ApiStatus.Experimental +@NullMarked +public final class ArgumentTypes { + + /** + * Represents a selector that can capture any + * single entity. + * + * @return argument that takes one entity + */ + public static ArgumentType<EntitySelectorArgumentResolver> entity() { + return provider().entity(); + } + + /** + * Represents a selector that can capture multiple + * entities. + * + * @return argument that takes multiple entities + */ + public static ArgumentType<EntitySelectorArgumentResolver> entities() { + return provider().entities(); + } + + /** + * Represents a selector that can capture a + * singular player entity. + * + * @return argument that takes one player + */ + public static ArgumentType<PlayerSelectorArgumentResolver> player() { + return provider().player(); + } + + /** + * Represents a selector that can capture multiple + * player entities. + * + * @return argument that takes multiple players + */ + public static ArgumentType<PlayerSelectorArgumentResolver> players() { + return provider().players(); + } + + /** + * A selector argument that provides a list + * of player profiles. + * + * @return player profile argument + */ + public static ArgumentType<PlayerProfileListResolver> playerProfiles() { + return provider().playerProfiles(); + } + + /** + * A block position argument. + * + * @return block position argument + */ + public static ArgumentType<BlockPositionResolver> blockPosition() { + return provider().blockPosition(); + } + + /** + * A fine position argument. + * + * @return fine position argument + * @see #finePosition(boolean) to center whole numbers + */ + public static ArgumentType<FinePositionResolver> finePosition() { + return finePosition(false); + } + + /** + * A fine position argument. + * + * @param centerIntegers if whole numbers should be centered (+0.5) + * @return fine position argument + */ + public static ArgumentType<FinePositionResolver> finePosition(final boolean centerIntegers) { + return provider().finePosition(centerIntegers); + } + + /** + * A blockstate argument which will provide rich parsing for specifying + * the specific block variant and then the block entity NBT if applicable. + * + * @return argument + */ + public static ArgumentType<BlockState> blockState() { + return provider().blockState(); + } + + /** + * An ItemStack argument which provides rich parsing for + * specifying item material and item NBT information. + * + * @return argument + */ + public static ArgumentType<ItemStack> itemStack() { + return provider().itemStack(); + } + + /** + * An item predicate argument. + * + * @return argument + */ + public static ArgumentType<ItemStackPredicate> itemPredicate() { + return provider().itemStackPredicate(); + } + + /** + * An argument for parsing {@link NamedTextColor}s. + * + * @return argument + */ + public static ArgumentType<NamedTextColor> namedColor() { + return provider().namedColor(); + } + + /** + * A component argument. + * + * @return argument + */ + public static ArgumentType<Component> component() { + return provider().component(); + } + + /** + * A style argument. + * + * @return argument + */ + public static ArgumentType<Style> style() { + return provider().style(); + } + + /** + * A signed message argument. + * This argument can be resolved to retrieve the underlying + * signed message. + * + * @return argument + */ + public static ArgumentType<SignedMessageResolver> signedMessage() { + return provider().signedMessage(); + } + + /** + * A scoreboard display slot argument. + * + * @return argument + */ + public static ArgumentType<DisplaySlot> scoreboardDisplaySlot() { + return provider().scoreboardDisplaySlot(); + } + + /** + * A namespaced key argument. + * + * @return argument + */ + public static ArgumentType<NamespacedKey> namespacedKey() { + return provider().namespacedKey(); + } + + /** + * A key argument. + * + * @return argument + */ + // include both key types as we are slowly moving to use adventure's key + public static ArgumentType<Key> key() { + return provider().key(); + } + + /** + * An inclusive range of integers that may be unbounded on either end. + * + * @return argument + */ + public static ArgumentType<IntegerRangeProvider> integerRange() { + return provider().integerRange(); + } + + /** + * An inclusive range of doubles that may be unbounded on either end. + * + * @return argument + */ + public static ArgumentType<DoubleRangeProvider> doubleRange() { + return provider().doubleRange(); + } + + /** + * A world argument. + * + * @return argument + */ + public static ArgumentType<World> world() { + return provider().world(); + } + + /** + * A game mode argument. + * + * @return argument + */ + public static ArgumentType<GameMode> gameMode() { + return provider().gameMode(); + } + + /** + * An argument for getting a heightmap type. + * + * @return argument + */ + public static ArgumentType<HeightMap> heightMap() { + return provider().heightMap(); + } + + /** + * A uuid argument. + * + * @return argument + */ + public static ArgumentType<UUID> uuid() { + return provider().uuid(); + } + + /** + * An objective criteria argument + * + * @return argument + */ + public static ArgumentType<Criteria> objectiveCriteria() { + return provider().objectiveCriteria(); + } + + /** + * An entity anchor argument. + * + * @return argument + */ + public static ArgumentType<LookAnchor> entityAnchor() { + return provider().entityAnchor(); + } + + /** + * A time argument, returning the number of ticks. + * <p>Examples: + * <ul> + * <li> "1d" + * <li> "5s" + * <li> "2" + * <li> "6t" + * </ul> + * + * @return argument + */ + public static ArgumentType<Integer> time() { + return time(0); + } + + /** + * A time argument, returning the number of ticks. + * <p>Examples: + * <ul> + * <li> "1d" + * <li> "5s" + * <li> "2" + * <li> "6t" + * </ul> + * + * @param mintime The minimum time required for this argument. + * @return argument + */ + public static ArgumentType<Integer> time(final int mintime) { + return provider().time(mintime); + } + + /** + * A template mirror argument + * + * @return argument + * @see Mirror + */ + public static ArgumentType<Mirror> templateMirror() { + return provider().templateMirror(); + } + + /** + * A template rotation argument. + * + * @return argument + * @see StructureRotation + */ + public static ArgumentType<StructureRotation> templateRotation() { + return provider().templateRotation(); + } + + /** + * An argument for a resource in a {@link org.bukkit.Registry}. + * + * @param registryKey the registry's key + * @return argument + * @param <T> the registry value type + */ + public static <T> ArgumentType<T> resource(final RegistryKey<T> registryKey) { + return provider().resource(registryKey); + } + + /** + * An argument for a typed key for a {@link org.bukkit.Registry}. + * + * @param registryKey the registry's key + * @return argument + * @param <T> the registry value type + * @see RegistryArgumentExtractor#getTypedKey(com.mojang.brigadier.context.CommandContext, RegistryKey, String) + */ + public static <T> ArgumentType<TypedKey<T>> resourceKey(final RegistryKey<T> registryKey) { + return provider().resourceKey(registryKey); + } + + private ArgumentTypes() { + } +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java b/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java new file mode 100644 index 0000000000000000000000000000000000000000..91d40ef0bdbdee3609e33577782c5cce29deda6a --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/CustomArgumentType.java @@ -0,0 +1,107 @@ +package io.papermc.paper.command.brigadier.argument; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * An argument type that wraps around a native-to-vanilla argument type. + * This argument receives special handling in that the native argument type will + * be sent to the client for possible client-side completions and syntax validation. + * <p> + * When implementing this class, you have to create your own parsing logic from a + * {@link StringReader}. If only want to convert from the native type ({@code N}) to the custom + * type ({@code T}), implement {@link Converted} instead. + * + * @param <T> custom type + * @param <N> type with an argument native to vanilla Minecraft (from {@link ArgumentTypes}) + */ +@ApiStatus.Experimental +@NullMarked +public interface CustomArgumentType<T, N> extends ArgumentType<T> { + + /** + * Parses the argument into the custom type ({@code T}). Keep in mind + * that this parsing will be done on the server. This means that if + * you throw a {@link CommandSyntaxException} during parsing, this + * will only show up to the user after the user has executed the command + * not while they are still entering it. + * + * @param reader string reader input + * @return parsed value + * @throws CommandSyntaxException if an error occurs while parsing + */ + @Override + T parse(final StringReader reader) throws CommandSyntaxException; + + /** + * Gets the native type that this argument uses, + * the type that is sent to the client. + * + * @return native argument type + */ + ArgumentType<N> getNativeType(); + + /** + * Cannot be controlled by the server. + * Returned in cases where there are multiple arguments in the same node. + * This helps differentiate and tell the player what the possible inputs are. + * + * @return client set examples + */ + @Override + @ApiStatus.NonExtendable + default Collection<String> getExamples() { + return this.getNativeType().getExamples(); + } + + /** + * Provides a list of suggestions to show to the client. + * + * @param context command context + * @param builder suggestion builder + * @return suggestions + * @param <S> context type + */ + @Override + default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) { + return ArgumentType.super.listSuggestions(context, builder); + } + + /** + * An argument type that wraps around a native-to-vanilla argument type. + * This argument receives special handling in that the native argument type will + * be sent to the client for possible client-side completions and syntax validation. + * <p> + * The parsed native type will be converted via {@link #convert(Object)}. + * Implement {@link CustomArgumentType} if you want to handle parsing the type manually. + * + * @param <T> custom type + * @param <N> type with an argument native to vanilla Minecraft (from {@link ArgumentTypes}) + */ + @ApiStatus.Experimental + interface Converted<T, N> extends CustomArgumentType<T, N> { + + @ApiStatus.NonExtendable + @Override + default T parse(final StringReader reader) throws CommandSyntaxException { + return this.convert(this.getNativeType().parse(reader)); + } + + /** + * Converts the value from the native type to the custom argument type. + * + * @param nativeType native argument provided value + * @return converted value + * @throws CommandSyntaxException if an exception occurs while parsing + */ + T convert(N nativeType) throws CommandSyntaxException; + } +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/RegistryArgumentExtractor.java b/src/main/java/io/papermc/paper/command/brigadier/argument/RegistryArgumentExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..c6123342df9610a97752030955a43df18e8d0cbd --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/RegistryArgumentExtractor.java @@ -0,0 +1,36 @@ +package io.papermc.paper.command.brigadier.argument; + +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import org.jspecify.annotations.NullMarked; + +/** + * Utilities for extracting registry-related arguments from a {@link CommandContext}. + */ +@NullMarked +public final class RegistryArgumentExtractor { + + /** + * Gets a typed key argument from a command context. + * + * @param context the command context + * @param registryKey the registry key for the typed key + * @param name the argument name + * @return the typed key argument + * @param <T> the value type + * @param <S> the sender type + * @throws IllegalArgumentException if the registry key doesn't match the typed key + */ + @SuppressWarnings("unchecked") + public static <T, S> TypedKey<T> getTypedKey(final CommandContext<S> context, final RegistryKey<T> registryKey, final String name) { + final TypedKey<T> typedKey = context.getArgument(name, TypedKey.class); + if (typedKey.registryKey().equals(registryKey)) { + return typedKey; + } + throw new IllegalArgumentException(registryKey + " is not the correct registry for " + typedKey); + } + + private RegistryArgumentExtractor() { + } +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..2f2c1729c5d9f1a6b6171c0ed5326b9b631f3c20 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/SignedMessageResolver.java @@ -0,0 +1,42 @@ +package io.papermc.paper.command.brigadier.argument; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import java.util.concurrent.CompletableFuture; +import net.kyori.adventure.chat.SignedMessage; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * A resolver for a {@link SignedMessage} + * + * @see ArgumentTypes#signedMessage() + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface SignedMessageResolver { + + /** + * Gets the string content of the message + * + * @return string content + */ + String content(); + + /** + * Resolves this signed message. This will the {@link CommandContext} + * and signed arguments sent by the client. + * <p> + * In the case that signed message information isn't provided, a "system" + * signed message will be returned instead. + * + * @param argumentName argument name + * @param context the command context + * @return a completable future for the {@link SignedMessage} + * @throws CommandSyntaxException syntax exception + */ + CompletableFuture<SignedMessage> resolveSignedMessage(String argumentName, CommandContext<CommandSourceStack> context) throws CommandSyntaxException; + +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProvider.java b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..4f640bd3e536fb79db54dcedd5807e7de402acef --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProvider.java @@ -0,0 +1,106 @@ +package io.papermc.paper.command.brigadier.argument; + +import com.mojang.brigadier.arguments.ArgumentType; +import io.papermc.paper.command.brigadier.argument.predicate.ItemStackPredicate; +import io.papermc.paper.command.brigadier.argument.range.DoubleRangeProvider; +import io.papermc.paper.command.brigadier.argument.range.IntegerRangeProvider; +import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver; +import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver; +import io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; +import io.papermc.paper.entity.LookAnchor; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.UUID; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import org.bukkit.GameMode; +import org.bukkit.HeightMap; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.block.BlockState; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scoreboard.Criteria; +import org.bukkit.scoreboard.DisplaySlot; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +@ApiStatus.Internal +@NullMarked +interface VanillaArgumentProvider { + + Optional<VanillaArgumentProvider> PROVIDER = ServiceLoader.load(VanillaArgumentProvider.class) + .findFirst(); + + static VanillaArgumentProvider provider() { + return PROVIDER.orElseThrow(); + } + + ArgumentType<EntitySelectorArgumentResolver> entity(); + + ArgumentType<PlayerSelectorArgumentResolver> player(); + + ArgumentType<EntitySelectorArgumentResolver> entities(); + + ArgumentType<PlayerSelectorArgumentResolver> players(); + + ArgumentType<PlayerProfileListResolver> playerProfiles(); + + ArgumentType<BlockPositionResolver> blockPosition(); + + ArgumentType<FinePositionResolver> finePosition(boolean centerIntegers); + + ArgumentType<BlockState> blockState(); + + ArgumentType<ItemStack> itemStack(); + + ArgumentType<ItemStackPredicate> itemStackPredicate(); + + ArgumentType<NamedTextColor> namedColor(); + + ArgumentType<Component> component(); + + ArgumentType<Style> style(); + + ArgumentType<SignedMessageResolver> signedMessage(); + + ArgumentType<DisplaySlot> scoreboardDisplaySlot(); + + ArgumentType<NamespacedKey> namespacedKey(); + + // include both key types as we are slowly moving to use adventure's key + ArgumentType<Key> key(); + + ArgumentType<IntegerRangeProvider> integerRange(); + + ArgumentType<DoubleRangeProvider> doubleRange(); + + ArgumentType<World> world(); + + ArgumentType<GameMode> gameMode(); + + ArgumentType<HeightMap> heightMap(); + + ArgumentType<UUID> uuid(); + + ArgumentType<Criteria> objectiveCriteria(); + + ArgumentType<LookAnchor> entityAnchor(); + + ArgumentType<Integer> time(int minTicks); + + ArgumentType<Mirror> templateMirror(); + + ArgumentType<StructureRotation> templateRotation(); + + <T> ArgumentType<TypedKey<T>> resourceKey(RegistryKey<T> registryKey); + + <T> ArgumentType<T> resource(RegistryKey<T> registryKey); +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/predicate/ItemStackPredicate.java b/src/main/java/io/papermc/paper/command/brigadier/argument/predicate/ItemStackPredicate.java new file mode 100644 index 0000000000000000000000000000000000000000..ba0cfb3c53f6a5a29b1719ed271a8f13d5f52f24 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/predicate/ItemStackPredicate.java @@ -0,0 +1,15 @@ +package io.papermc.paper.command.brigadier.argument.predicate; + +import java.util.function.Predicate; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; + +/** + * A predicate for ItemStack. + * + * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#itemPredicate() + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +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() + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +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() + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public non-sealed interface IntegerRangeProvider extends RangeProvider<Integer> { + +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/range/RangeProvider.java b/src/main/java/io/papermc/paper/command/brigadier/argument/range/RangeProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..af1c01ab0d2146225c54a7766788007314f59328 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/range/RangeProvider.java @@ -0,0 +1,22 @@ +package io.papermc.paper.command.brigadier.argument.range; + +import com.google.common.collect.Range; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * A provider for a range of numbers + * + * @param <T> + * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes + */ +@ApiStatus.Experimental +@NullMarked +public sealed interface RangeProvider<T extends Comparable<?>> permits DoubleRangeProvider, IntegerRangeProvider { + + /** + * Provides the given range. + * @return range + */ + Range<T> range(); +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/ArgumentResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/ArgumentResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..60439269d8b1535c779ae8bd008c8f28cc7e4133 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/ArgumentResolver.java @@ -0,0 +1,27 @@ +package io.papermc.paper.command.brigadier.argument.resolvers; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * An {@link ArgumentResolver} is capable of resolving + * an argument value using a {@link CommandSourceStack}. + * + * @param <T> resolved type + * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes + */ +@ApiStatus.Experimental +@NullMarked +@ApiStatus.NonExtendable +public interface ArgumentResolver<T> { + + /** + * Resolves the argument with the given + * command source stack. + * @param sourceStack source stack + * @return resolved + */ + T resolve(CommandSourceStack sourceStack) throws CommandSyntaxException; +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/BlockPositionResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/BlockPositionResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..908f40dbf3e52bdfc8577a8916884e9fa4557a7c --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/BlockPositionResolver.java @@ -0,0 +1,16 @@ +package io.papermc.paper.command.brigadier.argument.resolvers; + +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.math.BlockPosition; +import org.jetbrains.annotations.ApiStatus; + +/** + * An {@link ArgumentResolver} that's capable of resolving + * a block position argument value using a {@link CommandSourceStack}. + * + * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#blockPosition() + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface BlockPositionResolver extends ArgumentResolver<BlockPosition> { +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/FinePositionResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/FinePositionResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..e2fc26016b8d68fd0d69c8ca962f61fe65471b24 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/FinePositionResolver.java @@ -0,0 +1,17 @@ +package io.papermc.paper.command.brigadier.argument.resolvers; + +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.math.FinePosition; +import org.jetbrains.annotations.ApiStatus; + +/** + * An {@link ArgumentResolver} that's capable of resolving + * a fine position argument value using a {@link CommandSourceStack}. + * + * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#finePosition() + * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#finePosition(boolean) + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface FinePositionResolver extends ArgumentResolver<FinePosition> { +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/PlayerProfileListResolver.java b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/PlayerProfileListResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..89024e67fd81a9cd8a9d1ef5bb78d1c8bcb4fcc5 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/PlayerProfileListResolver.java @@ -0,0 +1,17 @@ +package io.papermc.paper.command.brigadier.argument.resolvers; + +import com.destroystokyo.paper.profile.PlayerProfile; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import java.util.Collection; +import org.jetbrains.annotations.ApiStatus; + +/** + * An {@link ArgumentResolver} that's capable of resolving + * argument value using a {@link CommandSourceStack}. + * + * @see io.papermc.paper.command.brigadier.argument.ArgumentTypes#playerProfiles() + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +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() + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +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() + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +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> + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface SelectorArgumentResolver<T> extends ArgumentResolver<T> { +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java index f70814de0d6c40b2c1c9921b8abdd1162e1d3995..ab6b262cf0d2d17962ed012b2ea7b8f1db8bc576 100644 --- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEvents.java @@ -1,9 +1,11 @@ package io.papermc.paper.plugin.lifecycle.event.types; +import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; +import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; @@ -17,6 +19,13 @@ import org.jspecify.annotations.NullMarked; @NullMarked public final class LifecycleEvents { + /** + * This event is for registering commands to the server's brigadier command system. You can register a handler for this event in + * {@link org.bukkit.plugin.java.JavaPlugin#onEnable()} or {@link io.papermc.paper.plugin.bootstrap.PluginBootstrap#bootstrap(BootstrapContext)}. + * @see Commands an example of a command being registered + */ + public static final LifecycleEventType.Prioritizable<LifecycleEventOwner, ReloadableRegistrarEvent<Commands>> COMMANDS = prioritized("commands", LifecycleEventOwner.class); + //<editor-fold desc="helper methods" defaultstate="collapsed"> @ApiStatus.Internal static <E extends LifecycleEvent> LifecycleEventType.Monitorable<Plugin, E> plugin(final String name) { diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java index 92b905e5da3cb8f477bb17d848c3523781545ae3..74384a56eebbce41d431db2507c55eddbcf50a41 100644 --- a/src/main/java/org/bukkit/command/Command.java +++ b/src/main/java/org/bukkit/command/Command.java @@ -520,4 +520,9 @@ public abstract class Command { public String toString() { return getClass().getName() + '(' + name + ')'; } + + // Paper start + @org.jetbrains.annotations.ApiStatus.Internal + public boolean canBeOverriden() { return false; } + // Paper end } diff --git a/src/main/java/org/bukkit/command/FormattedCommandAlias.java b/src/main/java/org/bukkit/command/FormattedCommandAlias.java index 9d4f553c04784cca63901a56a7aea62a5cae1d72..abe256e1e45ce28036da4aa1586715bc8a1a3414 100644 --- a/src/main/java/org/bukkit/command/FormattedCommandAlias.java +++ b/src/main/java/org/bukkit/command/FormattedCommandAlias.java @@ -117,7 +117,7 @@ public class FormattedCommandAlias extends Command { index = formatString.indexOf('$', index); } - return formatString; + return formatString.trim(); // Paper - Causes an extra space at the end, breaks with brig commands } @NotNull diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java index b5f9cd2bd191f8b071c6c95706ddbef97d3c244e..5df19bd701c67506689fc7f49d91f99ebfbc83f0 100644 --- a/src/main/java/org/bukkit/command/SimpleCommandMap.java +++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -23,10 +23,14 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class SimpleCommandMap implements CommandMap { - protected final Map<String, Command> knownCommands = new HashMap<String, Command>(); + protected final Map<String, Command> knownCommands; // Paper private final Server server; - public SimpleCommandMap(@NotNull final Server server) { + // Paper start + @org.jetbrains.annotations.ApiStatus.Internal + public SimpleCommandMap(@NotNull final Server server, Map<String, Command> backing) { + this.knownCommands = backing; + // Paper end this.server = server; setDefaultCommands(); } @@ -103,7 +107,10 @@ public class SimpleCommandMap implements CommandMap { */ private synchronized boolean register(@NotNull String label, @NotNull Command command, boolean isAlias, @NotNull String fallbackPrefix) { knownCommands.put(fallbackPrefix + ":" + label, command); - if ((command instanceof BukkitCommand || isAlias) && knownCommands.containsKey(label)) { + // Paper start + Command known = knownCommands.get(label); + if ((command instanceof BukkitCommand || isAlias) && (known != null && !known.canBeOverriden())) { + // Paper end // Request is for an alias/fallback command and it conflicts with // a existing command or previous alias ignore it // Note: This will mean it gets removed from the commands list of active aliases @@ -115,7 +122,9 @@ public class SimpleCommandMap implements CommandMap { // If the command exists but is an alias we overwrite it, otherwise we return Command conflict = knownCommands.get(label); if (conflict != null && conflict.getLabel().equals(label)) { + if (!conflict.canBeOverriden()) { // Paper return false; + } // Paper } if (!isAlias) { diff --git a/src/main/java/org/bukkit/command/defaults/ReloadCommand.java b/src/main/java/org/bukkit/command/defaults/ReloadCommand.java index 3ec32b46264cfff857b50129b5e0fa5584943ec6..bdfe68b386b5ca2878475e548d3c9a3808fce848 100644 --- a/src/main/java/org/bukkit/command/defaults/ReloadCommand.java +++ b/src/main/java/org/bukkit/command/defaults/ReloadCommand.java @@ -18,6 +18,9 @@ public class ReloadCommand extends BukkitCommand { this.setAliases(Arrays.asList("rl")); } + @org.jetbrains.annotations.ApiStatus.Internal // Paper + public static final String RELOADING_DISABLED_MESSAGE = "A lifecycle event handler has been registered which makes reloading plugins not possible"; // Paper + @Override public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { // Paper if (!testPermission(sender)) return true; @@ -51,7 +54,16 @@ public class ReloadCommand extends BukkitCommand { Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues when using some plugins."); Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); - Bukkit.reload(); + // Paper start - lifecycle events + try { + Bukkit.reload(); + } catch (final IllegalStateException ex) { + if (ex.getMessage().equals(RELOADING_DISABLED_MESSAGE)) { + Command.broadcastCommandMessage(sender, ChatColor.RED + RELOADING_DISABLED_MESSAGE); + return true; + } + } + // Paper end - lifecycle events Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Reload complete."); return true; diff --git a/src/main/java/org/bukkit/event/server/TabCompleteEvent.java b/src/main/java/org/bukkit/event/server/TabCompleteEvent.java index 6465e290c090d82986352d5ab7ba5dc65bd3dc17..c71c122ccc4775d030688f7b8df0b4feb49136f4 100644 --- a/src/main/java/org/bukkit/event/server/TabCompleteEvent.java +++ b/src/main/java/org/bukkit/event/server/TabCompleteEvent.java @@ -18,6 +18,8 @@ import org.jetbrains.annotations.NotNull; * themselves. Plugins wishing to remove commands from tab completion are * advised to ensure the client does not have permission for the relevant * commands, or use {@link PlayerCommandSendEvent}. + * @apiNote Only called for bukkit API commands {@link org.bukkit.command.Command} and + * {@link org.bukkit.command.CommandExecutor} and not for brigadier commands ({@link io.papermc.paper.command.brigadier.Commands}). */ public class TabCompleteEvent extends Event implements Cancellable {