2074 Zeilen
82 KiB
Diff
2074 Zeilen
82 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
|
|
Date: Mon, 1 Aug 2022 22:50: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 20ecbdd4e2f74475118769497004e93813c0a8a6..392c4bcf3a06172cdedb73250e2da617e1c6463e 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 {
|
|
|