From 0ba70dc1ad377db00e2ae1b99bbd2bc50a83b3f0 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Mon, 8 Jul 2024 23:05:29 +0200 Subject: Add builder-like registration for BasicCommand --- patches/api/0469-Brigadier-based-command-API.patch | 121 +++++++++++++++++++- .../server/0975-Brigadier-based-command-API.patch | 127 +++++++++++++++++++-- 2 files changed, 233 insertions(+), 15 deletions(-) diff --git a/patches/api/0469-Brigadier-based-command-API.patch b/patches/api/0469-Brigadier-based-command-API.patch index a47c1b82c5..1ccb383df1 100644 --- a/patches/api/0469-Brigadier-based-command-API.patch +++ b/patches/api/0469-Brigadier-based-command-API.patch @@ -502,16 +502,21 @@ index 0000000000000000000000000000000000000000..9df87708206e26167a2c4934deff7fc6 +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java b/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..0f6b921b4bcf983cf25188823f78a061eec5263d +index 0000000000000000000000000000000000000000..2722a855f16d5f5c0104854dedadea3eb54b8bad --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/BasicCommand.java -@@ -0,0 +1,36 @@ +@@ -0,0 +1,121 @@ +package io.papermc.paper.command.brigadier; + ++import io.papermc.paper.plugin.configuration.PluginMeta; +import java.util.Collection; +import java.util.Collections; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; + +/** + * Implementing this interface allows for easily creating "Bukkit-style" {@code String[] args} commands. @@ -541,6 +546,86 @@ index 0000000000000000000000000000000000000000..0f6b921b4bcf983cf25188823f78a061 + default @NotNull Collection suggest(final @NotNull CommandSourceStack commandSourceStack, final @NotNull String[] args) { + return Collections.emptyList(); + } ++ ++ /** ++ * Registration builder for {@link BasicCommand} to be used in {@link Commands#register(Consumer)}. ++ *

++ * The command handler and label always have to be specified, the rest will default to {@code null} or an empty collection. ++ * However, it is highly recommended to add a root permission/requirement, which is also used to limit the commands sent to players. ++ *

++ * {@link PluginMeta} may be automatically infered via the registration context, such as during bootstrap, ++ * but will otherwise also have to be defined. ++ */ ++ interface RegistrationBuilder { ++ ++ /** ++ * Sets the owning plugin's meta. This is not required to be set during plugin bootstrap. ++ * ++ * @param pluginMeta the owning plugin's meta ++ * @return self ++ */ ++ @NotNull RegistrationBuilder pluginMeta(@NotNull PluginMeta pluginMeta); ++ ++ /** ++ * Sets the command label. ++ * ++ * @param label the label of the to-be-registered command ++ * @return self ++ */ ++ @NotNull RegistrationBuilder label(@NotNull String label); ++ ++ /** ++ * Sets the command handler. ++ * ++ * @param basicCommand the basic command instance to register ++ * @return self ++ */ ++ @NotNull RegistrationBuilder commandHandler(@NotNull BasicCommand basicCommand); ++ ++ /** ++ * Sets the root permission node. Null by default. ++ * ++ * @param permission the root permission node ++ * @see #requires(Predicate) for a more flexible permission predicate ++ * @return self ++ */ ++ default @NotNull RegistrationBuilder permission(@Nullable String permission) { ++ return this.requires(permission == null ? s -> true : sender -> sender.hasPermission(permission)); ++ } ++ ++ /** ++ * Sets the root permission predicate. Is always true by default. ++ * ++ * @param requirement the root command permission predicate ++ * @see #permission(String) for a simple permission node ++ * @return self ++ */ ++ @NotNull RegistrationBuilder requires(@NotNull Predicate requirement); ++ ++ /** ++ * Sets the help description for the root literal node. Null by default. ++ * ++ * @param description the help description for the root literal node ++ * @return self ++ */ ++ @NotNull RegistrationBuilder description(@Nullable String description); ++ ++ /** ++ * Sets the aliases to register the basic command under. Empty by default. ++ * ++ * @param aliases a collection of aliases to register the basic command under ++ * @return self ++ */ ++ @NotNull RegistrationBuilder aliases(@NotNull String... aliases); ++ ++ /** ++ * Sets the aliases to register the basic command under. Empty by default. ++ * ++ * @param aliases a collection of aliases to register the basic command under ++ * @return self ++ */ ++ @NotNull RegistrationBuilder aliases(@NotNull Collection aliases); ++ } +} 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 @@ -620,10 +705,10 @@ index 0000000000000000000000000000000000000000..54288dbe7185b875a74184f002ee4de4 +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/Commands.java b/src/main/java/io/papermc/paper/command/brigadier/Commands.java new file mode 100644 -index 0000000000000000000000000000000000000000..ce60b618de10da7638f5aefa974aebe02600465c +index 0000000000000000000000000000000000000000..9260cf56e62859b1d5b181d10fd1b75f95e1578f --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/Commands.java -@@ -0,0 +1,266 @@ +@@ -0,0 +1,292 @@ +package io.papermc.paper.command.brigadier; + +import com.mojang.brigadier.CommandDispatcher; @@ -639,6 +724,7 @@ index 0000000000000000000000000000000000000000..ce60b618de10da7638f5aefa974aebe0 +import java.util.Collection; +import java.util.Collections; +import java.util.Set; ++import java.util.function.Consumer; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; @@ -838,7 +924,9 @@ index 0000000000000000000000000000000000000000..ce60b618de10da7638f5aefa974aebe0 + * @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) ++ * @deprecated use {@link #register(Consumer)}, including its new permission field + */ ++ @Deprecated(forRemoval = true) + default @Unmodifiable @NotNull Set register(final @NotNull String label, final @NotNull BasicCommand basicCommand) { + return this.register(label, null, Collections.emptyList(), basicCommand); + } @@ -850,7 +938,9 @@ index 0000000000000000000000000000000000000000..ce60b618de10da7638f5aefa974aebe0 + * @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) ++ * @deprecated use {@link #register(Consumer)}, including its new permission field + */ ++ @Deprecated(forRemoval = true) + default @Unmodifiable @NotNull Set register(final @NotNull String label, final @Nullable String description, final @NotNull BasicCommand basicCommand) { + return this.register(label, description, Collections.emptyList(), basicCommand); + } @@ -862,7 +952,9 @@ index 0000000000000000000000000000000000000000..ce60b618de10da7638f5aefa974aebe0 + * @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) ++ * @deprecated use {@link #register(Consumer)}, including its new permission field + */ ++ @Deprecated(forRemoval = true) + default @Unmodifiable @NotNull Set register(final @NotNull String label, final @NotNull Collection aliases, final @NotNull BasicCommand basicCommand) { + return this.register(label, null, aliases, basicCommand); + } @@ -875,7 +967,9 @@ index 0000000000000000000000000000000000000000..ce60b618de10da7638f5aefa974aebe0 + * @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) ++ * @deprecated use {@link #register(Consumer)}, including its new permission field + */ ++ @Deprecated(forRemoval = true) + @Unmodifiable @NotNull Set register(@NotNull String label, @Nullable String description, @NotNull Collection aliases, @NotNull BasicCommand basicCommand); + + /** @@ -887,8 +981,25 @@ index 0000000000000000000000000000000000000000..ce60b618de10da7638f5aefa974aebe0 + * @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) ++ * @deprecated use {@link #register(Consumer)}, including its new permission field ++ */ ++ @Deprecated(forRemoval = true) ++ default @Unmodifiable @NotNull Set register(@NotNull PluginMeta pluginMeta, @NotNull String label, @Nullable String description, @NotNull Collection aliases, @NotNull BasicCommand basicCommand) { ++ return this.register(registrationBuilder -> registrationBuilder.pluginMeta(pluginMeta) ++ .label(label) ++ .commandHandler(basicCommand) ++ .description(description) ++ .aliases(aliases)); ++ } ++ ++ /** ++ * Registers a command under the same logic as {@link Commands#register(PluginMeta, LiteralCommandNode, String, Collection)} ++ * but with {@link BasicCommand} and the builder parameters as a simplified handler interface. ++ * ++ * @param basicCommandRegistration the registration builder consumer ++ * @return successfully registered root command labels (including aliases and namespaced variants) + */ -+ @Unmodifiable @NotNull Set register(@NotNull PluginMeta pluginMeta, @NotNull String label, @Nullable String description, @NotNull Collection aliases, @NotNull BasicCommand basicCommand); ++ @Unmodifiable @NotNull Set register(@NotNull Consumer basicCommandRegistration); +} 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 diff --git a/patches/server/0975-Brigadier-based-command-API.patch b/patches/server/0975-Brigadier-based-command-API.patch index 71f121c880..5f16c419c6 100644 --- a/patches/server/0975-Brigadier-based-command-API.patch +++ b/patches/server/0975-Brigadier-based-command-API.patch @@ -510,6 +510,99 @@ index 0000000000000000000000000000000000000000..23525592d880f340745a28c956fa38d3 + } + +} +diff --git a/src/main/java/io/papermc/paper/command/brigadier/BasicCommandRegistration.java b/src/main/java/io/papermc/paper/command/brigadier/BasicCommandRegistration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9a07b3da38df6d38ed31360ab70565365e7f4076 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/brigadier/BasicCommandRegistration.java +@@ -0,0 +1,87 @@ ++package io.papermc.paper.command.brigadier; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.List; ++import java.util.Objects; ++import java.util.function.Predicate; ++import org.bukkit.command.CommandSender; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public final class BasicCommandRegistration implements BasicCommand.RegistrationBuilder { ++ ++ private Collection aliases = Collections.emptyList(); ++ private PluginMeta pluginMeta; ++ private String label; ++ private Predicate requirement; ++ private String description; ++ private BasicCommand commandHandler; ++ ++ @Override ++ public BasicCommand.@NotNull RegistrationBuilder pluginMeta(@NotNull final PluginMeta pluginMeta) { ++ this.pluginMeta = pluginMeta; ++ return this; ++ } ++ ++ @Override ++ public BasicCommand.@NotNull RegistrationBuilder label(@NotNull final String label) { ++ this.label = Objects.requireNonNull(label); ++ return this; ++ } ++ ++ @Override ++ public BasicCommand.@NotNull RegistrationBuilder description(@Nullable final String description) { ++ this.description = description; ++ return this; ++ } ++ ++ @Override ++ public BasicCommand.@NotNull RegistrationBuilder aliases(final @NotNull String... aliases) { ++ this.aliases = List.of(aliases); ++ return this; ++ } ++ ++ @Override ++ public BasicCommand.@NotNull RegistrationBuilder aliases(@NotNull final Collection aliases) { ++ this.aliases = Collections.unmodifiableCollection(aliases); ++ return this; ++ } ++ ++ @Override ++ public BasicCommand.@NotNull RegistrationBuilder commandHandler(@NotNull final BasicCommand basicCommand) { ++ this.commandHandler = Objects.requireNonNull(basicCommand); ++ return this; ++ } ++ ++ @Override ++ public BasicCommand.@NotNull RegistrationBuilder requires(@NotNull final Predicate requirement) { ++ this.requirement = Objects.requireNonNull(requirement); ++ return this; ++ } ++ ++ public @Nullable PluginMeta pluginMeta() { ++ return this.pluginMeta; ++ } ++ ++ public @NotNull String label() { ++ return Objects.requireNonNull(this.label, "Label missing in registration"); ++ } ++ ++ public @NotNull Predicate requirement() { ++ return this.requirement; ++ } ++ ++ public @Nullable String description() { ++ return this.description; ++ } ++ ++ public @NotNull Collection aliases() { ++ return this.aliases; ++ } ++ ++ public @NotNull BasicCommand commandHandler() { ++ return Objects.requireNonNull(this.commandHandler, "BasicCommand handler missing in registration"); ++ } ++} diff --git a/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerImpl.java b/src/main/java/io/papermc/paper/command/brigadier/MessageComponentSerializerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0b33c6cf2366568641e6f2fd7f74fb74f6ea0145 @@ -686,10 +779,10 @@ index 0000000000000000000000000000000000000000..1b1642f306771f029e6214a2e2ebebb6 +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java b/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java new file mode 100644 -index 0000000000000000000000000000000000000000..27509813a90980be1dfc7bde27d0eba60adfc820 +index 0000000000000000000000000000000000000000..9893c6409613471251cf2b2064f1e1e5068b3cc4 --- /dev/null +++ b/src/main/java/io/papermc/paper/command/brigadier/PaperCommands.java -@@ -0,0 +1,193 @@ +@@ -0,0 +1,207 @@ +package io.papermc.paper.command.brigadier; + +import com.google.common.base.Preconditions; @@ -709,6 +802,7 @@ index 0000000000000000000000000000000000000000..27509813a90980be1dfc7bde27d0eba6 +import java.util.List; +import java.util.Locale; +import java.util.Set; ++import java.util.function.Consumer; +import net.minecraft.commands.CommandBuildContext; +import org.apache.commons.lang3.StringUtils; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -854,33 +948,46 @@ index 0000000000000000000000000000000000000000..27509813a90980be1dfc7bde27d0eba6 + + @Override + public @Unmodifiable Set register(final String label, final @Nullable String description, final Collection aliases, final BasicCommand basicCommand) { -+ return this.register(requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(), label, description, aliases, basicCommand); ++ return this.register( ++ requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(), ++ label, ++ description, ++ aliases, ++ basicCommand ++ ); + } + + @Override -+ public @Unmodifiable Set register(final PluginMeta pluginMeta, final String label, final @Nullable String description, final Collection aliases, final BasicCommand basicCommand) { -+ final LiteralArgumentBuilder builder = Commands.literal(label) ++ public @Unmodifiable @NotNull Set register(@NotNull final Consumer registrationConsumer) { ++ final BasicCommandRegistration registration = new BasicCommandRegistration(); ++ registrationConsumer.accept(registration); ++ final LiteralArgumentBuilder builder = Commands.literal(registration.label()) ++ .requires(stack -> registration.requirement().test(stack.getSender())) + .then( + Commands.argument("args", StringArgumentType.greedyString()) + .suggests((context, suggestionsBuilder) -> { + final String[] args = StringUtils.split(suggestionsBuilder.getRemaining()); -+ final SuggestionsBuilder offsetSuggestionsBuilder = suggestionsBuilder.createOffset(suggestionsBuilder.getInput().lastIndexOf(' ') + 1);; ++ final SuggestionsBuilder offsetSuggestionsBuilder = suggestionsBuilder.createOffset(suggestionsBuilder.getInput().lastIndexOf(' ') + 1); + -+ final Collection suggestions = basicCommand.suggest(context.getSource(), args); ++ final Collection suggestions = registration.commandHandler().suggest(context.getSource(), args); + suggestions.forEach(offsetSuggestionsBuilder::suggest); + return offsetSuggestionsBuilder.buildFuture(); + }) + .executes((stack) -> { -+ basicCommand.execute(stack.getSource(), StringUtils.split(stack.getArgument("args", String.class), ' ')); ++ registration.commandHandler().execute(stack.getSource(), StringUtils.split(stack.getArgument("args", String.class), ' ')); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + }) + ) + .executes((stack) -> { -+ basicCommand.execute(stack.getSource(), new String[0]); ++ registration.commandHandler().execute(stack.getSource(), new String[0]); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + }); + -+ return this.register(pluginMeta, builder.build(), description, aliases); ++ PluginMeta pluginMeta = registration.pluginMeta(); ++ if (pluginMeta == null) { ++ pluginMeta = requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(); ++ } ++ return this.register(pluginMeta, builder.build(), registration.description(), registration.aliases()); + } +} diff --git a/src/main/java/io/papermc/paper/command/brigadier/PluginCommandNode.java b/src/main/java/io/papermc/paper/command/brigadier/PluginCommandNode.java -- cgit v1.2.3